From a652fefa9174d116c9ad4fd7f7e78d8a725af1ef Mon Sep 17 00:00:00 2001 From: Aosen Xiong Date: Thu, 2 May 2024 20:54:33 -0400 Subject: [PATCH 01/11] Initial pico in eisop --- .../checker/immutability/qual/Assignable.java | 15 + .../checker/immutability/qual/Bottom.java | 29 + .../checker/immutability/qual/Immutable.java | 69 ++ .../checker/immutability/qual/Mutable.java | 19 + .../qual/ObjectIdentityMethod.java | 12 + .../immutability/qual/PolyMutable.java | 23 + .../checker/immutability/qual/Readonly.java | 18 + .../qual/ReceiverDependantMutable.java | 17 + .../ExtendedViewpointAdapter.java | 14 + .../ObjectIdentityMethodEnforcer.java | 111 +++ .../PICOAnnotationMirrorHolder.java | 39 + .../checker/immutability/PICOChecker.java | 62 ++ .../immutability/PICONoInitAnalysis.java | 36 + .../PICONoInitAnnotatedTypeFactory.java | 699 ++++++++++++++++++ .../checker/immutability/PICONoInitStore.java | 26 + .../immutability/PICONoInitSubchecker.java | 28 + .../immutability/PICONoInitTransfer.java | 46 ++ .../checker/immutability/PICONoInitValue.java | 17 + .../immutability/PICONoInitVisitor.java | 687 +++++++++++++++++ .../checker/immutability/PICOTypeUtil.java | 474 ++++++++++++ .../checker/immutability/PICOValidator.java | 148 ++++ .../immutability/PICOViewpointAdapter.java | 101 +++ .../ViewpointAdapterGettable.java | 5 + .../checker/immutability/messages.properties | 15 + .../test/junit/ImmutabilityTypecheckTest.java | 18 + .../AnnotationApplicationOrder.java | 18 + .../immutability/AnonymousClassBound.java | 9 + checker/tests/immutability/Arrays.java | 39 + .../tests/immutability/AssignableExample.java | 44 ++ checker/tests/immutability/BigPrefix.java | 13 + .../tests/immutability/BinaryOperator.java | 13 + .../tests/immutability/BottomArrayWrite.java | 36 + .../tests/immutability/BoundIncompatible.java | 14 + .../tests/immutability/CloneCaseStudy.java | 82 ++ checker/tests/immutability/CloneProblem.java | 17 + .../tests/immutability/CompatabilityBEI1.java | 61 ++ .../tests/immutability/CompatibilityBEI2.java | 60 ++ .../tests/immutability/ComplicatedTest.java | 77 ++ checker/tests/immutability/CopyToCast.java | 26 + checker/tests/immutability/DateCell.java | 67 ++ checker/tests/immutability/DateCell2.java | 27 + checker/tests/immutability/DateCell3.java | 21 + checker/tests/immutability/DeepMutable.java | 38 + .../DeepRDMDefaultForInstanceField.java | 11 + .../immutability/DeepStaticRDMForbidden.java | 10 + .../DefaultRDMElementUsageToMutable.java | 27 + .../immutability/DiamondTreeProblem.java | 15 + .../EnumConstantNotAlwaysMutable.java | 29 + checker/tests/immutability/EnumTests.java | 27 + .../immutability/FbcViolatingMethod.java | 8 + .../tests/immutability/FieldAssignment.java | 34 + .../tests/immutability/FieldsInitialized.java | 31 + .../immutability/ForbidAssignmentCase.java | 71 ++ checker/tests/immutability/Generic.java | 36 + .../tests/immutability/GenericInterfaces.java | 19 + .../immutability/HashCodeSafetyExample.java | 42 ++ .../ImmutabilityFactoryPattern.java | 21 + .../tests/immutability/ImmutableClass1.java | 34 + .../tests/immutability/ImmutableClass2.java | 50 ++ .../tests/immutability/ImmutableClass3.java | 31 + .../immutability/ImmutableConstructor.java | 55 ++ .../ImmutableDefaultConstructor.java | 9 + .../ImmutableListImmutableElement.java | 29 + .../immutability/ImmutableListProblem.java | 64 ++ .../tests/immutability/ImmutablePerson.java | 46 ++ .../ImplicitAppliesToMethodReceiver.java | 5 + .../tests/immutability/IncompatibleCast.java | 15 + .../InitializationBlockProblem.java | 13 + .../immutability/InvalidAssignability.java | 17 + checker/tests/immutability/InvalidBound.java | 28 + .../InvariantFieldInitialized.java | 23 + .../immutability/LocalVariableReturned.java | 10 + .../LocalVariableTypeVariable.java | 7 + .../LocalVariableUsedAsArgument.java | 69 ++ .../MethodReceiverNotInhericClassBound.java | 9 + .../immutability/MutableConstructor.java | 44 ++ checker/tests/immutability/MutableField.java | 12 + .../immutability/NewAnnonymousClass.java | 28 + .../NotEveryInstFieldDefaultToRDM.java | 24 + .../ObjectIdentityMethodTest.java | 121 +++ checker/tests/immutability/ObjectMethods.java | 164 ++++ .../immutability/OnlyOneModifierIsUse.java | 12 + .../tests/immutability/OverrideEquals.java | 32 + checker/tests/immutability/Planet.java | 92 +++ .../tests/immutability/PolyMutableBug.java | 22 + .../PolyMutableOnConstructorParameters.java | 14 + checker/tests/immutability/Polymorphism.java | 60 ++ .../immutability/PolymorphismProblem.java | 23 + checker/tests/immutability/Primitive.java | 32 + checker/tests/immutability/Primitive2.java | 19 + checker/tests/immutability/Primitive3.java | 18 + checker/tests/immutability/Primitive4.java | 9 + .../tests/immutability/PritimitveReturn.java | 31 + .../RDMAllowedAsMethodReceiver.java | 15 + checker/tests/immutability/RDMBug.java | 16 + checker/tests/immutability/RDMField.java | 44 ++ checker/tests/immutability/RDMFieldInst.java | 31 + checker/tests/immutability/RGB.java | 75 ++ checker/tests/immutability/RawList.java | 49 ++ checker/tests/immutability/RawType.java | 30 + .../immutability/ReadonlyConstructor.java | 8 + .../ReadonlyMayCaptureMutable.java | 28 + .../ReceiverDependantMutableConstructor.java | 64 ++ .../ReceiverTypeOutsideConstructor.java | 154 ++++ .../tests/immutability/RefineFromNull.java | 36 + .../immutability/ShouldFailButDidnot.java | 10 + checker/tests/immutability/Static.java | 53 ++ checker/tests/immutability/StaticFields.java | 40 + checker/tests/immutability/SuperClass.java | 46 ++ checker/tests/immutability/SuperClass2.java | 50 ++ checker/tests/immutability/SuperClass3.java | 38 + .../immutability/SuperMethodInvocation.java | 54 ++ .../immutability/SupportedBuilderPattern.java | 56 ++ .../ThrowableOverridingError.java | 16 + checker/tests/immutability/Transitive.java | 45 ++ .../immutability/TwoDimensionalArray.java | 25 + .../immutability/TypeParameterFieldRDA.java | 21 + .../UnaryAndCompoundAssignment.java | 21 + .../tests/immutability/UnaryOperators.java | 13 + .../immutability/UnsupportedCarBuilder.java | 73 ++ .../ViewpointAdaptationRules.java | 177 +++++ .../WildcardExplicitLowerBound.java | 31 + 122 files changed, 6361 insertions(+) create mode 100644 checker-qual/src/main/java/org/checkerframework/checker/immutability/qual/Assignable.java create mode 100644 checker-qual/src/main/java/org/checkerframework/checker/immutability/qual/Bottom.java create mode 100644 checker-qual/src/main/java/org/checkerframework/checker/immutability/qual/Immutable.java create mode 100644 checker-qual/src/main/java/org/checkerframework/checker/immutability/qual/Mutable.java create mode 100644 checker-qual/src/main/java/org/checkerframework/checker/immutability/qual/ObjectIdentityMethod.java create mode 100644 checker-qual/src/main/java/org/checkerframework/checker/immutability/qual/PolyMutable.java create mode 100644 checker-qual/src/main/java/org/checkerframework/checker/immutability/qual/Readonly.java create mode 100644 checker-qual/src/main/java/org/checkerframework/checker/immutability/qual/ReceiverDependantMutable.java create mode 100644 checker/src/main/java/org/checkerframework/checker/immutability/ExtendedViewpointAdapter.java create mode 100644 checker/src/main/java/org/checkerframework/checker/immutability/ObjectIdentityMethodEnforcer.java create mode 100644 checker/src/main/java/org/checkerframework/checker/immutability/PICOAnnotationMirrorHolder.java create mode 100644 checker/src/main/java/org/checkerframework/checker/immutability/PICOChecker.java create mode 100644 checker/src/main/java/org/checkerframework/checker/immutability/PICONoInitAnalysis.java create mode 100644 checker/src/main/java/org/checkerframework/checker/immutability/PICONoInitAnnotatedTypeFactory.java create mode 100644 checker/src/main/java/org/checkerframework/checker/immutability/PICONoInitStore.java create mode 100644 checker/src/main/java/org/checkerframework/checker/immutability/PICONoInitSubchecker.java create mode 100644 checker/src/main/java/org/checkerframework/checker/immutability/PICONoInitTransfer.java create mode 100644 checker/src/main/java/org/checkerframework/checker/immutability/PICONoInitValue.java create mode 100644 checker/src/main/java/org/checkerframework/checker/immutability/PICONoInitVisitor.java create mode 100644 checker/src/main/java/org/checkerframework/checker/immutability/PICOTypeUtil.java create mode 100644 checker/src/main/java/org/checkerframework/checker/immutability/PICOValidator.java create mode 100644 checker/src/main/java/org/checkerframework/checker/immutability/PICOViewpointAdapter.java create mode 100644 checker/src/main/java/org/checkerframework/checker/immutability/ViewpointAdapterGettable.java create mode 100644 checker/src/main/java/org/checkerframework/checker/immutability/messages.properties create mode 100644 checker/src/test/java/org/checkerframework/checker/test/junit/ImmutabilityTypecheckTest.java create mode 100644 checker/tests/immutability/AnnotationApplicationOrder.java create mode 100644 checker/tests/immutability/AnonymousClassBound.java create mode 100644 checker/tests/immutability/Arrays.java create mode 100644 checker/tests/immutability/AssignableExample.java create mode 100644 checker/tests/immutability/BigPrefix.java create mode 100644 checker/tests/immutability/BinaryOperator.java create mode 100644 checker/tests/immutability/BottomArrayWrite.java create mode 100644 checker/tests/immutability/BoundIncompatible.java create mode 100644 checker/tests/immutability/CloneCaseStudy.java create mode 100644 checker/tests/immutability/CloneProblem.java create mode 100644 checker/tests/immutability/CompatabilityBEI1.java create mode 100644 checker/tests/immutability/CompatibilityBEI2.java create mode 100644 checker/tests/immutability/ComplicatedTest.java create mode 100644 checker/tests/immutability/CopyToCast.java create mode 100644 checker/tests/immutability/DateCell.java create mode 100644 checker/tests/immutability/DateCell2.java create mode 100644 checker/tests/immutability/DateCell3.java create mode 100644 checker/tests/immutability/DeepMutable.java create mode 100644 checker/tests/immutability/DeepRDMDefaultForInstanceField.java create mode 100644 checker/tests/immutability/DeepStaticRDMForbidden.java create mode 100644 checker/tests/immutability/DefaultRDMElementUsageToMutable.java create mode 100644 checker/tests/immutability/DiamondTreeProblem.java create mode 100644 checker/tests/immutability/EnumConstantNotAlwaysMutable.java create mode 100644 checker/tests/immutability/EnumTests.java create mode 100644 checker/tests/immutability/FbcViolatingMethod.java create mode 100644 checker/tests/immutability/FieldAssignment.java create mode 100644 checker/tests/immutability/FieldsInitialized.java create mode 100644 checker/tests/immutability/ForbidAssignmentCase.java create mode 100644 checker/tests/immutability/Generic.java create mode 100644 checker/tests/immutability/GenericInterfaces.java create mode 100644 checker/tests/immutability/HashCodeSafetyExample.java create mode 100644 checker/tests/immutability/ImmutabilityFactoryPattern.java create mode 100644 checker/tests/immutability/ImmutableClass1.java create mode 100644 checker/tests/immutability/ImmutableClass2.java create mode 100644 checker/tests/immutability/ImmutableClass3.java create mode 100644 checker/tests/immutability/ImmutableConstructor.java create mode 100644 checker/tests/immutability/ImmutableDefaultConstructor.java create mode 100644 checker/tests/immutability/ImmutableListImmutableElement.java create mode 100644 checker/tests/immutability/ImmutableListProblem.java create mode 100644 checker/tests/immutability/ImmutablePerson.java create mode 100644 checker/tests/immutability/ImplicitAppliesToMethodReceiver.java create mode 100644 checker/tests/immutability/IncompatibleCast.java create mode 100644 checker/tests/immutability/InitializationBlockProblem.java create mode 100644 checker/tests/immutability/InvalidAssignability.java create mode 100644 checker/tests/immutability/InvalidBound.java create mode 100644 checker/tests/immutability/InvariantFieldInitialized.java create mode 100644 checker/tests/immutability/LocalVariableReturned.java create mode 100644 checker/tests/immutability/LocalVariableTypeVariable.java create mode 100644 checker/tests/immutability/LocalVariableUsedAsArgument.java create mode 100644 checker/tests/immutability/MethodReceiverNotInhericClassBound.java create mode 100644 checker/tests/immutability/MutableConstructor.java create mode 100644 checker/tests/immutability/MutableField.java create mode 100644 checker/tests/immutability/NewAnnonymousClass.java create mode 100644 checker/tests/immutability/NotEveryInstFieldDefaultToRDM.java create mode 100644 checker/tests/immutability/ObjectIdentityMethodTest.java create mode 100644 checker/tests/immutability/ObjectMethods.java create mode 100644 checker/tests/immutability/OnlyOneModifierIsUse.java create mode 100644 checker/tests/immutability/OverrideEquals.java create mode 100644 checker/tests/immutability/Planet.java create mode 100644 checker/tests/immutability/PolyMutableBug.java create mode 100644 checker/tests/immutability/PolyMutableOnConstructorParameters.java create mode 100644 checker/tests/immutability/Polymorphism.java create mode 100644 checker/tests/immutability/PolymorphismProblem.java create mode 100644 checker/tests/immutability/Primitive.java create mode 100644 checker/tests/immutability/Primitive2.java create mode 100644 checker/tests/immutability/Primitive3.java create mode 100644 checker/tests/immutability/Primitive4.java create mode 100644 checker/tests/immutability/PritimitveReturn.java create mode 100644 checker/tests/immutability/RDMAllowedAsMethodReceiver.java create mode 100644 checker/tests/immutability/RDMBug.java create mode 100644 checker/tests/immutability/RDMField.java create mode 100644 checker/tests/immutability/RDMFieldInst.java create mode 100644 checker/tests/immutability/RGB.java create mode 100644 checker/tests/immutability/RawList.java create mode 100644 checker/tests/immutability/RawType.java create mode 100644 checker/tests/immutability/ReadonlyConstructor.java create mode 100644 checker/tests/immutability/ReadonlyMayCaptureMutable.java create mode 100644 checker/tests/immutability/ReceiverDependantMutableConstructor.java create mode 100644 checker/tests/immutability/ReceiverTypeOutsideConstructor.java create mode 100644 checker/tests/immutability/RefineFromNull.java create mode 100644 checker/tests/immutability/ShouldFailButDidnot.java create mode 100644 checker/tests/immutability/Static.java create mode 100644 checker/tests/immutability/StaticFields.java create mode 100644 checker/tests/immutability/SuperClass.java create mode 100644 checker/tests/immutability/SuperClass2.java create mode 100644 checker/tests/immutability/SuperClass3.java create mode 100644 checker/tests/immutability/SuperMethodInvocation.java create mode 100644 checker/tests/immutability/SupportedBuilderPattern.java create mode 100644 checker/tests/immutability/ThrowableOverridingError.java create mode 100644 checker/tests/immutability/Transitive.java create mode 100644 checker/tests/immutability/TwoDimensionalArray.java create mode 100644 checker/tests/immutability/TypeParameterFieldRDA.java create mode 100644 checker/tests/immutability/UnaryAndCompoundAssignment.java create mode 100644 checker/tests/immutability/UnaryOperators.java create mode 100644 checker/tests/immutability/UnsupportedCarBuilder.java create mode 100644 checker/tests/immutability/ViewpointAdaptationRules.java create mode 100644 checker/tests/immutability/WildcardExplicitLowerBound.java diff --git a/checker-qual/src/main/java/org/checkerframework/checker/immutability/qual/Assignable.java b/checker-qual/src/main/java/org/checkerframework/checker/immutability/qual/Assignable.java new file mode 100644 index 00000000000..9677a1299fd --- /dev/null +++ b/checker-qual/src/main/java/org/checkerframework/checker/immutability/qual/Assignable.java @@ -0,0 +1,15 @@ +package org.checkerframework.checker.immutability.qual; + +import org.checkerframework.checker.initialization.qual.HoldsForDefaultValue; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Documented +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.FIELD}) +@HoldsForDefaultValue +public @interface Assignable {} diff --git a/checker-qual/src/main/java/org/checkerframework/checker/immutability/qual/Bottom.java b/checker-qual/src/main/java/org/checkerframework/checker/immutability/qual/Bottom.java new file mode 100644 index 00000000000..2db7d8ae95e --- /dev/null +++ b/checker-qual/src/main/java/org/checkerframework/checker/immutability/qual/Bottom.java @@ -0,0 +1,29 @@ +package org.checkerframework.checker.immutability.qual; + +import org.checkerframework.framework.qual.DefaultFor; +import org.checkerframework.framework.qual.SubtypeOf; +import org.checkerframework.framework.qual.TargetLocations; +import org.checkerframework.framework.qual.TypeKind; +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; + +/** + * {@code @Bottom} can only be annotated before a type parameter (For example, {@code class + * C<@Bottom T extends MutableBox>{}}). This means {@code @Bottom} is the lower bound for this type + * parameter. + * + *

User can explicitly write {@code @Bottom} but it's not necessary because it's automatically + * inferred, and we have -AwarnRedundantAnnotations flag to warn about redundant annotations. + */ +@SubtypeOf({Mutable.class, Immutable.class, ReceiverDependantMutable.class}) +@DefaultFor(typeKinds = {TypeKind.NULL}) +@Documented +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.TYPE_PARAMETER}) +@TargetLocations({TypeUseLocation.LOWER_BOUND}) +public @interface Bottom {} diff --git a/checker-qual/src/main/java/org/checkerframework/checker/immutability/qual/Immutable.java b/checker-qual/src/main/java/org/checkerframework/checker/immutability/qual/Immutable.java new file mode 100644 index 00000000000..1e2fee4c943 --- /dev/null +++ b/checker-qual/src/main/java/org/checkerframework/checker/immutability/qual/Immutable.java @@ -0,0 +1,69 @@ +package org.checkerframework.checker.immutability.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 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 java.math.BigDecimal; +import java.math.BigInteger; + +@SubtypeOf({Readonly.class}) +@Documented +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER}) +@DefaultFor( + types = { + Enum.class, + String.class, + Double.class, + Boolean.class, + Byte.class, + Character.class, + Float.class, + Integer.class, + Long.class, + Short.class, + Number.class, + BigDecimal.class, + BigInteger.class + }, + typeKinds = { + TypeKind.INT, + TypeKind.BYTE, + TypeKind.SHORT, + TypeKind.BOOLEAN, + TypeKind.LONG, + TypeKind.CHAR, + TypeKind.FLOAT, + TypeKind.DOUBLE + }) +@QualifierForLiterals({LiteralKind.PRIMITIVE, LiteralKind.STRING}) +@UpperBoundFor( + typeKinds = { + TypeKind.INT, TypeKind.BYTE, TypeKind.SHORT, TypeKind.BOOLEAN, + TypeKind.LONG, TypeKind.CHAR, TypeKind.FLOAT, TypeKind.DOUBLE + }, + types = { + Enum.class, + String.class, + Double.class, + Boolean.class, + Byte.class, + Character.class, + Float.class, + Integer.class, + Long.class, + Short.class, + Number.class, + BigDecimal.class, + BigInteger.class + }) +public @interface Immutable {} diff --git a/checker-qual/src/main/java/org/checkerframework/checker/immutability/qual/Mutable.java b/checker-qual/src/main/java/org/checkerframework/checker/immutability/qual/Mutable.java new file mode 100644 index 00000000000..775d7d4ce14 --- /dev/null +++ b/checker-qual/src/main/java/org/checkerframework/checker/immutability/qual/Mutable.java @@ -0,0 +1,19 @@ +package org.checkerframework.checker.immutability.qual; + +import org.checkerframework.checker.initialization.qual.HoldsForDefaultValue; +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; + +@SubtypeOf({Readonly.class}) +@DefaultQualifierInHierarchy +@Documented +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER}) +@HoldsForDefaultValue +public @interface Mutable {} diff --git a/checker-qual/src/main/java/org/checkerframework/checker/immutability/qual/ObjectIdentityMethod.java b/checker-qual/src/main/java/org/checkerframework/checker/immutability/qual/ObjectIdentityMethod.java new file mode 100644 index 00000000000..2df91e00893 --- /dev/null +++ b/checker-qual/src/main/java/org/checkerframework/checker/immutability/qual/ObjectIdentityMethod.java @@ -0,0 +1,12 @@ +package org.checkerframework.checker.immutability.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; + +@Documented +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.METHOD}) +public @interface ObjectIdentityMethod {} diff --git a/checker-qual/src/main/java/org/checkerframework/checker/immutability/qual/PolyMutable.java b/checker-qual/src/main/java/org/checkerframework/checker/immutability/qual/PolyMutable.java new file mode 100644 index 00000000000..2184a01fb4d --- /dev/null +++ b/checker-qual/src/main/java/org/checkerframework/checker/immutability/qual/PolyMutable.java @@ -0,0 +1,23 @@ +package org.checkerframework.checker.immutability.qual; + +import org.checkerframework.framework.qual.PolymorphicQualifier; +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; + +@Documented +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER}) +@PolymorphicQualifier(Readonly.class) +@TargetLocations({ + TypeUseLocation.PARAMETER, + TypeUseLocation.RECEIVER, + TypeUseLocation.RETURN, + TypeUseLocation.LOCAL_VARIABLE +}) +public @interface PolyMutable {} diff --git a/checker-qual/src/main/java/org/checkerframework/checker/immutability/qual/Readonly.java b/checker-qual/src/main/java/org/checkerframework/checker/immutability/qual/Readonly.java new file mode 100644 index 00000000000..51921e323b5 --- /dev/null +++ b/checker-qual/src/main/java/org/checkerframework/checker/immutability/qual/Readonly.java @@ -0,0 +1,18 @@ +package org.checkerframework.checker.immutability.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; + +@SubtypeOf({}) +@Documented +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER}) +@DefaultFor({TypeUseLocation.IMPLICIT_UPPER_BOUND}) +public @interface Readonly {} diff --git a/checker-qual/src/main/java/org/checkerframework/checker/immutability/qual/ReceiverDependantMutable.java b/checker-qual/src/main/java/org/checkerframework/checker/immutability/qual/ReceiverDependantMutable.java new file mode 100644 index 00000000000..196dc154e17 --- /dev/null +++ b/checker-qual/src/main/java/org/checkerframework/checker/immutability/qual/ReceiverDependantMutable.java @@ -0,0 +1,17 @@ +package org.checkerframework.checker.immutability.qual; + +import org.checkerframework.checker.initialization.qual.HoldsForDefaultValue; +import org.checkerframework.framework.qual.SubtypeOf; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@SubtypeOf(Readonly.class) +@Documented +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER}) +@HoldsForDefaultValue +public @interface ReceiverDependantMutable {} diff --git a/checker/src/main/java/org/checkerframework/checker/immutability/ExtendedViewpointAdapter.java b/checker/src/main/java/org/checkerframework/checker/immutability/ExtendedViewpointAdapter.java new file mode 100644 index 00000000000..dfcfe719fdb --- /dev/null +++ b/checker/src/main/java/org/checkerframework/checker/immutability/ExtendedViewpointAdapter.java @@ -0,0 +1,14 @@ +package org.checkerframework.checker.immutability; + +import org.checkerframework.framework.type.AnnotatedTypeMirror; +import org.checkerframework.framework.type.ViewpointAdapter; + +import javax.lang.model.element.AnnotationMirror; + +public interface ExtendedViewpointAdapter extends ViewpointAdapter { + AnnotatedTypeMirror rawCombineAnnotationWithType( + AnnotationMirror anno, AnnotatedTypeMirror type); + + AnnotationMirror rawCombineAnnotationWithAnnotation( + AnnotationMirror anno, AnnotationMirror type); +} diff --git a/checker/src/main/java/org/checkerframework/checker/immutability/ObjectIdentityMethodEnforcer.java b/checker/src/main/java/org/checkerframework/checker/immutability/ObjectIdentityMethodEnforcer.java new file mode 100644 index 00000000000..d4704f579b3 --- /dev/null +++ b/checker/src/main/java/org/checkerframework/checker/immutability/ObjectIdentityMethodEnforcer.java @@ -0,0 +1,111 @@ +package org.checkerframework.checker.immutability; + +import static org.checkerframework.checker.immutability.PICOAnnotationMirrorHolder.MUTABLE; +import static org.checkerframework.checker.immutability.PICOAnnotationMirrorHolder.READONLY; + +import com.sun.source.tree.IdentifierTree; +import com.sun.source.tree.MemberSelectTree; +import com.sun.source.tree.MethodInvocationTree; +import com.sun.source.tree.Tree; +import com.sun.source.util.TreePath; +import com.sun.source.util.TreePathScanner; + +import org.checkerframework.common.basetype.BaseTypeChecker; +import org.checkerframework.framework.util.AnnotatedTypes; +import org.checkerframework.javacutil.ElementUtils; +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 ObjectIdentityMethodEnforcer extends TreePathScanner { + + private PICONoInitAnnotatedTypeFactory typeFactory; + private BaseTypeChecker checker; + + private ObjectIdentityMethodEnforcer( + PICONoInitAnnotatedTypeFactory typeFactory, BaseTypeChecker checker) { + this.typeFactory = typeFactory; + this.checker = checker; + } + + // Main entry + public static void check( + TreePath statement, + PICONoInitAnnotatedTypeFactory typeFactory, + BaseTypeChecker checker) { + if (statement == null) return; + ObjectIdentityMethodEnforcer asfchecker = + new ObjectIdentityMethodEnforcer(typeFactory, checker); + asfchecker.scan(statement, null); + } + + @Override + public Void visitMethodInvocation(MethodInvocationTree node, Void aVoid) { + Element elt = TreeUtils.elementFromUse(node); + checkMethod(node, elt); + return super.visitMethodInvocation(node, aVoid); + } + + private void checkMethod(MethodInvocationTree node, Element elt) { + assert elt instanceof ExecutableElement; + if (ElementUtils.isStatic(elt)) { + return; // Doesn't check static method invocation because it doesn't access instance + // field + } + if (!PICOTypeUtil.isObjectIdentityMethod((ExecutableElement) elt, typeFactory)) { + // Report warning since invoked method is not only dependant on abstract state fields, + // but we + // don't know whether this method invocation's result flows into the hashcode or not. + checker.reportWarning(node, "object.identity.method.invocation.invalid", elt); + } + } + + @Override + public Void visitIdentifier(IdentifierTree node, Void aVoid) { + Element elt = TreeUtils.elementFromUse(node); + checkField(node, elt); + return super.visitIdentifier(node, aVoid); + } + + @Override + public Void visitMemberSelect(MemberSelectTree node, Void aVoid) { + Element elt = TreeUtils.elementFromUse(node); + checkField(node, elt); + return super.visitMemberSelect(node, aVoid); + } + + private void checkField(Tree node, Element elt) { + if (elt == null) return; + if (elt.getSimpleName().contentEquals("this") + || elt.getSimpleName().contentEquals("super")) { + return; + } + if (elt.getKind() == ElementKind.FIELD) { + if (ElementUtils.isStatic(elt)) { + checker.reportWarning(node, "object.identity.static.field.access.forbidden", elt); + } else { + if (!isInAbstractState(elt, typeFactory)) { + // Report warning since accessed field is not within abstract state + checker.reportWarning(node, "object.identity.field.access.invalid", elt); + } + } + } + } + + // Deeply test if a field is in abstract state or not. For composite types: array component, + // type arguments, upper bound of type parameter uses are also checked. + private boolean isInAbstractState(Element elt, PICONoInitAnnotatedTypeFactory typeFactory) { + boolean in = true; + if (PICOTypeUtil.isAssignableField(elt, typeFactory)) { + in = false; + } else if (AnnotatedTypes.containsModifier(typeFactory.getAnnotatedType(elt), MUTABLE)) { + in = false; + } else if (AnnotatedTypes.containsModifier(typeFactory.getAnnotatedType(elt), READONLY)) { + in = false; + } + + return in; + } +} diff --git a/checker/src/main/java/org/checkerframework/checker/immutability/PICOAnnotationMirrorHolder.java b/checker/src/main/java/org/checkerframework/checker/immutability/PICOAnnotationMirrorHolder.java new file mode 100644 index 00000000000..388652ddf6d --- /dev/null +++ b/checker/src/main/java/org/checkerframework/checker/immutability/PICOAnnotationMirrorHolder.java @@ -0,0 +1,39 @@ +package org.checkerframework.checker.immutability; + +import org.checkerframework.checker.immutability.qual.Bottom; +import org.checkerframework.checker.immutability.qual.Immutable; +import org.checkerframework.checker.immutability.qual.Mutable; +import org.checkerframework.checker.immutability.qual.PolyMutable; +import org.checkerframework.checker.immutability.qual.Readonly; +import org.checkerframework.checker.immutability.qual.ReceiverDependantMutable; +import org.checkerframework.checker.initialization.qual.Initialized; +import org.checkerframework.framework.source.SourceChecker; +import org.checkerframework.javacutil.AnnotationBuilder; + +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.util.Elements; + +/** A holder class that holds AnnotationMirrors that are shared by PICO and PICOInfer. */ +public class PICOAnnotationMirrorHolder { + + public static AnnotationMirror READONLY; + public static AnnotationMirror MUTABLE; + public static AnnotationMirror POLY_MUTABLE; + public static AnnotationMirror RECEIVER_DEPENDANT_MUTABLE; + public static AnnotationMirror IMMUTABLE; + public static AnnotationMirror BOTTOM; + public static AnnotationMirror COMMITED; + + public static void init(SourceChecker checker) { + Elements elements = checker.getElementUtils(); + READONLY = AnnotationBuilder.fromClass(elements, Readonly.class); + MUTABLE = AnnotationBuilder.fromClass(elements, Mutable.class); + POLY_MUTABLE = AnnotationBuilder.fromClass(elements, PolyMutable.class); + RECEIVER_DEPENDANT_MUTABLE = + AnnotationBuilder.fromClass(elements, ReceiverDependantMutable.class); + IMMUTABLE = AnnotationBuilder.fromClass(elements, Immutable.class); + BOTTOM = AnnotationBuilder.fromClass(elements, Bottom.class); + + COMMITED = AnnotationBuilder.fromClass(elements, Initialized.class); + } +} diff --git a/checker/src/main/java/org/checkerframework/checker/immutability/PICOChecker.java b/checker/src/main/java/org/checkerframework/checker/immutability/PICOChecker.java new file mode 100644 index 00000000000..918d65c2fcf --- /dev/null +++ b/checker/src/main/java/org/checkerframework/checker/immutability/PICOChecker.java @@ -0,0 +1,62 @@ +package org.checkerframework.checker.immutability; + +import org.checkerframework.checker.initialization.InitializationChecker; +import org.checkerframework.common.basetype.BaseTypeChecker; +import org.checkerframework.framework.source.SupportedOptions; + +import java.util.Map.Entry; +import java.util.Set; + +/** Created by mier on 20/06/17. */ +@SupportedOptions({"printFbcErrors"}) +public class PICOChecker extends InitializationChecker { + + public PICOChecker() {} + + @Override + public Class getTargetCheckerClass() { + return PICONoInitSubchecker.class; + } + + @Override + public void initChecker() { + super.initChecker(); + PICOAnnotationMirrorHolder.init(this); + } + + @Override + public boolean checkPrimitives() { + return true; + } + + @Override + protected boolean shouldAddShutdownHook() { + return hasOption("printFbcErrors") || super.shouldAddShutdownHook(); + } + + @Override + protected void shutdownHook() { + super.shutdownHook(); + if (hasOption("printFbcErrors")) { + printFbcViolatedMethods(); + } + } + + private void printFbcViolatedMethods() { + Set> entries = + ((PICONoInitVisitor) visitor).fbcViolatedMethods.entrySet(); + if (entries.isEmpty()) { + System.out.println( + "\n=============== Congrats! No Fbc Violations Found. ===============\n"); + } else { + System.out.println( + "\n===================== Fbc Violations Found! ======================"); + System.out.format("%30s%30s\n", "Method", "Violated Times"); + for (Entry e : entries) { + System.out.format("%30s%30s\n", e.getKey(), e.getValue()); + } + System.out.println( + "====================================================================\n"); + } + } +} diff --git a/checker/src/main/java/org/checkerframework/checker/immutability/PICONoInitAnalysis.java b/checker/src/main/java/org/checkerframework/checker/immutability/PICONoInitAnalysis.java new file mode 100644 index 00000000000..1f6fba80bd2 --- /dev/null +++ b/checker/src/main/java/org/checkerframework/checker/immutability/PICONoInitAnalysis.java @@ -0,0 +1,36 @@ +package org.checkerframework.checker.immutability; + +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; + +/** Created by mier on 15/08/17. */ +public class PICONoInitAnalysis + extends CFAbstractAnalysis { + + public PICONoInitAnalysis(BaseTypeChecker checker, PICONoInitAnnotatedTypeFactory factory) { + super(checker, factory, -1); + } + + @Override + public PICONoInitStore createEmptyStore(boolean sequentialSemantics) { + return new PICONoInitStore(this, sequentialSemantics); + } + + @Override + public PICONoInitStore createCopiedStore(PICONoInitStore picoNoInitStore) { + return new PICONoInitStore(picoNoInitStore); + } + + @Override + public PICONoInitValue createAbstractValue( + AnnotationMirrorSet annotations, TypeMirror underlyingType) { + if (!CFAbstractValue.validateSet(annotations, underlyingType, atypeFactory)) { + return null; + } + return new PICONoInitValue(this, annotations, underlyingType); + } +} diff --git a/checker/src/main/java/org/checkerframework/checker/immutability/PICONoInitAnnotatedTypeFactory.java b/checker/src/main/java/org/checkerframework/checker/immutability/PICONoInitAnnotatedTypeFactory.java new file mode 100644 index 00000000000..f6850c2410b --- /dev/null +++ b/checker/src/main/java/org/checkerframework/checker/immutability/PICONoInitAnnotatedTypeFactory.java @@ -0,0 +1,699 @@ +package org.checkerframework.checker.immutability; + +import static org.checkerframework.checker.immutability.PICOAnnotationMirrorHolder.*; + +import com.sun.source.tree.BinaryTree; +import com.sun.source.tree.IdentifierTree; +import com.sun.source.tree.MethodTree; +import com.sun.source.tree.NewArrayTree; +import com.sun.source.tree.NewClassTree; +import com.sun.source.tree.ParameterizedTypeTree; +import com.sun.source.tree.Tree; +import com.sun.source.tree.TypeCastTree; +import com.sun.source.tree.VariableTree; +import com.sun.source.util.TreePath; + +import org.checkerframework.checker.immutability.qual.Bottom; +import org.checkerframework.checker.immutability.qual.Immutable; +import org.checkerframework.checker.immutability.qual.Mutable; +import org.checkerframework.checker.immutability.qual.PolyMutable; +import org.checkerframework.checker.immutability.qual.Readonly; +import org.checkerframework.checker.immutability.qual.ReceiverDependantMutable; +import org.checkerframework.checker.initialization.InitializationFieldAccessTreeAnnotator; +import org.checkerframework.common.basetype.BaseTypeChecker; +import org.checkerframework.framework.qual.RelevantJavaTypes; +import org.checkerframework.framework.type.*; +import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedExecutableType; +import org.checkerframework.framework.type.treeannotator.ListTreeAnnotator; +import org.checkerframework.framework.type.treeannotator.LiteralTreeAnnotator; +import org.checkerframework.framework.type.treeannotator.PropagationTreeAnnotator; +import org.checkerframework.framework.type.treeannotator.TreeAnnotator; +import org.checkerframework.framework.type.typeannotator.*; +import org.checkerframework.javacutil.*; + +import java.lang.annotation.Annotation; +import java.util.*; + +import javax.lang.model.element.AnnotationMirror; +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.TypeKind; +import javax.lang.model.type.TypeMirror; + +/** + * AnnotatedTypeFactory for PICO. In addition to getting atms, it also propagates and applies + * mutability qualifiers correctly depending on AST locations(e.g. fields, binary trees) or + * methods(toString(), hashCode(), clone(), equals(Object o)) using TreeAnnotators and + * TypeAnnotators. It also applies implicits to method receiver that is not so by default in super + * implementation. + */ +// TODO Use @Immutable for classes that extends those predefined immutable classess like String or +// Number +// and explicitly annotated classes with @Immutable on its declaration +public class PICONoInitAnnotatedTypeFactory + extends GenericAnnotatedTypeFactory< + PICONoInitValue, PICONoInitStore, PICONoInitTransfer, PICONoInitAnalysis> + implements ViewpointAdapterGettable { + + public PICONoInitAnnotatedTypeFactory(BaseTypeChecker checker) { + super(checker); + postInit(); + // PICO aliasing is not implemented correctly + // remove for now + // addAliasedAnnotation(org.jmlspecs.annotation.Readonly.class, READONLY); + } + + @Override + protected Set> createSupportedTypeQualifiers() { + return new LinkedHashSet<>( + Arrays.asList( + Readonly.class, + Mutable.class, + PolyMutable.class, + ReceiverDependantMutable.class, + Immutable.class, + Bottom.class)); + } + + @Override + protected ViewpointAdapter createViewpointAdapter() { + return new PICOViewpointAdapter(this); + } + + /** Annotators are executed by the added order. Same for Type Annotator */ + @Override + protected TreeAnnotator createTreeAnnotator() { + List annotators = new ArrayList<>(5); + annotators.add(new InitializationFieldAccessTreeAnnotator(this)); + annotators.add(new PICOPropagationTreeAnnotator(this)); + annotators.add(new LiteralTreeAnnotator(this)); + annotators.add(new PICOSuperClauseAnnotator(this)); + annotators.add(new PICOTreeAnnotator(this)); + return new ListTreeAnnotator(annotators); + } + + // TODO Refactor super class to remove this duplicate code + @Override + protected TypeAnnotator createTypeAnnotator() { + /*Copied code start*/ + List typeAnnotators = new ArrayList<>(); + RelevantJavaTypes relevantJavaTypes = + checker.getClass().getAnnotation(RelevantJavaTypes.class); + if (relevantJavaTypes != null) { + // Class[] classes = relevantJavaTypes.value(); + // Must be first in order to annotated all irrelevant types that are not explicilty + // annotated. + typeAnnotators.add(new IrrelevantTypeAnnotator(this)); + } + typeAnnotators.add(new PropagationTypeAnnotator(this)); + /*Copied code ends*/ + // Adding order is important here. Because internally type annotators are using + // addMissingAnnotations() method, so if one annotator already applied the annotations, the + // others won't apply twice + // at the same location + typeAnnotators.add(new PICOTypeAnnotator(this)); + typeAnnotators.add(new PICODefaultForTypeAnnotator(this)); + typeAnnotators.add(new PICOEnumDefaultAnnotator(this)); + return new ListTypeAnnotator(typeAnnotators); + } + + @Override + public QualifierHierarchy createQualifierHierarchy() { + return new NoElementQualifierHierarchy(getSupportedTypeQualifiers(), elements, this); + } + + @Override + public ParameterizedExecutableType constructorFromUse(NewClassTree tree) { + boolean hasExplicitAnnos = false; + if (!getExplicitNewClassAnnos(tree).isEmpty()) { + hasExplicitAnnos = true; + } + ParameterizedExecutableType mType = super.constructorFromUse(tree); + AnnotatedExecutableType method = mType.executableType; + if (!hasExplicitAnnos && method.getReturnType().hasAnnotation(RECEIVER_DEPENDANT_MUTABLE)) { + method.getReturnType().replaceAnnotation(MUTABLE); + } + return mType; + } + + // /** Just to transfer the method from super class to package */ + // @Override + // protected boolean isInitializationAnnotation(AnnotationMirror anno) { + // return super.isInitializationAnnotation(anno); + // } + // + // @Override + // public AnnotationMirror getFieldInvariantAnnotation() { + // return IMMUTABLE; + // } + // + // /** This affects what fields pico warns not initialized in constructors */ + // @Override + // protected boolean hasFieldInvariantAnnotation( + // AnnotatedTypeMirror type, VariableElement fieldElement) { + // // This affects which fields should be guaranteed to be initialized: + // // Fields of any immutability should be initialized. + // return !PICOTypeUtil.isAssignableField(fieldElement, this); + // } + + /** Forbid applying top annotations to type variables if they are used on local variables */ + @Override + public boolean getShouldDefaultTypeVarLocals() { + return false; + } + + /** + * This covers the case when static fields are used and constructor is accessed as an + * element(regarding applying @Immutable on type declaration to constructor return type). + */ + @Override + public void addComputedTypeAnnotations(Element elt, AnnotatedTypeMirror type) { + PICOTypeUtil.addDefaultForField(this, type, elt); + PICOTypeUtil.defaultConstructorReturnToClassBound(this, elt, type); + // PICOTypeUtil.applyImmutableToEnumAndEnumConstant(type); + super.addComputedTypeAnnotations(elt, type); + } + + /** This method gets lhs WITH flow sensitive refinement */ + // TODO Should refactor super class to avoid too much duplicate code. + // This method is pretty hacky right now. + // @Override + // public AnnotatedTypeMirror getAnnotatedTypeLhs(Tree lhsTree) { + // boolean oldComputingAnnotatedTypeMirrorOfLHS = computingAnnotatedTypeMirrorOfLHS; + // computingAnnotatedTypeMirrorOfLHS = true; + // + // AnnotatedTypeMirror result; + // boolean oldShouldCache = shouldCache; + // // Don't cache the result because getAnnotatedType(lhsTree) could + // // be called from elsewhere and would expect flow-sensitive type refinements. + // shouldCache = false; + // switch (lhsTree.getKind()) { + // case VARIABLE: + // case IDENTIFIER: + // case MEMBER_SELECT: + // case ARRAY_ACCESS: + // result = getAnnotatedType(lhsTree); + // break; + // default: + // if (TreeUtils.isTypeTree(lhsTree)) { + // // lhsTree is a type tree at the pseudo assignment of a returned + // expression to declared return type. + // result = getAnnotatedType(lhsTree); + // } else { + // throw new BugInCF( + // "GenericAnnotatedTypeFactory: Unexpected tree passed to + // getAnnotatedTypeLhs. " + // + "lhsTree: " + // + lhsTree + // + " Tree.Kind: " + // + lhsTree.getKind()); + // } + // } + // shouldCache = oldShouldCache; + // + // computingAnnotatedTypeMirrorOfLHS = oldComputingAnnotatedTypeMirrorOfLHS; + // return result; + // } + + // /**Handles invoking static methods with polymutable on its declaration*/ + // @Override + // public ParameterizedExecutableType methodFromUse(ExpressionTree tree, ExecutableElement + // methodElt, AnnotatedTypeMirror receiverType) { + // ParameterizedExecutableType pair = super.methodFromUse(tree, methodElt, receiverType); + // // We want to replace polymutable with substitutablepolymutable when we invoke static + // methods + // if (ElementUtils.isStatic(methodElt)) { + // AnnotatedExecutableType methodType = pair.executableType; + // AnnotatedTypeMirror returnType = methodType.getReturnType(); + // if (returnType.hasAnnotation(POLY_MUTABLE)) { + // // Only substitute polymutable but not other qualifiers! Missing the if + // statement + // // caused bugs before! + // returnType.replaceAnnotation(SUBSTITUTABLE_POLY_MUTABLE); + // } + // List parameterTypes = methodType.getParameterTypes(); + // for (AnnotatedTypeMirror p : parameterTypes) { + // if (returnType.hasAnnotation(POLY_MUTABLE)) { + // p.replaceAnnotation(SUBSTITUTABLE_POLY_MUTABLE); + // } + // } + // } + // return pair; + // } + + // protected class PICOQualifierHierarchy extends MostlyNoElementQualifierHierarchy { + // protected PICOQualifierHierarchy(Collection> + // qualifierClasses, Elements elements, GenericAnnotatedTypeFactory atypeFactory) { + // super(qualifierClasses, elements, atypeFactory); + // } + // + // // public PICOQualifierHierarchy(MultiGraphFactory f, Object[] arg) { + // // super(f, arg); + // // } + // + // // @Override + // // public boolean isSubtype(AnnotationMirror subAnno, AnnotationMirror + // superAnno) { + // // if (isInitializationAnnotation(subAnno) || + // // isInitializationAnnotation(superAnno)) { + // // return this.isSubtypeInitialization(subAnno, superAnno); + // // } + // // return super.isSubtype(subAnno, superAnno); + // // } + // + // @Override + // protected boolean isSubtypeWithElements( + // AnnotationMirror subAnno, + // QualifierKind subKind, + // AnnotationMirror superAnno, + // QualifierKind superKind) { + // return super.isSubtypeQualifiersOnly(subAnno, superAnno); + // } + // + // // @Override + // // public AnnotationMirror leastUpperBound(AnnotationMirror a1, + // AnnotationMirror a2) + // // { + // // if (isInitializationAnnotation(a1) || isInitializationAnnotation(a2)) { + // // return this.leastUpperBoundInitialization(a1, a2); + // // } + // // return super.leastUpperBound(a1, a2); + // // } + // + // @Override + // protected AnnotationMirror leastUpperBoundWithElements( + // AnnotationMirror a1, + // QualifierKind q1, + // AnnotationMirror a2, + // QualifierKind q2, + // QualifierKind lub) { + // return super.leastUpperBoundQualifiersOnly(a1, a2); + // } + // + // @Override + // protected AnnotationMirror greatestLowerBoundWithElements( + // AnnotationMirror annotationMirror, + // QualifierKind qualifierKind, + // AnnotationMirror annotationMirror1, + // QualifierKind qualifierKind1, + // QualifierKind qualifierKind2) { + // return super.greatestLowerBoundQualifiersOnly(annotationMirror, + // annotationMirror1); + // } + // } + + /** Tree Annotators */ + public static class PICOPropagationTreeAnnotator extends PropagationTreeAnnotator { + public PICOPropagationTreeAnnotator(AnnotatedTypeFactory atypeFactory) { + super(atypeFactory); + } + + // + // // TODO This is very ugly. Why is array component type from lhs propagates to + // rhs?! + @Override + public Void visitNewArray(NewArrayTree tree, AnnotatedTypeMirror type) { + AnnotatedTypeMirror componentType = + ((AnnotatedTypeMirror.AnnotatedArrayType) type).getComponentType(); + boolean noExplicitATM = false; + if (!componentType.hasAnnotation(RECEIVER_DEPENDANT_MUTABLE)) { + noExplicitATM = true; + } + super.visitNewArray(tree, type); + // if new explicit anno before, but RDM after, the RDM must come from the type + // declaration bound + // however, for type has declaration bound as RDM, its default use is mutable. + if (noExplicitATM && componentType.hasAnnotation(RECEIVER_DEPENDANT_MUTABLE)) { + componentType.replaceAnnotation(MUTABLE); + } + return null; + } + + // + // /**Add immutable to the result type of a binary operation if the result type is + // implicitly immutable*/ + // @Override + // public Void visitBinary(BinaryTree node, AnnotatedTypeMirror type) { + // applyImmutableIfImplicitlyImmutable(type);// Usually there isn't existing + // annotation on binary trees, but to be safe, run it first + // super.visitBinary(node, type); + // // NullnessPropagationTreeAnnotator says result type of binary tree is always + // @Initialized. So replace it + // // with COMMITED here. + // applyCommitedIfSupported(atypeFactory, type); + // return null; + // } + + // @Override + // public Void visitUnary(UnaryTree node, AnnotatedTypeMirror type) { + // super.visitUnary(node, type); + // // Same reason as above + // applyCommitedIfSupported(atypeFactory, type); + // return null; + // } + // + @Override + public Void visitTypeCast(TypeCastTree node, AnnotatedTypeMirror type) { + boolean hasExplicitAnnos = false; + if (!type.getAnnotations().isEmpty()) { + hasExplicitAnnos = true; + } + super.visitTypeCast(node, type); + if (!hasExplicitAnnos && type.hasAnnotation(RECEIVER_DEPENDANT_MUTABLE)) { + type.replaceAnnotation(MUTABLE); + } + return null; + } + + @Override + public Void visitBinary(BinaryTree node, AnnotatedTypeMirror type) { + return null; + } + + // private void applyCommitedIfSupported(AnnotatedTypeFactory annotatedTypeFactory, + // AnnotatedTypeMirror type) { + // if (annotatedTypeFactory.isSupportedQualifier(COMMITED)) { + // type.replaceAnnotation(COMMITED); + // } + // } + } + + @Override + public ExtendedViewpointAdapter getViewpointAdapter() { + return (ExtendedViewpointAdapter) viewpointAdapter; + } + + @Override + protected AnnotationMirrorSet getDefaultTypeDeclarationBounds() { + AnnotationMirrorSet frameworkDefault = + new AnnotationMirrorSet(super.getDefaultTypeDeclarationBounds()); + return replaceAnnotationInHierarchy(frameworkDefault, MUTABLE); + } + + @Override + public AnnotationMirrorSet getTypeDeclarationBounds(TypeMirror type) { + AnnotationMirror mut = getTypeDeclarationBoundForMutability(type); + AnnotationMirrorSet frameworkDefault = super.getTypeDeclarationBounds(type); + if (mut != null) { + frameworkDefault = replaceAnnotationInHierarchy(frameworkDefault, mut); + } + return frameworkDefault; + } + + private AnnotationMirrorSet replaceAnnotationInHierarchy( + AnnotationMirrorSet set, AnnotationMirror mirror) { + AnnotationMirrorSet result = new AnnotationMirrorSet(set); + AnnotationMirror removeThis = + getQualifierHierarchy().findAnnotationInSameHierarchy(set, mirror); + result.remove(removeThis); + result.add(mirror); + return result; + } + + public AnnotationMirror getTypeDeclarationBoundForMutability(TypeMirror type) { + // copied from inference real type factory with minor modification + // TODO too awkward. maybe overload isImplicitlyImmutableType + if (PICOTypeUtil.isImplicitlyImmutableType(toAnnotatedType(type, false))) { + return IMMUTABLE; + } + if (type.getKind() == TypeKind.ARRAY) { + return RECEIVER_DEPENDANT_MUTABLE; // if decided to use vpa for array, return RDM. + } + + // IMMUTABLE for enum w/o decl anno + if (type instanceof DeclaredType) { + Element ele = ((DeclaredType) type).asElement(); + if (ele.getKind() == ElementKind.ENUM) { + if (!AnnotationUtils.containsSameByName(getDeclAnnotations(ele), MUTABLE) + && !AnnotationUtils.containsSameByName( + getDeclAnnotations(ele), + RECEIVER_DEPENDANT_MUTABLE)) { // no decl anno + return IMMUTABLE; + } + } + } + return null; + } + + @Override + public AnnotatedTypeMirror getTypeOfExtendsImplements(Tree clause) { + // this is still needed with PICOSuperClauseAnnotator. + // maybe just use getAnnotatedType + // add default anno from class main qual, if no qual present + AnnotatedTypeMirror fromTypeTree = super.getTypeOfExtendsImplements(clause); + if (fromTypeTree.hasAnnotation(RECEIVER_DEPENDANT_MUTABLE)) { + AnnotatedTypeMirror enclosing = + getAnnotatedType(TreePathUtil.enclosingClass(getPath(clause))); + AnnotationMirror mainBound = enclosing.getAnnotationInHierarchy(READONLY); + fromTypeTree.replaceAnnotation(mainBound); + } + return fromTypeTree; + } + + /** Apply defaults for static fields with non-implicitly immutable types */ + public static class PICOTreeAnnotator extends TreeAnnotator { + public PICOTreeAnnotator(AnnotatedTypeFactory atypeFactory) { + super(atypeFactory); + } + + // This adds @Immutable annotation to constructor return type if type declaration has + // @Immutable when the + // constructor is accessed as a tree. + @Override + public Void visitMethod(MethodTree node, AnnotatedTypeMirror p) { + Element element = TreeUtils.elementFromDeclaration(node); + // See: + // https://github.com/opprop/checker-framework/blob/master/framework/src/org/checkerframework/framework/type/AnnotatedTypeFactory.java#L1593 + // for why constructor return is not applied class bound annotation + PICOTypeUtil.defaultConstructorReturnToClassBound(atypeFactory, element, p); + return super.visitMethod(node, p); + } + + /** This covers the declaration of static fields */ + @Override + public Void visitVariable(VariableTree node, AnnotatedTypeMirror annotatedTypeMirror) { + VariableElement element = TreeUtils.elementFromDeclaration(node); + PICOTypeUtil.addDefaultForField(atypeFactory, annotatedTypeMirror, element); + // PICOTypeUtil.applyImmutableToEnumAndEnumConstant(annotatedTypeMirror); + return super.visitVariable(node, annotatedTypeMirror); + } + + @Override + public Void visitBinary(BinaryTree node, AnnotatedTypeMirror type) { + type.replaceAnnotation(IMMUTABLE); + return null; + } + } + + /** Type Annotators */ + public static class PICOTypeAnnotator extends TypeAnnotator { + + public PICOTypeAnnotator(AnnotatedTypeFactory typeFactory) { + super(typeFactory); + } + + /** + * Applies pre-knowledged defaults that are same with jdk.astub to toString, hashCode, + * equals, clone Object methods + */ + @Override + public Void visitExecutable(AnnotatedExecutableType t, Void p) { + super.visitExecutable(t, p); + + // Only handle instance methods, not static methods + if (!ElementUtils.isStatic(t.getElement())) { + if (PICOTypeUtil.isMethodOrOverridingMethod(t, "toString()", atypeFactory) + || PICOTypeUtil.isMethodOrOverridingMethod(t, "hashCode()", atypeFactory)) { + assert t.getReceiverType() != null; + t.getReceiverType().replaceAnnotation(READONLY); + } else if (PICOTypeUtil.isMethodOrOverridingMethod( + t, "equals(java.lang.Object)", atypeFactory)) { + assert t.getReceiverType() != null; + t.getReceiverType().replaceAnnotation(READONLY); + t.getParameterTypes().get(0).replaceAnnotation(READONLY); + } + } + + // Array decl methods + // Array methods are implemented as JVM native method, so we cannot add that to stubs. + // for now: default array in receiver, parameter and return type to RDM + if (t.getReceiverType() != null) { + if (PICOTypeUtil.isArrayType(t.getReceiverType(), atypeFactory)) { + switch (t.toString()) { + case "Object clone(Array this)": + // Receiver type will not be viewpoint adapted: + // SyntheticArrays.replaceReturnType() will rollback the viewpoint adapt + // result. + // Use readonly to allow all invocations. + if (!t.getReceiverType().hasAnnotationInHierarchy(READONLY)) + t.getReceiverType().replaceAnnotation(READONLY); + // The return type will be fixed by SyntheticArrays anyway. + // Qualifiers added here will not have effect. + break; + } + } + } + + return null; + } + } + + @Override + protected DefaultQualifierForUseTypeAnnotator createDefaultForUseTypeAnnotator() { + return new PICOQualifierForUseTypeAnnotator(this); + } + + // @DefaultQFU + public static class PICOQualifierForUseTypeAnnotator + extends DefaultQualifierForUseTypeAnnotator { + + public PICOQualifierForUseTypeAnnotator(AnnotatedTypeFactory typeFactory) { + super(typeFactory); + } + + @Override + public Void visitDeclared(AnnotatedTypeMirror.AnnotatedDeclaredType type, Void aVoid) { + + Element element = type.getUnderlyingType().asElement(); + Set annosToApply = getDefaultAnnosForUses(element); + + if (annosToApply.contains(MUTABLE) || annosToApply.contains(IMMUTABLE)) { + type.addMissingAnnotations(annosToApply); + } + + // Below copied from super.super + // TODO add a function to super.super visitor + if (!type.getTypeArguments().isEmpty()) { + // Only declared types with type arguments might be recursive. + if (visitedNodes.containsKey(type)) { + return visitedNodes.get(type); + } + visitedNodes.put(type, null); + } + Void r = null; + if (type.getEnclosingType() != null) { + scan(type.getEnclosingType(), null); + } + r = scanAndReduce(type.getTypeArguments(), null, r); + return r; + } + } + + public static class PICODefaultForTypeAnnotator extends DefaultForTypeAnnotator { + + public PICODefaultForTypeAnnotator(AnnotatedTypeFactory typeFactory) { + super(typeFactory); + } + + /** Also applies implicits to method receiver */ + @Override + public Void visitExecutable(AnnotatedExecutableType t, Void p) { + // TODO The implementation before doesn't work after update. Previously, I sanned the + // method receiver without null check. But even if I check nullness, scanning receiver + // at first caused some tests to fail. Need to investigate the reason. + super.visitExecutable(t, p); + // Also scan the receiver to apply implicit annotation + if (t.getReceiverType() != null) { + return scanAndReduce(t.getReceiverType(), p, null); + } + return null; + } + + @Override + protected Void scan(AnnotatedTypeMirror type, Void p) { + // If underlying type is enum or enum constant, appy @Immutable to type + // PICOTypeUtil.applyImmutableToEnumAndEnumConstant(type); + return super.scan(type, p); + } + } + + // TODO Right now, instance method receiver cannot inherit bound annotation from class element, + // and + // this caused the inconsistency when accessing the type of receiver while visiting the method + // and + // while visiting the variable tree. Implicit annotation can be inserted to method receiver via + // extending DefaultForTypeAnnotator; But InheritedFromClassAnnotator cannot be inheritted + // because its + // constructor is private and I can't override it to also inherit bound annotation from class + // element + // to the declared receiver type of instance methods. To view the details, look at + // ImmutableClass1.java + // testcase. + // class PICOInheritedFromClassAnnotator extends InheritedFromClassAnnotator {} + + public static class PICOSuperClauseAnnotator extends TreeAnnotator { + + public PICOSuperClauseAnnotator(AnnotatedTypeFactory atypeFactory) { + super(atypeFactory); + } + + public static boolean isSuperClause(TreePath path) { + if (path == null) { + return false; + } + return TreeUtils.isClassTree(path.getParentPath().getLeaf()); + } + + private void addDefaultFromMain(Tree tree, AnnotatedTypeMirror mirror) { + TreePath path = atypeFactory.getPath(tree); + + // only annotates when: + // 1. it's a super clause, AND + // 2. atm OR tree is not annotated + // Note: TreeUtils.typeOf returns no stub or default annotations, but in this scenario + // they are not needed + // Here only explicit annotation on super clause have effect because framework default + // rule is overriden + if (isSuperClause(path) + && (!mirror.hasAnnotationInHierarchy(READONLY) + || atypeFactory + .getQualifierHierarchy() + .findAnnotationInHierarchy( + TreeUtils.typeOf(tree).getAnnotationMirrors(), + READONLY) + == null)) { + AnnotatedTypeMirror enclosing = + atypeFactory.getAnnotatedType(TreePathUtil.enclosingClass(path)); + AnnotationMirror mainBound = enclosing.getAnnotationInHierarchy(READONLY); + mirror.replaceAnnotation(mainBound); + // System.err.println("ANNOT: ADDED DEFAULT FOR: " + mirror); + } + } + + @Override + public Void visitIdentifier( + IdentifierTree identifierTree, AnnotatedTypeMirror annotatedTypeMirror) { + // super clauses without type param use this + addDefaultFromMain(identifierTree, annotatedTypeMirror); + return super.visitIdentifier(identifierTree, annotatedTypeMirror); + } + + @Override + public Void visitParameterizedType( + ParameterizedTypeTree parameterizedTypeTree, + AnnotatedTypeMirror annotatedTypeMirror) { + // super clauses with type param use this + addDefaultFromMain(parameterizedTypeTree, annotatedTypeMirror); + return super.visitParameterizedType(parameterizedTypeTree, annotatedTypeMirror); + } + } + + public static class PICOEnumDefaultAnnotator extends TypeAnnotator { + // Defaulting only applies the same annotation to all class declarations + // We need this to "only default enums" to immutable + + public PICOEnumDefaultAnnotator(AnnotatedTypeFactory typeFactory) { + super(typeFactory); + } + + @Override + public Void visitDeclared(AnnotatedTypeMirror.AnnotatedDeclaredType type, Void aVoid) { + if (PICOTypeUtil.isEnumOrEnumConstant(type)) { + type.addMissingAnnotations(Collections.singleton(IMMUTABLE)); + } + return super.visitDeclared(type, aVoid); + } + } +} diff --git a/checker/src/main/java/org/checkerframework/checker/immutability/PICONoInitStore.java b/checker/src/main/java/org/checkerframework/checker/immutability/PICONoInitStore.java new file mode 100644 index 00000000000..e0253d7f5bd --- /dev/null +++ b/checker/src/main/java/org/checkerframework/checker/immutability/PICONoInitStore.java @@ -0,0 +1,26 @@ +package org.checkerframework.checker.immutability; + +import org.checkerframework.dataflow.expression.FieldAccess; +import org.checkerframework.framework.flow.CFAbstractAnalysis; +import org.checkerframework.framework.flow.CFAbstractStore; + +import java.util.Map; + +/** Created by mier on 15/08/17. */ +public class PICONoInitStore extends CFAbstractStore { + + protected Map initializedFields; + + public PICONoInitStore( + CFAbstractAnalysis analysis, + boolean sequentialSemantics) { + super(analysis, sequentialSemantics); + } + + public PICONoInitStore(PICONoInitStore s) { + super(s); + if (s.initializedFields != null) { + initializedFields = s.initializedFields; + } + } +} diff --git a/checker/src/main/java/org/checkerframework/checker/immutability/PICONoInitSubchecker.java b/checker/src/main/java/org/checkerframework/checker/immutability/PICONoInitSubchecker.java new file mode 100644 index 00000000000..9bca6c8236b --- /dev/null +++ b/checker/src/main/java/org/checkerframework/checker/immutability/PICONoInitSubchecker.java @@ -0,0 +1,28 @@ +package org.checkerframework.checker.immutability; + +import org.checkerframework.checker.initialization.InitializationFieldAccessSubchecker; +import org.checkerframework.common.basetype.BaseTypeChecker; +import org.checkerframework.common.basetype.BaseTypeVisitor; + +import java.util.Set; + +public class PICONoInitSubchecker extends BaseTypeChecker { + public PICONoInitSubchecker() {} + + @Override + public PICONoInitAnnotatedTypeFactory getTypeFactory() { + return (PICONoInitAnnotatedTypeFactory) super.getTypeFactory(); + } + + @Override + protected Set> getImmediateSubcheckerClasses() { + Set> checkers = super.getImmediateSubcheckerClasses(); + checkers.add(InitializationFieldAccessSubchecker.class); + return checkers; + } + + @Override + protected BaseTypeVisitor createSourceVisitor() { + return new PICONoInitVisitor(this); + } +} diff --git a/checker/src/main/java/org/checkerframework/checker/immutability/PICONoInitTransfer.java b/checker/src/main/java/org/checkerframework/checker/immutability/PICONoInitTransfer.java new file mode 100644 index 00000000000..a7bd5e3b71d --- /dev/null +++ b/checker/src/main/java/org/checkerframework/checker/immutability/PICONoInitTransfer.java @@ -0,0 +1,46 @@ +package org.checkerframework.checker.immutability; + +import com.sun.source.tree.VariableTree; + +import org.checkerframework.dataflow.analysis.RegularTransferResult; +import org.checkerframework.dataflow.analysis.TransferInput; +import org.checkerframework.dataflow.analysis.TransferResult; +import org.checkerframework.dataflow.cfg.node.AssignmentNode; +import org.checkerframework.dataflow.cfg.node.NullLiteralNode; +import org.checkerframework.framework.flow.CFAbstractTransfer; +import org.checkerframework.javacutil.TreeUtils; + +import javax.lang.model.element.VariableElement; + +/** Created by mier on 15/08/17. */ +public class PICONoInitTransfer + extends CFAbstractTransfer { + + public PICONoInitTransfer(PICONoInitAnalysis analysis) { + super(analysis); + } + + @Override + public TransferResult visitAssignment( + AssignmentNode n, TransferInput in) { + if (n.getExpression() instanceof NullLiteralNode + && n.getTarget().getTree() instanceof VariableTree) { + VariableElement varElement = + TreeUtils.elementFromDeclaration((VariableTree) n.getTarget().getTree()); + // Below is for removing false positive warning of bottom illegal write cacused by + // refining field to @Bottom if + // field initializer is null. + // Forbid refinement from null literal in initializer to fields variable tree(identifier + // tree not affected, e.g. + // assigning a field as null in instance methods or constructors) + if (varElement != null && varElement.getKind().isField()) { + PICONoInitStore store = in.getRegularStore(); + PICONoInitValue storeValue = in.getValueOfSubNode(n); + PICONoInitValue value = moreSpecificValue(null, storeValue); + return new RegularTransferResult<>(finishValue(value, store), store); + } + } + TransferResult result = super.visitAssignment(n, in); + return result; + } +} diff --git a/checker/src/main/java/org/checkerframework/checker/immutability/PICONoInitValue.java b/checker/src/main/java/org/checkerframework/checker/immutability/PICONoInitValue.java new file mode 100644 index 00000000000..cfe55bf207a --- /dev/null +++ b/checker/src/main/java/org/checkerframework/checker/immutability/PICONoInitValue.java @@ -0,0 +1,17 @@ +package org.checkerframework.checker.immutability; + +import org.checkerframework.framework.flow.CFAbstractAnalysis; +import org.checkerframework.framework.flow.CFAbstractValue; +import org.checkerframework.javacutil.AnnotationMirrorSet; + +import javax.lang.model.type.TypeMirror; + +/** Created by mier on 15/08/17. */ +public class PICONoInitValue extends CFAbstractValue { + public PICONoInitValue( + CFAbstractAnalysis analysis, + AnnotationMirrorSet annotations, + TypeMirror underlyingType) { + super(analysis, annotations, underlyingType); + } +} diff --git a/checker/src/main/java/org/checkerframework/checker/immutability/PICONoInitVisitor.java b/checker/src/main/java/org/checkerframework/checker/immutability/PICONoInitVisitor.java new file mode 100644 index 00000000000..8e269fa90a8 --- /dev/null +++ b/checker/src/main/java/org/checkerframework/checker/immutability/PICONoInitVisitor.java @@ -0,0 +1,687 @@ +package org.checkerframework.checker.immutability; + +import static org.checkerframework.checker.immutability.PICOAnnotationMirrorHolder.BOTTOM; +import static org.checkerframework.checker.immutability.PICOAnnotationMirrorHolder.IMMUTABLE; +import static org.checkerframework.checker.immutability.PICOAnnotationMirrorHolder.MUTABLE; +import static org.checkerframework.checker.immutability.PICOAnnotationMirrorHolder.POLY_MUTABLE; +import static org.checkerframework.checker.immutability.PICOAnnotationMirrorHolder.READONLY; +import static org.checkerframework.checker.immutability.PICOAnnotationMirrorHolder.RECEIVER_DEPENDANT_MUTABLE; +import static org.checkerframework.javacutil.TreePathUtil.isTopLevelAssignmentInInitializerBlock; + +import com.sun.source.tree.ArrayAccessTree; +import com.sun.source.tree.AssignmentTree; +import com.sun.source.tree.ClassTree; +import com.sun.source.tree.CompoundAssignmentTree; +import com.sun.source.tree.ExpressionTree; +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.UnaryTree; +import com.sun.source.tree.VariableTree; + +import org.checkerframework.checker.compilermsgs.qual.CompilerMessageKey; +import org.checkerframework.common.basetype.BaseTypeChecker; +import org.checkerframework.common.basetype.BaseTypeVisitor; +import org.checkerframework.common.basetype.TypeValidator; +import org.checkerframework.framework.type.AnnotatedTypeFactory.ParameterizedExecutableType; +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.QualifierHierarchy; +import org.checkerframework.framework.util.AnnotatedTypes; +import org.checkerframework.javacutil.*; + +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.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; + +/** Created by mier on 20/06/17. Enforce PICO type rules. */ +public class PICONoInitVisitor extends BaseTypeVisitor { + + private final boolean shouldOutputFbcError; + final Map fbcViolatedMethods; + + public PICONoInitVisitor(BaseTypeChecker checker) { + super(checker); + shouldOutputFbcError = checker.hasOption("printFbcErrors"); + fbcViolatedMethods = shouldOutputFbcError ? new HashMap<>() : null; + } + + @Override + protected TypeValidator createTypeValidator() { + return new PICOValidator(checker, this, atypeFactory); + } + + @Override + protected void checkConstructorResult( + AnnotatedExecutableType constructorType, ExecutableElement constructorElement) {} + + // This method is for validating usage of mutability qualifier is conformable to element + // declaration, + // Ugly thing here is that declarationType is not the result of calling the other method - + // PICOTypeUtil#getBoundTypeOfTypeDeclaration. Instead it's the result of calling + // ATF#getAnnotatedType(Element). + // Why it works is that PICOTypeUtil#getBoundTypeOfTypeDeclaration and + // ATF#getAnnotatedType(Element) has + // the same effect most of the time except on java.lang.Object. We need to be careful when + // modifying + // PICOTypeUtil#getBoundTypeOfTypeDeclaration so that it has the same behaviour as + // ATF#getAnnotatedType(Element) + // (at least for types other than java.lang.Object) + @Override + public boolean isValidUse( + AnnotatedDeclaredType declarationType, AnnotatedDeclaredType useType, Tree tree) { + + // FIXME workaround for poly anno, remove after fix substitutable poly and add poly vp rules + if (useType.hasAnnotation(POLY_MUTABLE)) { + return true; + } + + // // allow RDM on mutable fields with enclosing class bounded with mutable + // if (tree instanceof VariableTree && useType.isDeclaration()) { + // VariableElement element = + // TreeUtils.elementFromDeclaration((VariableTree)tree); + // if (element.getKind() == ElementKind.FIELD && + // ElementUtils.enclosingClass(element) != null) { + // Set enclosingBound = + // atypeFactory.getTypeDeclarationBounds( + // + // Objects.requireNonNull(ElementUtils.enclosingClass(element)).asType()); + // + // if(declarationType.hasAnnotation(MUTABLE) + // && useType.hasAnnotation(RECEIVER_DEPENDANT_MUTABLE) + // && AnnotationUtils.containsSameByName(enclosingBound, MUTABLE)) { + // return true; + // } + // } + // + // } + + AnnotationMirror declared = declarationType.getAnnotationInHierarchy(READONLY); + AnnotationMirror used = useType.getAnnotationInHierarchy(READONLY); + + return isAdaptedSubtype(used, declared); + } + + @Override + public boolean isValidUse(AnnotatedTypeMirror.AnnotatedArrayType type, Tree tree) { + // You don't need adapted subtype if the decl bound is guaranteed to be RDM. + // That simply means that any use is valid except bottom. + AnnotationMirror used = type.getAnnotationInHierarchy(READONLY); + return !AnnotationUtils.areSame(used, BOTTOM); + } + + private boolean isAdaptedSubtype(AnnotationMirror lhs, AnnotationMirror rhs) { + ExtendedViewpointAdapter vpa = + ((ViewpointAdapterGettable) atypeFactory).getViewpointAdapter(); + AnnotationMirror adapted = vpa.rawCombineAnnotationWithAnnotation(lhs, rhs); + return atypeFactory.getQualifierHierarchy().isSubtypeQualifiersOnly(adapted, lhs); + } + + @Override + protected boolean commonAssignmentCheck( + Tree varTree, ExpressionTree valueExp, String errorKey, Object... extraArgs) { + AnnotatedTypeMirror var = atypeFactory.getAnnotatedTypeLhs(varTree); + assert var != null : "no variable found for tree: " + varTree; + + if (!validateType(varTree, var)) { + return false; + } + + if (varTree instanceof VariableTree) { + VariableElement element = TreeUtils.elementFromDeclaration((VariableTree) varTree); + if (element.getKind() == ElementKind.FIELD && !ElementUtils.isStatic(element)) { + AnnotatedDeclaredType bound = + PICOTypeUtil.getBoundTypeOfEnclosingTypeDeclaration(varTree, atypeFactory); + // var is singleton, so shouldn't modify var directly. Otherwise, the variable + // tree's type will be + // altered permanently, and other clients who access this type will see the change, + // too. + AnnotatedTypeMirror varAdapted = var.shallowCopy(true); + // Viewpoint adapt varAdapted to the bound. + // PICOInferenceAnnotatedTypeFactory#viewpointAdaptMember() + // mutates varAdapted, so after the below method is called, varAdapted is the result + // adapted to bound + atypeFactory.getViewpointAdapter().viewpointAdaptMember(bound, element, varAdapted); + // Pass varAdapted here as lhs type. + // Caution: cannot pass var directly. Modifying type in PICOInferenceTreeAnnotator# + // visitVariable() will cause wrong type to be gotton here, as on inference side, + // atm is uniquely determined by each element. + return commonAssignmentCheck(varAdapted, valueExp, errorKey, extraArgs); + } + } + + return commonAssignmentCheck(var, valueExp, errorKey, extraArgs); + } + + @Override + protected void checkConstructorInvocation( + AnnotatedDeclaredType invocation, + AnnotatedExecutableType constructor, + NewClassTree newClassTree) { + // TODO Is the copied code really needed? + /*Copied Code Start*/ + AnnotatedDeclaredType returnType = (AnnotatedDeclaredType) constructor.getReturnType(); + // When an interface is used as the identifier in an anonymous class (e.g. new Comparable() + // {}) + // the constructor method will be Object.init() {} which has an Object return type + // When TypeHierarchy attempts to convert it to the supertype (e.g. Comparable) it will + // return + // null from asSuper and return false for the check. Instead, copy the primary annotations + // to the declared type and then do a subtyping check + if (invocation.getUnderlyingType().asElement().getKind().isInterface() + && TypesUtils.isObject(returnType.getUnderlyingType())) { + final AnnotatedDeclaredType retAsDt = invocation.deepCopy(); + retAsDt.replaceAnnotations(returnType.getAnnotations()); + returnType = retAsDt; + } else if (newClassTree.getClassBody() != null) { + // An anonymous class invokes the constructor of its super class, so the underlying + // types of invocation and returnType are not the same. Call asSuper so they are the + // same and the is subtype tests below work correctly + invocation = AnnotatedTypes.asSuper(atypeFactory, invocation, returnType); + } + /*Copied Code End*/ + + // TODO fix inference counterpart, not here + // // CF base check disabled by InitializationVisitor + // // if no explicit anno it must inherited from class decl + // AnnotationMirror declAnno = + // constructor.getReturnType().getAnnotationInHierarchy(READONLY); + // AnnotationMirror useAnno = invocation.getAnnotationInHierarchy(READONLY); + // declAnno = declAnno == null ? MUTABLE : declAnno; + // + // if(useAnno != null && !AnnotationUtils.areSameByName(declAnno, POLY_MUTABLE) && + // !isAdaptedSubtype(useAnno, declAnno)) { + // checker.reportError(newClassTree, "type.invalid.annotations.on.use", declAnno, + // useAnno); + // } + + // The immutability return qualifier of the constructor (returnType) must be supertype of + // the + // constructor invocation immutability qualifier(invocation). + if (!atypeFactory + .getQualifierHierarchy() + .isSubtypeQualifiersOnly( + invocation.getAnnotationInHierarchy(READONLY), + returnType.getAnnotationInHierarchy(READONLY))) { + checker.reportError( + newClassTree, "constructor.invocation.invalid", invocation, returnType); + } + } + + @Override + public Void visitMethod(MethodTree node, Void p) { + AnnotatedExecutableType executableType = atypeFactory.getAnnotatedType(node); + AnnotatedDeclaredType bound = + PICOTypeUtil.getBoundTypeOfEnclosingTypeDeclaration(node, atypeFactory); + + if (TreeUtils.isConstructor(node)) { + AnnotatedDeclaredType constructorReturnType = + (AnnotatedDeclaredType) executableType.getReturnType(); + if (constructorReturnType.hasAnnotation(READONLY) + || constructorReturnType.hasAnnotation(POLY_MUTABLE)) { + checker.reportError(node, "constructor.return.invalid", constructorReturnType); + return super.visitMethod(node, p); + } + // if no explicit anno it must inherit from class decl so identical + // => if not the same must not inherited from class decl + // => no need to check the source of the anno + + } else { + AnnotatedDeclaredType declareReceiverType = executableType.getReceiverType(); + if (declareReceiverType != null) { + if (bound != null + && !bound.hasAnnotation(RECEIVER_DEPENDANT_MUTABLE) + && !atypeFactory + .getQualifierHierarchy() + .isSubtypeQualifiersOnly( + declareReceiverType.getAnnotationInHierarchy(READONLY), + bound.getAnnotationInHierarchy(READONLY)) + // Below three are allowed on declared receiver types of instance methods in + // either @Mutable class or @Immutable class + && !declareReceiverType.hasAnnotation(READONLY) + && !declareReceiverType.hasAnnotation(POLY_MUTABLE)) { + checker.reportError(node, "method.receiver.incompatible", declareReceiverType); + } + } + } + + flexibleOverrideChecker(node); + + // ObjectIdentityMethod check + if (PICOTypeUtil.isObjectIdentityMethod(node, atypeFactory)) { + ObjectIdentityMethodEnforcer.check( + atypeFactory.getPath(node.getBody()), atypeFactory, checker); + } + return super.visitMethod(node, p); + } + + private void flexibleOverrideChecker(MethodTree node) { + // Method overriding checks + // TODO Copied from super, hence has lots of duplicate code with super. We need to + // change the signature of checkOverride() method to also pass ExecutableElement for + // viewpoint adaptation. + ExecutableElement methodElement = TreeUtils.elementFromDeclaration(node); + AnnotatedDeclaredType enclosingType = + (AnnotatedDeclaredType) + atypeFactory.getAnnotatedType(methodElement.getEnclosingElement()); + + Map overriddenMethods = + AnnotatedTypes.overriddenMethods(elements, atypeFactory, methodElement); + for (Map.Entry pair : + overriddenMethods.entrySet()) { + AnnotatedDeclaredType overriddenType = pair.getKey(); + AnnotatedExecutableType overriddenMethod = + AnnotatedTypes.asMemberOf(types, atypeFactory, enclosingType, pair.getValue()); + // Viewpoint adapt super method executable type to current class bound(is this always + // class bound?) + // to allow flexible overriding + atypeFactory + .getViewpointAdapter() + .viewpointAdaptMethod(enclosingType, pair.getValue(), overriddenMethod); + AnnotatedExecutableType overrider = atypeFactory.getAnnotatedType(node); + if (!checkOverride(node, overrider, enclosingType, overriddenMethod, 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; + } + } + } + + // Disables method overriding checks in BaseTypeVisitor + @Override + protected boolean checkOverride( + MethodTree overriderTree, + AnnotatedDeclaredType overridingType, + AnnotatedExecutableType overridden, + AnnotatedDeclaredType overriddenType) { + return true; + } + + @Override + public Void visitAssignment(AssignmentTree node, Void p) { + ExpressionTree variable = node.getVariable(); + // TODO Question Here, receiver type uses flow refinement. But in commonAssignmentCheck to + // compute lhs type + // , it doesn't. This causes inconsistencies when enforcing immutability and doing subtype + // check. I overrode + // getAnnotatedTypeLhs() to also use flow sensitive refinement, but came across with + // "private access" problem + // on field "computingAnnotatedTypeMirrorOfLHS" + checkMutation(node, variable); + return super.visitAssignment(node, p); + } + + @Override + public Void visitCompoundAssignment(CompoundAssignmentTree node, Void p) { + ExpressionTree variable = node.getVariable(); + checkMutation(node, variable); + return super.visitCompoundAssignment(node, p); + } + + @Override + public Void visitUnary(UnaryTree node, Void p) { + if (PICOTypeUtil.isSideEffectingUnaryTree(node)) { + ExpressionTree variable = node.getExpression(); + checkMutation(node, variable); + } + return super.visitUnary(node, p); + } + + private void checkMutation(Tree node, ExpressionTree variable) { + AnnotatedTypeMirror receiverType = atypeFactory.getReceiverType(variable); + MethodTree enclosingMethod = TreePathUtil.enclosingMethod(getCurrentPath()); + if (enclosingMethod != null && TreeUtils.isConstructor(enclosingMethod)) { + // If the enclosing method is constructor, we don't need to check the receiver type + return; + } + if (isTopLevelAssignmentInInitializerBlock(getCurrentPath())) { + // If the assignment is in initializer block, we don't need to check the receiver type + return; + } + // Cannot use receiverTree = TreeUtils.getReceiverTree(variable) to determine if it's + // field assignment or not. Because for field assignment with implicit "this", receiverTree + // is null but receiverType is non-null. We still need to check this case. + if (receiverType != null && !allowWrite(receiverType, variable)) { + reportFieldOrArrayWriteError(node, variable, receiverType); + } + } + + private boolean allowWrite(AnnotatedTypeMirror receiverType, ExpressionTree variable) { + // One pico side, if only receiver is mutable, we allow assigning/reassigning. Because if + // the field + // is declared as final, Java compiler will catch that, and we couldn't have reached this + // point + if (PICOTypeUtil.isAssigningAssignableField(variable, atypeFactory)) { + return isAllowedAssignableField(receiverType, variable); + } else if (receiverType.hasAnnotation(MUTABLE)) { + // If the receiver is mutable, we allow assigning/reassigning + return true; + // } else if (TreeUtils.elementFromUse(variable)) { + // // If the field is not initialized, we allow assigning/reassigning + // return true; + } + + return false; + } + + private boolean isAllowedAssignableField( + AnnotatedTypeMirror receiverType, ExpressionTree node) { + Element fieldElement = TreeUtils.elementFromUse(node); + Set bounds = + atypeFactory.getTypeDeclarationBounds(TreeUtils.typeOf(node)); + AnnotatedTypeMirror fieldType = atypeFactory.getAnnotatedType(fieldElement); + if (fieldElement == null) return false; + // Forbid the case that might break type soundness. See ForbidAssignmentCase.java:21 + // the second and third predicates ensure that the field is actually rdm (since sometimes we + // replace implicitly mutable with rdm to protect transitive immutability). + return !(receiverType.hasAnnotation(READONLY) + && AnnotationUtils.containsSameByName(bounds, RECEIVER_DEPENDANT_MUTABLE) + && fieldType.hasAnnotation(RECEIVER_DEPENDANT_MUTABLE)); + } + + private void reportFieldOrArrayWriteError( + Tree node, ExpressionTree variable, AnnotatedTypeMirror receiverType) { + if (variable.getKind() == Kind.MEMBER_SELECT) { + checker.reportError( + TreeUtils.getReceiverTree(variable), "illegal.field.write", receiverType); + } else if (variable.getKind() == Kind.IDENTIFIER) { + checker.reportError(node, "illegal.field.write", receiverType); + } else if (variable.getKind() == Kind.ARRAY_ACCESS) { + checker.reportError( + ((ArrayAccessTree) variable).getExpression(), + "illegal.array.write", + receiverType); + } else { + throw new BugInCF("Unknown assignment variable at: ", node); + } + } + + @Override + public Void visitVariable(VariableTree node, Void p) { + VariableElement element = TreeUtils.elementFromDeclaration(node); + AnnotatedTypeMirror type = atypeFactory.getAnnotatedType(element); + if (element.getKind() == ElementKind.FIELD) { + if (type.hasAnnotation(POLY_MUTABLE)) { + checker.reportError(node, "field.polymutable.forbidden", element); + } + } + // When to check: + // bound == Immutable, OR + // not FIELD, OR + // top anno not RDM + // TODO use base cf check methods + AnnotationMirror declAnno = + atypeFactory.getTypeDeclarationBoundForMutability(type.getUnderlyingType()); + if ((declAnno != null && AnnotationUtils.areSameByName(declAnno, IMMUTABLE)) + || element.getKind() != ElementKind.FIELD + || !type.hasAnnotation(RECEIVER_DEPENDANT_MUTABLE)) { + checkAndReportInvalidAnnotationOnUse(type, node); + } + return super.visitVariable(node, p); + } + + private void checkAndReportInvalidAnnotationOnUse(AnnotatedTypeMirror type, Tree node) { + AnnotationMirror useAnno = type.getAnnotationInHierarchy(READONLY); + // FIXME rm after poly vp + if (useAnno != null && AnnotationUtils.areSame(useAnno, POLY_MUTABLE)) { + return; + } + if (useAnno != null + && !PICOTypeUtil.isImplicitlyImmutableType(type) + && type.getKind() + != TypeKind.ARRAY) { // TODO: annotate the use instead of using this + AnnotationMirror defaultAnno = MUTABLE; + for (AnnotationMirror anno : + atypeFactory.getTypeDeclarationBounds(type.getUnderlyingType())) { + if (atypeFactory.getQualifierHierarchy().isSubtypeQualifiersOnly(anno, READONLY) + && !AnnotationUtils.areSame(anno, READONLY)) { + defaultAnno = anno; + } + } + if (!isAdaptedSubtype(useAnno, defaultAnno)) { + checker.reportError(node, "type.invalid.annotations.on.use", defaultAnno, useAnno); + } + } + } + + @Override + public Void visitNewClass(NewClassTree node, Void p) { + checkNewInstanceCreation(node); + return super.visitNewClass(node, p); + } + + @Override + public Void visitNewArray(NewArrayTree node, Void p) { + checkNewInstanceCreation(node); + return super.visitNewArray(node, p); + } + + private void checkNewInstanceCreation(Tree node) { + // Ensure only @Mutable/@Immutable/@ReceiverDependantMutable/@PolyMutable are used on new + // instance creation + AnnotatedTypeMirror type = atypeFactory.getAnnotatedType(node); + if (!(type.hasAnnotation(IMMUTABLE) + || type.hasAnnotation(MUTABLE) + || type.hasAnnotation(RECEIVER_DEPENDANT_MUTABLE) + || type.hasAnnotation(POLY_MUTABLE))) { + checker.reportError(node, "pico.new.invalid", type); + } + } + + @Override + public Void visitMethodInvocation(MethodInvocationTree node, Void p) { + super.visitMethodInvocation(node, p); + ParameterizedExecutableType mfuPair = atypeFactory.methodFromUse(node); + AnnotatedExecutableType invokedMethod = mfuPair.executableType; + ExecutableElement invokedMethodElement = invokedMethod.getElement(); + // Only check invocability if it's super call, as non-super call is already checked + // by super implementation(of course in both cases, invocability is not checked when + // invoking static methods) + if (!ElementUtils.isStatic(invokedMethodElement) + && TreeUtils.isSuperConstructorCall(node)) { + checkMethodInvocability(invokedMethod, node); + } + return null; + } + + // @Override + // protected void checkFieldsInitialized( + // Tree blockNode, + // boolean staticFields, + // PICONoInitStore store, + // List receiverAnnotations) { + // // If a class doesn't have constructor, it cannot be initialized as @Immutable, + // therefore no + // // need to check uninitialized fields + // if (TreeUtils.isClassTree(blockNode)) return; + // if (blockNode.getKind() == Kind.METHOD && TreeUtils.isConstructor((MethodTree) + // blockNode)) { + // // Only raise errors when in @Immutable or @ReceiverDependantMutable constructors. + // As + // // @Mutable constructor can initialized + // // those fields out of constructor + // MethodTree methodTree = (MethodTree) blockNode; + // AnnotatedExecutableType executableType = + // atypeFactory.getAnnotatedType(methodTree); + // AnnotatedDeclaredType constructorReturnType = + // (AnnotatedDeclaredType) executableType.getReturnType(); + // // Only care abstract state initialization in @Immutable and + // @ReceiverDependantMutable + // // constructors, as @Mutable constructors + // // only allows instantiating @Mutable objects and fields can be initialized later + // if (!(constructorReturnType.hasAnnotation(IMMUTABLE) + // || constructorReturnType.hasAnnotation(RECEIVER_DEPENDANT_MUTABLE))) { + // return; + // } + // } + // super.checkFieldsInitialized(blockNode, staticFields, store, receiverAnnotations); + // } + + @Override + protected AnnotationMirrorSet getExceptionParameterLowerBoundAnnotations() { + AnnotationMirrorSet result = new AnnotationMirrorSet(); + result.add(atypeFactory.getQualifierHierarchy().getBottomAnnotation(BOTTOM)); + return result; + } + + @Override + protected AnnotationMirrorSet getThrowUpperBoundAnnotations() { + AnnotationMirrorSet result = new AnnotationMirrorSet(); + result.add(atypeFactory.getQualifierHierarchy().getTopAnnotation(READONLY)); + return result; + } + + @Override + public void processClassTree(ClassTree node) { + TypeElement typeElement = TreeUtils.elementFromDeclaration(node); + // TODO Don't process anonymous class. I'm not even sure if whether + // processClassTree(ClassTree) is + // called on anonymous class tree + if (typeElement.toString().contains("anonymous")) { + super.processClassTree(node); + return; + } + + AnnotatedDeclaredType bound = + PICOTypeUtil.getBoundTypeOfTypeDeclaration(typeElement, atypeFactory); + // Has to be either @Mutable, @ReceiverDependantMutable or @Immutable, nothing else + if (!bound.hasAnnotation(MUTABLE) + && !bound.hasAnnotation(RECEIVER_DEPENDANT_MUTABLE) + && !bound.hasAnnotation(IMMUTABLE)) { + checker.reportError(node, "class.bound.invalid", bound); + return; // Doesn't process the class tree anymore + } + + // Issue warnings on implicit shallow immutable: + // Condition: + // * Class decl == Immutable or RDM * move rdm default error here. see 3.6.3 last part. + // liansun + // * Member is field + // * Member's declared bound == Mutable + // * Member's use anno == null + if (bound.hasAnnotation(IMMUTABLE) || bound.hasAnnotation(RECEIVER_DEPENDANT_MUTABLE)) { + for (Tree member : node.getMembers()) { + if (member.getKind() == Kind.VARIABLE) { + Element ele = TreeUtils.elementFromTree(member); + assert ele != null; + // fromElement will not apply defaults, if no explicit anno exists in code, + // mirror have no anno + AnnotatedTypeMirror noDefaultMirror = atypeFactory.fromElement(ele); + TypeMirror ty = ele.asType(); + if (ty.getKind() == TypeKind.TYPEVAR) { + ty = TypesUtils.upperBound(ty); + } + if (AnnotationUtils.containsSameByName( + atypeFactory.getTypeDeclarationBounds(ty), MUTABLE) + && !noDefaultMirror.hasAnnotationInHierarchy(READONLY)) { + checker.reportError(member, "implicit.shallow.immutable"); + } + } + } + } + + // // field of mutable class cannot use RDM in immutable class + // // Condition: + // // * Class decl == Immutable + // // * Member is field (variable) + // // * Member's declared bound == Mutable + // // * Member's use anno == RDM + // if (bound.hasAnnotation(IMMUTABLE)) { + // for(Tree member : node.getMembers()) { + // if(member.getKind() == Kind.VARIABLE) { + // AnnotatedTypeMirror fieldAtm = atypeFactory.getAnnotatedType(member); + // if (fieldAtm.hasAnnotation(RECEIVER_DEPENDANT_MUTABLE) && + // + // AnnotationUtils.containsSameByName(atypeFactory.getTypeDeclarationBounds(fieldAtm.getUnderlyingType()), MUTABLE)) { + // checker.reportError(member, "test-key-1"); + // } + // } + // } + // } + super.processClassTree(node); + } + + /** + * The invoked constructor’s return type adapted to the invoking constructor’s return type must + * be a supertype of the invoking constructor’s return type. Since InitializationChecker does + * not apply any type rules at here, only READONLY hierarchy is checked. + * + * @param superCall the super invocation, e.g., "super()" + * @param errorKey the error key, e.g., "super.invocation.invalid" + */ + @Override + protected void checkThisOrSuperConstructorCall( + MethodInvocationTree superCall, @CompilerMessageKey String errorKey) { + MethodTree enclosingMethod = methodTree; + AnnotatedTypeMirror superType = atypeFactory.getAnnotatedType(superCall); + AnnotatedExecutableType constructorType = atypeFactory.getAnnotatedType(enclosingMethod); + AnnotationMirror superTypeMirror = superType.getAnnotationInHierarchy(READONLY); + AnnotationMirror constructorTypeMirror = + constructorType.getReturnType().getAnnotationInHierarchy(READONLY); + if (!atypeFactory + .getQualifierHierarchy() + .isSubtypeQualifiersOnly(constructorTypeMirror, superTypeMirror)) { + checker.reportError( + superCall, errorKey, constructorTypeMirror, superCall, superTypeMirror); + } + super.checkThisOrSuperConstructorCall(superCall, errorKey); + } + + @Override + protected boolean isTypeCastSafe(AnnotatedTypeMirror castType, AnnotatedTypeMirror exprType) { + QualifierHierarchy qualifierHierarchy = atypeFactory.getQualifierHierarchy(); + + final TypeKind castTypeKind = castType.getKind(); + if (castTypeKind == TypeKind.DECLARED) { + // Don't issue an error if the mutability annotations are equivalent to the qualifier + // upper bound + // of the type. + // BaseTypeVisitor#isTypeCastSafe is not used, to be consistent with inference which + // only have mutability qualifiers + // if inference is supporting FBC in the future, this overridden method can be removed. + AnnotatedDeclaredType castDeclared = (AnnotatedDeclaredType) castType; + + AnnotationMirror bound = + qualifierHierarchy.findAnnotationInHierarchy( + atypeFactory.getTypeDeclarationBounds(castDeclared.getUnderlyingType()), + READONLY); + assert bound != null; + + if (AnnotationUtils.areSame(castDeclared.getAnnotationInHierarchy(READONLY), bound)) { + return true; + } + } + + return super.isTypeCastSafe(castType, exprType); + } + + @Override + protected boolean commonAssignmentCheck( + AnnotatedTypeMirror varType, + AnnotatedTypeMirror valueType, + Tree valueTree, + String errorKey, + Object... extraArgs) { + // TODO: WORKAROUND: anonymous class handling + if (TypesUtils.isAnonymous(valueType.getUnderlyingType())) { + AnnotatedTypeMirror newValueType = varType.deepCopy(); + newValueType.replaceAnnotation(valueType.getAnnotationInHierarchy(READONLY)); + valueType = newValueType; + } + return super.commonAssignmentCheck(varType, valueType, valueTree, errorKey, extraArgs); + } +} diff --git a/checker/src/main/java/org/checkerframework/checker/immutability/PICOTypeUtil.java b/checker/src/main/java/org/checkerframework/checker/immutability/PICOTypeUtil.java new file mode 100644 index 00000000000..32b9781000a --- /dev/null +++ b/checker/src/main/java/org/checkerframework/checker/immutability/PICOTypeUtil.java @@ -0,0 +1,474 @@ +package org.checkerframework.checker.immutability; + +import static org.checkerframework.checker.immutability.PICOAnnotationMirrorHolder.IMMUTABLE; +import static org.checkerframework.checker.immutability.PICOAnnotationMirrorHolder.MUTABLE; +import static org.checkerframework.checker.immutability.PICOAnnotationMirrorHolder.READONLY; +import static org.checkerframework.checker.immutability.PICOAnnotationMirrorHolder.RECEIVER_DEPENDANT_MUTABLE; + +import com.sun.source.tree.ClassTree; +import com.sun.source.tree.ExpressionTree; +import com.sun.source.tree.MethodTree; +import com.sun.source.tree.NewClassTree; +import com.sun.source.tree.Tree; +import com.sun.source.tree.UnaryTree; +import com.sun.source.tree.VariableTree; +import com.sun.source.util.TreePath; +import com.sun.tools.javac.code.Symbol; + +import org.checkerframework.checker.immutability.qual.Assignable; +import org.checkerframework.checker.immutability.qual.Immutable; +import org.checkerframework.checker.immutability.qual.ObjectIdentityMethod; +import org.checkerframework.framework.qual.DefaultFor; +import org.checkerframework.framework.qual.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.AnnotatedExecutableType; +import org.checkerframework.framework.util.AnnotatedTypes; +import org.checkerframework.javacutil.AnnotationProvider; +import org.checkerframework.javacutil.AnnotationUtils; +import org.checkerframework.javacutil.ElementUtils; +import org.checkerframework.javacutil.TreePathUtil; +import org.checkerframework.javacutil.TreeUtils; +import org.checkerframework.javacutil.TypesUtils; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Objects; +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.DeclaredType; +import javax.lang.model.type.TypeMirror; + +public class PICOTypeUtil { + + private static final Set sideEffectingUnaryOperators; + + static { + sideEffectingUnaryOperators = new HashSet<>(); + sideEffectingUnaryOperators.add(Tree.Kind.POSTFIX_INCREMENT); + sideEffectingUnaryOperators.add(Tree.Kind.PREFIX_INCREMENT); + sideEffectingUnaryOperators.add(Tree.Kind.POSTFIX_DECREMENT); + sideEffectingUnaryOperators.add(Tree.Kind.PREFIX_DECREMENT); + } + + private static boolean isInTypeKindsOfDefaultForOfImmutable(AnnotatedTypeMirror atm) { + DefaultFor defaultFor = Immutable.class.getAnnotation(DefaultFor.class); + assert defaultFor != null; + for (TypeKind typeKind : defaultFor.typeKinds()) { + if (typeKind.name().equals(atm.getKind().name())) return true; + } + return false; + } + + private static boolean isInTypesOfDefaultForOfImmutable(AnnotatedTypeMirror atm) { + if (!atm.getKind().name().equals(TypeKind.DECLARED.name())) { + return false; + } + DefaultFor defaultFor = Immutable.class.getAnnotation(DefaultFor.class); + assert defaultFor != null; + Class[] types = defaultFor.types(); + String fqn = TypesUtils.getQualifiedName((DeclaredType) atm.getUnderlyingType()).toString(); + for (Class type : types) { + if (type.getCanonicalName().contentEquals(fqn)) { + return true; + } + } + return false; + } + + /** + * Method to determine if the underlying type is implicitly immutable. This method is consistent + * with the types and typeNames that are in @ImplicitFor in the definition of @Immutable + * qualifier + */ + public static boolean isImplicitlyImmutableType(AnnotatedTypeMirror atm) { + return isInTypeKindsOfDefaultForOfImmutable(atm) || isInTypesOfDefaultForOfImmutable(atm); + } + + /** + * Returns the bound of type declaration enclosing the node. + * + *

If no annotation exists on type declaration, bound is defaulted to @Mutable instead of + * having empty annotations. + * + *

This method simply gets/defaults annotation on bounds of classes, but doesn't validate the + * correctness of the annotation. They are validated in {@link + * PICOVisitor#processClassTree(ClassTree)} method. + * + * @param node tree whose enclosing type declaration's bound annotation is to be extracted + * @param atypeFactory pico type factory + * @return annotation on the bound of enclosing type declaration + */ + public static AnnotatedDeclaredType getBoundTypeOfEnclosingTypeDeclaration( + Tree node, AnnotatedTypeFactory atypeFactory) { + TypeElement typeElement = null; + if (node instanceof MethodTree) { + MethodTree methodTree = (MethodTree) node; + ExecutableElement element = TreeUtils.elementFromDeclaration(methodTree); + typeElement = ElementUtils.enclosingTypeElement(element); + } else if (node instanceof VariableTree) { + VariableTree variableTree = (VariableTree) node; + VariableElement variableElement = TreeUtils.elementFromDeclaration(variableTree); + assert variableElement != null && variableElement.getKind().isField(); + typeElement = ElementUtils.enclosingTypeElement(variableElement); + } + + if (typeElement != null) { + return getBoundTypeOfTypeDeclaration(typeElement, atypeFactory); + } + + return null; + } + + public static AnnotatedDeclaredType getBoundTypeOfEnclosingTypeDeclaration( + Element element, AnnotatedTypeFactory atypeFactory) { + TypeElement typeElement = ElementUtils.enclosingTypeElement(element); + if (typeElement != null) { + return getBoundTypeOfTypeDeclaration(typeElement, atypeFactory); + } + return null; + } + + public static List getBoundTypesOfDirectSuperTypes( + TypeElement current, AnnotatedTypeFactory atypeFactory) { + List boundsOfSupers = new ArrayList<>(); + TypeMirror supertypecls; + try { + supertypecls = current.getSuperclass(); + } catch (Symbol.CompletionFailure cf) { + // Copied from ElementUtils#getSuperTypes(Elements, TypeElement) + // 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. + // TODO: Use an expanded ErrorReporter to output a message. + supertypecls = null; + } + + if (supertypecls != null && !supertypecls.getKind().name().equals(TypeKind.NONE.name())) { + TypeElement supercls = (TypeElement) ((DeclaredType) supertypecls).asElement(); + boundsOfSupers.add(getBoundTypeOfTypeDeclaration(supercls, atypeFactory)); + } + + for (TypeMirror supertypeitf : current.getInterfaces()) { + TypeElement superitf = (TypeElement) ((DeclaredType) supertypeitf).asElement(); + boundsOfSupers.add(getBoundTypeOfTypeDeclaration(superitf, atypeFactory)); + } + return boundsOfSupers; + } + + public static AnnotatedDeclaredType getBoundTypeOfTypeDeclaration( + ClassTree classTree, AnnotatedTypeFactory atypeFactory) { + TypeElement typeElement = TreeUtils.elementFromDeclaration(classTree); + return getBoundTypeOfTypeDeclaration(typeElement, atypeFactory); + } + + public static AnnotatedDeclaredType getBoundTypeOfTypeDeclaration( + TypeElement typeElement, AnnotatedTypeFactory atypeFactory) { + // Reads bound annotation from source code or stub files + // Implicitly immutable types have @Immutable in its bound + // All other elements that are: not implicitly immutable types specified in definition of + // @Immutable qualifier; + // Or has no bound annotation on its type element declaration either in source tree or stub + // file(jdk.astub) have + // @Mutable in its bound + return atypeFactory.getAnnotatedType(typeElement); + + // It's a bit strange that bound annotations on implicilty immutable types + // are not specified in the stub file. For implicitly immutable types, having bounds in stub + // file suppresses type cast warnings, because in base implementation, it checks cast type + // is whether + // from element itself. If yes, no matter what the casted type is, the warning is + // suppressed, which is + // also not wanted. BUT, they are applied @Immutable as their bounds CORRECTLY, because we + // have TypeAnnotator! + + // TODO This method doesn't have logic of handling anonymous class! We should implement it, + // maybe in different + // places, at some time. + } + + public static AnnotatedDeclaredType getBoundTypeOfTypeDeclaration( + TypeMirror typeMirror, AnnotatedTypeFactory atypeFactory) { + return getBoundTypeOfTypeDeclaration(TypesUtils.getTypeElement(typeMirror), atypeFactory); + } + + public static boolean isObjectIdentityMethod( + MethodTree node, AnnotatedTypeFactory annotatedTypeFactory) { + ExecutableElement element = TreeUtils.elementFromDeclaration(node); + return isObjectIdentityMethod(element, annotatedTypeFactory); + } + + public static boolean isObjectIdentityMethod( + ExecutableElement executableElement, AnnotatedTypeFactory annotatedTypeFactory) { + return hasObjectIdentityMethodDeclAnnotation(executableElement, annotatedTypeFactory) + || isMethodOrOverridingMethod(executableElement, "hashCode()", annotatedTypeFactory) + || isMethodOrOverridingMethod( + executableElement, "equals(java.lang.Object)", annotatedTypeFactory); + } + + private static boolean hasObjectIdentityMethodDeclAnnotation( + ExecutableElement element, AnnotatedTypeFactory annotatedTypeFactory) { + return annotatedTypeFactory.getDeclAnnotation(element, ObjectIdentityMethod.class) != null; + } + + /** Helper method to determine a method using method name */ + public static boolean isMethodOrOverridingMethod( + AnnotatedExecutableType methodType, + String methodName, + AnnotatedTypeFactory annotatedTypeFactory) { + return isMethodOrOverridingMethod( + methodType.getElement(), methodName, annotatedTypeFactory); + } + + public static boolean isMethodOrOverridingMethod( + ExecutableElement executableElement, + String methodName, + AnnotatedTypeFactory annotatedTypeFactory) { + // Check if it is the target method + if (executableElement.toString().contentEquals(methodName)) return true; + // Check if it is overriding the target method + // Because AnnotatedTypes.overriddenMethods returns all the methods overriden in the class + // hierarchy, we need to + // iterate over the set to check if it's overriding corresponding methods specifically in + // java.lang.Object class + Iterator> overriddenMethods = + AnnotatedTypes.overriddenMethods( + annotatedTypeFactory.getElementUtils(), + annotatedTypeFactory, + executableElement) + .entrySet() + .iterator(); + while (overriddenMethods.hasNext()) { + if (overriddenMethods.next().getValue().toString().contentEquals(methodName)) { + return true; + } + } + return false; + } + + public static void addDefaultForField( + AnnotatedTypeFactory annotatedTypeFactory, + AnnotatedTypeMirror annotatedTypeMirror, + Element element) { + if (element != null && element.getKind() == ElementKind.FIELD) { + if (ElementUtils.isStatic(element)) { + AnnotatedTypeMirror explicitATM = annotatedTypeFactory.fromElement(element); + if (!explicitATM.hasAnnotationInHierarchy(READONLY)) { + if (!PICOTypeUtil.isImplicitlyImmutableType(explicitATM)) { + annotatedTypeMirror.replaceAnnotation(MUTABLE); + } + } + } else { + AnnotatedTypeMirror explicitATM = annotatedTypeFactory.fromElement(element); + if (!explicitATM.hasAnnotationInHierarchy(READONLY)) { + if (explicitATM instanceof AnnotatedDeclaredType) { + AnnotatedDeclaredType adt = (AnnotatedDeclaredType) explicitATM; + Element typeElement = adt.getUnderlyingType().asElement(); + + // add RDM if bound=M and enclosingBound=M/RDM + Set enclosingBound = + annotatedTypeFactory.getTypeDeclarationBounds( + Objects.requireNonNull( + ElementUtils.enclosingTypeElement(element)) + .asType()); + Set declBound = + annotatedTypeFactory.getTypeDeclarationBounds(element.asType()); + if (AnnotationUtils.containsSameByName(declBound, MUTABLE)) { + if (AnnotationUtils.containsSameByName(enclosingBound, MUTABLE)) { + annotatedTypeMirror.replaceAnnotation(RECEIVER_DEPENDANT_MUTABLE); + } + } + if (typeElement instanceof TypeElement) { + AnnotatedDeclaredType bound = + getBoundTypeOfTypeDeclaration( + (TypeElement) typeElement, annotatedTypeFactory); + if (bound.hasAnnotation(RECEIVER_DEPENDANT_MUTABLE)) { + annotatedTypeMirror.replaceAnnotation(RECEIVER_DEPENDANT_MUTABLE); + } + } + } else if (explicitATM instanceof AnnotatedArrayType) { + // Also apply rdm to array main. + annotatedTypeMirror.replaceAnnotation(RECEIVER_DEPENDANT_MUTABLE); + } + } + } + } + } + + public static boolean isEnumOrEnumConstant(AnnotatedTypeMirror annotatedTypeMirror) { + if (!(annotatedTypeMirror instanceof AnnotatedDeclaredType)) { + return false; + } + Element element = + ((AnnotatedDeclaredType) annotatedTypeMirror).getUnderlyingType().asElement(); + return element != null + && (element.getKind() == ElementKind.ENUM_CONSTANT + || element.getKind() == ElementKind.ENUM); + } + + public static boolean isEnumOrEnumConstant(TypeMirror type) { + if (!(type instanceof DeclaredType)) { + return false; + } + Element element = ((DeclaredType) type).asElement(); + return element != null + && (element.getKind() == ElementKind.ENUM_CONSTANT + || element.getKind() == ElementKind.ENUM); + } + + public static void applyImmutableToEnumAndEnumConstant( + AnnotatedTypeMirror annotatedTypeMirror) { + if (isEnumOrEnumConstant(annotatedTypeMirror)) { + // I guess enum constant can't have explicit annotation, am I right? + annotatedTypeMirror.addMissingAnnotations(Arrays.asList(IMMUTABLE)); + } + } + + // Default annotation on type declaration to constructor return type if elt is constructor and + // doesn't have + // explicit annotation(type is actually AnnotatedExecutableType of executable element - elt + // constructor) + public static void defaultConstructorReturnToClassBound( + AnnotatedTypeFactory annotatedTypeFactory, Element elt, AnnotatedTypeMirror type) { + if (elt.getKind() == ElementKind.CONSTRUCTOR && type instanceof AnnotatedExecutableType) { + AnnotatedDeclaredType bound = + PICOTypeUtil.getBoundTypeOfEnclosingTypeDeclaration(elt, annotatedTypeFactory); + ((AnnotatedExecutableType) type) + .getReturnType() + .addMissingAnnotations(Arrays.asList(bound.getAnnotationInHierarchy(READONLY))); + } + } + + /** Check if a field is final or not. */ + public static boolean isFinalField(Element variableElement) { + assert variableElement instanceof VariableElement; // FIXME consider rm + return ElementUtils.isFinal(variableElement); + } + + /** Check if a field is assignable or not. */ + public static boolean isAssignableField(Element variableElement, AnnotationProvider provider) { + if (!(variableElement instanceof VariableElement)) { // FIXME consider rm + return false; + } + boolean hasExplicitAssignableAnnotation = + provider.getDeclAnnotation(variableElement, Assignable.class) != null; + if (!ElementUtils.isStatic(variableElement)) { + // Instance fields must have explicit @Assignable annotation to be assignable + return hasExplicitAssignableAnnotation; + } else { + // If there is explicit @Assignable annotation on static fields, then it's assignable; + // If there isn't, + // and the static field is not final, we treat it as if it's assignable field. + return hasExplicitAssignableAnnotation || !isFinalField(variableElement); + } + } + + /** Check if a field is @ReceiverDependantAssignable. Static fields always returns false. */ + public static boolean isReceiverDependantAssignable( + Element variableElement, AnnotationProvider provider) { + assert variableElement instanceof VariableElement; + if (ElementUtils.isStatic(variableElement)) { + // Static fields can never be @ReceiverDependantAssignable! + return false; + } + return !isAssignableField(variableElement, provider) && !isFinalField(variableElement); + } + + public static boolean hasOneAndOnlyOneAssignabilityQualifier( + VariableElement field, AnnotationProvider provider) { + boolean valid = false; + if (isAssignableField(field, provider) + && !isFinalField(field) + && !isReceiverDependantAssignable(field, provider)) { + valid = true; + } else if (!isAssignableField(field, provider) + && isFinalField(field) + && !isReceiverDependantAssignable(field, provider)) { + valid = true; + } else if (!isAssignableField(field, provider) + && !isFinalField(field) + && isReceiverDependantAssignable(field, provider)) { + assert !ElementUtils.isStatic(field); + valid = true; + } + return valid; + } + + public static boolean isAssigningAssignableField( + ExpressionTree node, AnnotationProvider provider) { + Element fieldElement = TreeUtils.elementFromUse(node); + if (fieldElement == null) return false; + return isAssignableField(fieldElement, provider); + } + + public static boolean inStaticScope(TreePath treePath) { + boolean in = false; + if (TreePathUtil.isTreeInStaticScope(treePath)) { + in = true; + // Exclude case in which enclosing class is static + ClassTree classTree = TreePathUtil.enclosingClass(treePath); + if (classTree != null + && classTree.getModifiers().getFlags().contains(Modifier.STATIC)) { + in = false; + } + } + return in; + } + + public static boolean isSideEffectingUnaryTree(final UnaryTree tree) { + return sideEffectingUnaryOperators.contains(tree.getKind()); + } + + /** + * !! Experimental !! + * + *

CF's isAnonymous cannot recognize some anonymous classes with annotation on new clause. + * This one hopefully will provide a workaround when the class tree is available. + * + *

This will work if an anonymous class decl tree is always a child node of a {@code + * NewClassTree}. i.e. an anonymous class declaration is always inside a new clause. + * + * @param tree a class decl tree. + * @param atypeFactory used to get the path. Any factory should be ok. + * @return whether the class decl tree is of an anonymous class + */ + public static boolean isAnonymousClassTree(ClassTree tree, AnnotatedTypeFactory atypeFactory) { + TreePath path = atypeFactory.getPath(tree); + Tree parent = path.getParentPath().getLeaf(); + return parent instanceof NewClassTree && ((NewClassTree) parent).getClassBody() != null; + } + + /** + * !! Experimental !! Check whether the type is actually an array. + * + * @param type AnnotatedDeclaredType + * @param typeFactory any AnnotatedTypeFactory + * @return true if the type is array + */ + public static boolean isArrayType( + AnnotatedDeclaredType type, AnnotatedTypeFactory typeFactory) { + Element ele = + typeFactory.getProcessingEnv().getTypeUtils().asElement(type.getUnderlyingType()); + + // If it is a user-declared "Array" class without package, a class / source file should be + // there. + // Otherwise, it is the java inner type. + return ele instanceof Symbol.ClassSymbol + && ElementUtils.getQualifiedName(ele).equals("Array") + && ((Symbol.ClassSymbol) ele).classfile == null + && ((Symbol.ClassSymbol) ele).sourcefile == null; + } +} diff --git a/checker/src/main/java/org/checkerframework/checker/immutability/PICOValidator.java b/checker/src/main/java/org/checkerframework/checker/immutability/PICOValidator.java new file mode 100644 index 00000000000..7e1fe75e04c --- /dev/null +++ b/checker/src/main/java/org/checkerframework/checker/immutability/PICOValidator.java @@ -0,0 +1,148 @@ +package org.checkerframework.checker.immutability; + +import static org.checkerframework.checker.immutability.PICOAnnotationMirrorHolder.*; +import static org.checkerframework.checker.immutability.PICOAnnotationMirrorHolder.MUTABLE; + +import com.sun.source.tree.Tree; +import com.sun.source.tree.Tree.Kind; +import com.sun.source.tree.VariableTree; + +import org.checkerframework.common.basetype.BaseTypeChecker; +import org.checkerframework.common.basetype.BaseTypeValidator; +import org.checkerframework.common.basetype.BaseTypeVisitor; +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.AnnotatedPrimitiveType; +import org.checkerframework.javacutil.AnnotationUtils; +import org.checkerframework.javacutil.ElementUtils; +import org.checkerframework.javacutil.TreePathUtil; +import org.checkerframework.javacutil.TreeUtils; + +import java.util.Objects; +import java.util.Set; + +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.element.ElementKind; +import javax.lang.model.element.VariableElement; + +/** + * Created by mier on 29/09/17. Enforce correct usage of immutability and assignability qualifiers. + * TODO @PolyMutable is only used on constructor/method parameters or method return + */ +public class PICOValidator extends BaseTypeValidator { + public PICOValidator( + BaseTypeChecker checker, + BaseTypeVisitor visitor, + AnnotatedTypeFactory atypeFactory) { + super(checker, visitor, atypeFactory); + } + + @Override + public Void visitDeclared(AnnotatedDeclaredType type, Tree tree) { + checkStaticReceiverDependantMutableError(type, tree); + checkImplicitlyImmutableTypeError(type, tree); + checkOnlyOneAssignabilityModifierOnField(tree); + + return super.visitDeclared(type, tree); + } + + @Override + protected boolean shouldCheckTopLevelDeclaredOrPrimitiveType( + AnnotatedTypeMirror type, Tree tree) { + // check top annotations in extends/implements clauses + if ((tree.getKind() == Kind.IDENTIFIER || tree.getKind() == Kind.PARAMETERIZED_TYPE) + && PICONoInitAnnotatedTypeFactory.PICOSuperClauseAnnotator.isSuperClause( + atypeFactory.getPath(tree))) { + return true; + } + // allow RDM on mutable fields with enclosing class bounded with mutable + if (tree instanceof VariableTree) { + VariableElement element = TreeUtils.elementFromDeclaration((VariableTree) tree); + if (element.getKind() == ElementKind.FIELD + && ElementUtils.enclosingTypeElement(element) != null) { + Set enclosingBound = + atypeFactory.getTypeDeclarationBounds( + Objects.requireNonNull(ElementUtils.enclosingTypeElement(element)) + .asType()); + + Set declaredBound = + atypeFactory.getTypeDeclarationBounds(type.getUnderlyingType()); + + if (AnnotationUtils.containsSameByName(declaredBound, MUTABLE) + && type.hasAnnotation(RECEIVER_DEPENDANT_MUTABLE) + && AnnotationUtils.containsSameByName(enclosingBound, MUTABLE)) { + return false; + } + } + } + // if (TreeUtils.isLocalVariable(tree)) { + // return true; + // } + + return super.shouldCheckTopLevelDeclaredOrPrimitiveType(type, tree); + } + + @Override + public Void visitArray(AnnotatedArrayType type, Tree tree) { + checkStaticReceiverDependantMutableError(type, tree); + // Array can not be implicitly immutable + return super.visitArray(type, tree); + } + + @Override + public Void visitPrimitive(AnnotatedPrimitiveType type, Tree tree) { + checkImplicitlyImmutableTypeError(type, tree); + return super.visitPrimitive(type, tree); + } + + private void checkStaticReceiverDependantMutableError(AnnotatedTypeMirror type, Tree tree) { + if (!type.isDeclaration() // variables in static contexts and static fields use class + // decl as enclosing type + && PICOTypeUtil.inStaticScope(visitor.getCurrentPath()) + && !"" + .contentEquals( + Objects.requireNonNull( + TreePathUtil.enclosingClass( + visitor.getCurrentPath())) + .getSimpleName()) // Exclude @RDM usages in anonymous + // classes + && type.hasAnnotation(RECEIVER_DEPENDANT_MUTABLE)) { + reportValidityResult("static.receiverdependantmutable.forbidden", type, tree); + } + } + + /** + * Check that implicitly immutable type has immutable or bottom type. Dataflow might refine + * immutable type to {@code @Bottom} (see RefineFromNull.java), so we accept @Bottom as a valid + * qualifier for implicitly immutable types + */ + private void checkImplicitlyImmutableTypeError(AnnotatedTypeMirror type, Tree tree) { + if (PICOTypeUtil.isImplicitlyImmutableType(type) + && !type.hasAnnotation(IMMUTABLE) + && !type.hasAnnotation(BOTTOM)) { + reportInvalidAnnotationsOnUse(type, tree); + } + } + + /** + * Ensures the well-formdness in terms of assignability on a field. This covers both instance + * fields and static fields. + */ + private void checkOnlyOneAssignabilityModifierOnField(Tree tree) { + if (tree.getKind() == Kind.VARIABLE) { + VariableTree variableTree = (VariableTree) tree; + VariableElement variableElement = TreeUtils.elementFromDeclaration(variableTree); + if (!PICOTypeUtil.hasOneAndOnlyOneAssignabilityQualifier( + variableElement, atypeFactory)) { + reportFieldMultipleAssignabilityModifiersError(variableElement); + } + } + } + + private void reportFieldMultipleAssignabilityModifiersError(VariableElement field) { + checker.reportError(field, "one.assignability.invalid", field); + isValid = false; + } +} diff --git a/checker/src/main/java/org/checkerframework/checker/immutability/PICOViewpointAdapter.java b/checker/src/main/java/org/checkerframework/checker/immutability/PICOViewpointAdapter.java new file mode 100644 index 00000000000..2847d2f4fd4 --- /dev/null +++ b/checker/src/main/java/org/checkerframework/checker/immutability/PICOViewpointAdapter.java @@ -0,0 +1,101 @@ +package org.checkerframework.checker.immutability; + +import static org.checkerframework.checker.immutability.PICOAnnotationMirrorHolder.BOTTOM; +import static org.checkerframework.checker.immutability.PICOAnnotationMirrorHolder.IMMUTABLE; +import static org.checkerframework.checker.immutability.PICOAnnotationMirrorHolder.MUTABLE; +import static org.checkerframework.checker.immutability.PICOAnnotationMirrorHolder.POLY_MUTABLE; +import static org.checkerframework.checker.immutability.PICOAnnotationMirrorHolder.READONLY; +import static org.checkerframework.checker.immutability.PICOAnnotationMirrorHolder.RECEIVER_DEPENDANT_MUTABLE; + +import org.checkerframework.framework.type.AbstractViewpointAdapter; +import org.checkerframework.framework.type.AnnotatedTypeFactory; +import org.checkerframework.framework.type.AnnotatedTypeMirror; +import org.checkerframework.javacutil.AnnotationUtils; +import org.checkerframework.javacutil.BugInCF; + +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.element.Element; +import javax.lang.model.type.TypeKind; + +public class PICOViewpointAdapter extends AbstractViewpointAdapter + implements ExtendedViewpointAdapter { + + public PICOViewpointAdapter(AnnotatedTypeFactory atypeFactory) { + super(atypeFactory); + } + + @Override + protected boolean shouldAdaptMember(AnnotatedTypeMirror type, Element element) { + if (!(type.getKind() == TypeKind.DECLARED || type.getKind() == TypeKind.ARRAY)) { + return false; + } + return super.shouldAdaptMember(type, element); + } + + @Override + protected AnnotationMirror extractAnnotationMirror(AnnotatedTypeMirror atm) { + return atm.getAnnotationInHierarchy(READONLY); + } + + @Override + protected AnnotationMirror combineAnnotationWithAnnotation( + AnnotationMirror receiverAnnotation, AnnotationMirror declaredAnnotation) { + if (AnnotationUtils.areSame(declaredAnnotation, READONLY)) { + return READONLY; + } else if (AnnotationUtils.areSame(declaredAnnotation, MUTABLE)) { + return MUTABLE; + } else if (AnnotationUtils.areSame(declaredAnnotation, IMMUTABLE)) { + return IMMUTABLE; + } else if (AnnotationUtils.areSame(declaredAnnotation, BOTTOM)) { + return BOTTOM; + } else if (AnnotationUtils.areSame(declaredAnnotation, POLY_MUTABLE)) { + return POLY_MUTABLE; + } else if (AnnotationUtils.areSame(declaredAnnotation, RECEIVER_DEPENDANT_MUTABLE)) { + return receiverAnnotation; + } else { + throw new BugInCF("Unknown declared modifier: " + declaredAnnotation); + } + } + + // + // @Override + // protected AnnotatedTypeMirror combineAnnotationWithType(AnnotationMirror + // receiverAnnotation, AnnotatedTypeMirror declared) { + // boolean prevRdm = declared.hasAnnotation(RECEIVER_DEPENDANT_MUTABLE); + // AnnotatedTypeMirror raw = super.combineAnnotationWithType(receiverAnnotation, + // declared); + // if(prevRdm && + // + // AnnotationUtils.containsSameByName(atypeFactory.getTypeDeclarationBounds(declared.getUnderlyingType()), MUTABLE) + // && (raw.hasAnnotation(IMMUTABLE) || + // raw.hasAnnotation(RECEIVER_DEPENDANT_MUTABLE))) { + // raw.replaceAnnotation(MUTABLE); + // } + // return raw; + // } + @Override + public AnnotatedTypeMirror rawCombineAnnotationWithType( + AnnotationMirror anno, AnnotatedTypeMirror type) { + // System.err.println("VPA: " + anno + " ->" + type); + return combineAnnotationWithType(anno, type); + } + + @Override + public AnnotationMirror rawCombineAnnotationWithAnnotation( + AnnotationMirror anno, AnnotationMirror type) { + // System.err.println("VPA: " + anno + " ->" + type); + return combineAnnotationWithAnnotation(anno, type); + } + + // + // @Override + // protected AnnotationMirror getModifier(AnnotatedTypeMirror atm, AnnotatedTypeFactory f) { + // return atm.getAnnotationInHierarchy(READONLY); + // } + + // @Override + // protected AnnotationMirror + // extractModifier(AnnotatedTypeMirror atm, TypeFactory f) { + // return null; + // } +} diff --git a/checker/src/main/java/org/checkerframework/checker/immutability/ViewpointAdapterGettable.java b/checker/src/main/java/org/checkerframework/checker/immutability/ViewpointAdapterGettable.java new file mode 100644 index 00000000000..3ddae2f16f2 --- /dev/null +++ b/checker/src/main/java/org/checkerframework/checker/immutability/ViewpointAdapterGettable.java @@ -0,0 +1,5 @@ +package org.checkerframework.checker.immutability; + +public interface ViewpointAdapterGettable { + ExtendedViewpointAdapter getViewpointAdapter(); +} diff --git a/checker/src/main/java/org/checkerframework/checker/immutability/messages.properties b/checker/src/main/java/org/checkerframework/checker/immutability/messages.properties new file mode 100644 index 00000000000..1e88b57f242 --- /dev/null +++ b/checker/src/main/java/org/checkerframework/checker/immutability/messages.properties @@ -0,0 +1,15 @@ +constructor.invocation.invalid=Cannot not instantiate type: %s out of constructor: %s +constructor.return.invalid=Invalid constructor return type: %s +method.receiver.incompatible=Incompatible method receiver: %s +class.bound.invalid=Invalid class bound: %s +subclass.bound.incompatible=Incompatible subclass bound: %s +illegal.field.write=Cannot write field via receiver: %s +illegal.array.write=Cannot write array via receiver: %s +static.receiverdependantmutable.forbidden=%s is forbidden in static context +pico.new.invalid=Invalid new instance type: %s +field.polymutable.forbidden=Field %s cannot be @PolyMutable +one.assignability.invalid=Only one assignability qualifier is allowed on %s +object.identity.method.invocation.invalid=Cannot invoke non-object identity method %s from object identity context! +object.identity.field.access.invalid=Object identity context cannot reference non abstract state field %s! +object.identity.static.field.access.forbidden=Object identity context cannot reference static field %s! +implicit.shallow.immutable=Write an explicit mutable qualifier if wants to exclude the field from the abstract state! Otherwise change the class mutability of this object ! diff --git a/checker/src/test/java/org/checkerframework/checker/test/junit/ImmutabilityTypecheckTest.java b/checker/src/test/java/org/checkerframework/checker/test/junit/ImmutabilityTypecheckTest.java new file mode 100644 index 00000000000..0cb0e8563f6 --- /dev/null +++ b/checker/src/test/java/org/checkerframework/checker/test/junit/ImmutabilityTypecheckTest.java @@ -0,0 +1,18 @@ +package org.checkerframework.checker.test.junit; + +import org.checkerframework.checker.immutability.PICOChecker; +import org.checkerframework.framework.test.CheckerFrameworkPerFileTest; +import org.junit.runners.Parameterized.Parameters; + +import java.io.File; + +public class ImmutabilityTypecheckTest extends CheckerFrameworkPerFileTest { + public ImmutabilityTypecheckTest(File testFile) { + super(testFile, PICOChecker.class, "immutability", "-Anomsgtext", "-Anocheckjdk"); + } + + @Parameters + public static String[] getTestDirs() { + return new String[] {"immutability"}; + } +} diff --git a/checker/tests/immutability/AnnotationApplicationOrder.java b/checker/tests/immutability/AnnotationApplicationOrder.java new file mode 100644 index 00000000000..bba1ca9005a --- /dev/null +++ b/checker/tests/immutability/AnnotationApplicationOrder.java @@ -0,0 +1,18 @@ +import java.math.BigDecimal; +import java.util.Date; + +/** + * Computed annotations are applied in thir order: TreeAnnotator(with order inside also) - eg. Takes + * care of annotations according to ast locations, for example, fields TypeAnnotator(with order + * inside also) - eg. ImplicitFor using typeNames; changing method signatures according to method + * name Defaults - eg. @DefaultFor using TypeUseLocation. Usually doesn't change previous result. + * But May mutate result from above sometimes. For example, if we apply top annotation to type + * variable used on local variable if we don't override getShouldDefaultTypeVarLocals() to false + * Dataflow refinement + */ +public class AnnotationApplicationOrder { + static Object o; // PICOTreeAnnotator takes care of static fields + // :: error: (initialization.field.uninitialized) + BigDecimal decimal; // PICOImplicitsTypeAnnotator takes care of it + Date date; // QualifierDefaults takes care of it +} diff --git a/checker/tests/immutability/AnonymousClassBound.java b/checker/tests/immutability/AnonymousClassBound.java new file mode 100644 index 00000000000..20fa0aac84f --- /dev/null +++ b/checker/tests/immutability/AnonymousClassBound.java @@ -0,0 +1,9 @@ +class A {} + +public class AnonymousClassBound { + static A[] as = new A[1]; + + static { + as[0] = new A() {}; + } +} diff --git a/checker/tests/immutability/Arrays.java b/checker/tests/immutability/Arrays.java new file mode 100644 index 00000000000..0cd7a1cc26a --- /dev/null +++ b/checker/tests/immutability/Arrays.java @@ -0,0 +1,39 @@ +import org.checkerframework.checker.immutability.qual.Immutable; +import org.checkerframework.checker.immutability.qual.Mutable; +import org.checkerframework.checker.immutability.qual.Readonly; + +public class Arrays { + + void test1(String @Immutable [] array) { + // :: error: (illegal.array.write) + array[0] = "something"; + } + + void test2() { + // :: error: (pico.new.invalid) + int[] a = new int @Readonly [] {1, 2}; + } + + void test3(String[] array) { + array[0] = "something"; + } + + void test4(@Immutable String @Mutable [] p) { + // :: error: (assignment.type.incompatible) + Object[] l = p; // By default, array type is @Readonly(local variable); Object class is by + // default @Mutable. So assignment should not typecheck + } + + void test5(@Immutable Integer @Mutable [] p) { + // :: error: (assignment.type.incompatible) + @Mutable Object @Readonly [] l = p; + } + + void test6(double @Readonly [] a1, double @Readonly [] a2) { + java.util.Arrays.equals(a1, a2); + } + + void test7() { + @Readonly Object[] f = new String @Immutable [] {"HELLO"}; + } +} diff --git a/checker/tests/immutability/AssignableExample.java b/checker/tests/immutability/AssignableExample.java new file mode 100644 index 00000000000..0f3bcb2d644 --- /dev/null +++ b/checker/tests/immutability/AssignableExample.java @@ -0,0 +1,44 @@ +import org.checkerframework.checker.immutability.qual.Assignable; +import org.checkerframework.checker.immutability.qual.Immutable; +import org.checkerframework.checker.immutability.qual.Mutable; +import org.checkerframework.checker.immutability.qual.ReceiverDependantMutable; + +import java.util.Date; + +@ReceiverDependantMutable +public class AssignableExample { + @Immutable Object o; + @Immutable Date date; + @Assignable @Immutable Date assignableDate; + + // :: error: (initialization.fields.uninitialized) + @Mutable + AssignableExample() { + o = new @Immutable Object(); + } + + void foo(@Immutable AssignableExample this) { + // :: error: (illegal.field.write) + this.date = new @Immutable Date(); + this.assignableDate = new @Immutable Date(); + } + + void foo2(@Mutable AssignableExample this) { + this.o = new @Immutable Object(); + } +} + +// :: error: (super.invocation.invalid) +@ReceiverDependantMutable +class Subclass extends AssignableExample { + void bar(@Immutable Subclass this) { + // :: error: (illegal.field.write) + this.date = new @Immutable Date(); + this.assignableDate = new @Immutable Date(); + } + + void bar2(@Mutable Subclass this) { + this.date = new @Immutable Date(); + this.assignableDate = new @Immutable Date(); + } +} diff --git a/checker/tests/immutability/BigPrefix.java b/checker/tests/immutability/BigPrefix.java new file mode 100644 index 00000000000..7231547820a --- /dev/null +++ b/checker/tests/immutability/BigPrefix.java @@ -0,0 +1,13 @@ +import java.math.BigDecimal; +import java.math.MathContext; + +public abstract class BigPrefix { + + public static final BigDecimal YOCTO = + new BigDecimal("0.000000000000000000000001", MathContext.DECIMAL128); +} + +abstract class PrimitivePrefix { + + public static final double YOCTO = BigPrefix.YOCTO.doubleValue(); +} diff --git a/checker/tests/immutability/BinaryOperator.java b/checker/tests/immutability/BinaryOperator.java new file mode 100644 index 00000000000..b55c32fb520 --- /dev/null +++ b/checker/tests/immutability/BinaryOperator.java @@ -0,0 +1,13 @@ +import org.checkerframework.checker.immutability.qual.Readonly; + +public class BinaryOperator { + @Readonly Object o; + + String test1() { + return "Object is: " + o; + } + + String test2() { + return o.toString(); + } +} diff --git a/checker/tests/immutability/BottomArrayWrite.java b/checker/tests/immutability/BottomArrayWrite.java new file mode 100644 index 00000000000..d3cfc0ccfaf --- /dev/null +++ b/checker/tests/immutability/BottomArrayWrite.java @@ -0,0 +1,36 @@ +import java.util.Arrays; + +public class BottomArrayWrite { + double[] a = null; + + BottomArrayWrite(double[] a) { + this.a = a; + } + + void foo() { + // If we don't use flow sensitive refinement when a is on lhs, + // we don't get illegal.array.write via @FBCBottom @Bottom receiver + // error; But this also fails local variables that can be mutated + // (refined to/assigned mutable objects). + a[0] = 1.0; + } + + void bar() { + a = new double[1]; + } + + @Override + public int hashCode() { + int hash = 0; + // :: error: (argument.type.incompatible) + hash = 83 * hash + Arrays.hashCode(a); + return hash; + } +} + +class Tester { + + public static void foo(BottomArrayWrite b) { + b.a[0] = 1.0; + } +} diff --git a/checker/tests/immutability/BoundIncompatible.java b/checker/tests/immutability/BoundIncompatible.java new file mode 100644 index 00000000000..5b2d0ad733a --- /dev/null +++ b/checker/tests/immutability/BoundIncompatible.java @@ -0,0 +1,14 @@ +import org.checkerframework.checker.immutability.qual.Immutable; +import org.checkerframework.checker.immutability.qual.Mutable; +import org.checkerframework.checker.immutability.qual.ReceiverDependantMutable; + +public class BoundIncompatible implements java.io.Serializable {} + +@Mutable +class A implements java.io.Serializable {} + +@ReceiverDependantMutable +class B implements java.io.Serializable {} + +@Immutable +class C implements java.io.Serializable {} diff --git a/checker/tests/immutability/CloneCaseStudy.java b/checker/tests/immutability/CloneCaseStudy.java new file mode 100644 index 00000000000..4e42daacda8 --- /dev/null +++ b/checker/tests/immutability/CloneCaseStudy.java @@ -0,0 +1,82 @@ +// clone() method proposal: +// 1. Let overriding check in type declaration with @Mutable and @Immutable +// bound more flexible. +// In the overriding clone() method in those two type declarations, +// as long as declared receiver and return type are the same as bound, +// overriding check passes. For example, @Mutable class can override clone() +// method as @Mutable Object clone(@Mutable A this) while @Immutable class +// can override to @Immutable Object clone(@Immutable B this). But +// @ReceiverDependantMutable class should keep the exact same signature as that +// in jdk.astub, because both @Mutable and @Immutable might be the client. +// Overriding to either @Mutable or @Immutable may cause existing client to break. + +// 2. clone() method has the same defaulted mechanism as other instance methods +// (Remove the special handling of clone() method) + +// 3. There is still need to specially handle super.clone() invocation, because @Immutable +// object's @RDA @Mutable field and @RDA @Readonly field are not guaranteed to be @Immutable +// so there might be use cases to clone those fields and reassign them to the cloned copy. + +// 3.1 As of method signature of clone() method in terms of initialization qualifiers, +// I would keep method return @Initialized. It's common that overriding method call the constructor +// and returns a new @Initialized cloned copy. Chaning clone() method return to @UnderInitialization +// just for special handling for super invocations doesn't make much sense. +// But this requires us to disable subtype checking of return type in terms of initialization +// hierarchy. + +// ======================== Outdated =======================// +// 3.1 Assigning field in cloned copy is an indication of mutable object. Because +// @Immutable objects can not be modified, so no need to deeply clone it. So let's +// treat reassigning cloned object as sign of @Mutable object, thus don't need special +// handling for super.clone()'s result(i.e. no @UnderInitialization staff for the super +// clone() invocation result). + +// 3.2 @Immutable classes' clone() method should either: 1) directly call new +// instance creation and all the initialization steps are finished after constructor +// returns. This is also consistent with the case where objectsa are finished initialization +// after constructor returns and no furthur modification is allowed outside constructor. +// 2) doesn't override clone() method(by default shallow copies receiver object which +// makes sense) +// ======================== Outdated =======================// + +// @skip-test + +public class CloneCaseStudy { + // @Initialized return type + public Object clone() { + AbstractDistribution copy = + (AbstractDistribution) super.clone(); // returns @UnderInitialization object + // Allow reassigning since copy is @UnderInitialization + if (this.randomGenerator != null) + copy.randomGenerator = (RandomEngine) this.randomGenerator.clone(); + // Disable subtype checking in terms of initialization hierarchy. Since + // @UnderInitialization is not + // subtype of @Initialized + return copy; + } + + public Object clone() { + return new Algebra(property.tolerance()); + } + + public Object clone() { + BooleanArrayList clone = new BooleanArrayList((boolean[]) elements.clone()); + // clone finished being initialized, so later instance method call to mutate the state of + // clone makes clone to be @Mutable. This is different from super.clone() extends + // initialization + // scope + clone.setSizeRaw(size); + return clone; + } + + public Object clone() { + return partFromTo(0, size - 1); + } + + public AbstractShortList partFromTo(int from, int to) { + int length = to - from + 1; + ShortArrayList part = new ShortArrayList(length); + part.addAllOfFromTo(this, from, to); + return part; + } +} diff --git a/checker/tests/immutability/CloneProblem.java b/checker/tests/immutability/CloneProblem.java new file mode 100644 index 00000000000..c7b8f66a2fc --- /dev/null +++ b/checker/tests/immutability/CloneProblem.java @@ -0,0 +1,17 @@ +public class CloneProblem { + public boolean removeAll(CloneProblem other) throws CloneNotSupportedException { + if (true) { + CloneProblem l = (CloneProblem) other.overridenClone(); + // Gets method invocation invalid error + l.foo(); + } + return true; + } + + // This return @Mutable Object + public Object overridenClone() { + return null; + } + + void foo() {} +} diff --git a/checker/tests/immutability/CompatabilityBEI1.java b/checker/tests/immutability/CompatabilityBEI1.java new file mode 100644 index 00000000000..28a826a5261 --- /dev/null +++ b/checker/tests/immutability/CompatabilityBEI1.java @@ -0,0 +1,61 @@ +import org.checkerframework.checker.immutability.qual.Immutable; +import org.checkerframework.checker.immutability.qual.Mutable; +import org.checkerframework.checker.immutability.qual.ReceiverDependantMutable; + +@Mutable +public class CompatabilityBEI1 {} + +@Immutable +class A {} + +@ReceiverDependantMutable +class B {} + +@Mutable +class C implements Cloneable {} + +@Immutable +class D implements Cloneable {} + +@ReceiverDependantMutable +class E implements Cloneable {} + +@Mutable +class F extends Object {} + +@Immutable +class G extends Object {} + +@ReceiverDependantMutable +class H extends Object {} + +@Mutable +class I implements @Mutable Cloneable {} + +// :: error: (declaration.inconsistent.with.implements.clause) +@Mutable +class J implements @Immutable Cloneable {} + +@Mutable +class K implements @ReceiverDependantMutable Cloneable {} + +// :: error: (declaration.inconsistent.with.extends.clause) +@Immutable +class L extends @Mutable Object {} + +@Immutable +class M extends @Immutable Object {} + +@Immutable +class N extends @ReceiverDependantMutable Object {} + +abstract class O implements CharSequence {} + +@Mutable +abstract class P implements CharSequence {} + +@Immutable +abstract class Q implements CharSequence {} + +@ReceiverDependantMutable +abstract class R implements CharSequence {} diff --git a/checker/tests/immutability/CompatibilityBEI2.java b/checker/tests/immutability/CompatibilityBEI2.java new file mode 100644 index 00000000000..857f53c525e --- /dev/null +++ b/checker/tests/immutability/CompatibilityBEI2.java @@ -0,0 +1,60 @@ +import org.checkerframework.checker.immutability.qual.Immutable; +import org.checkerframework.checker.immutability.qual.Mutable; +import org.checkerframework.checker.immutability.qual.ReceiverDependantMutable; + +import java.util.ArrayList; +import java.util.List; + +@Mutable +abstract class C implements List<@Immutable Object> {} + +@Immutable +abstract class D implements List<@Immutable Object> {} + +@ReceiverDependantMutable +abstract class E implements List<@Immutable Object> {} + +@Mutable +class F extends ArrayList<@Immutable Object> {} + +@Immutable +class G extends ArrayList<@Immutable Object> {} + +@ReceiverDependantMutable +class H extends ArrayList<@Immutable Object> {} + +@Mutable +abstract class I implements @Mutable List<@Immutable Object> {} + +// :: error: (declaration.inconsistent.with.implements.clause) +@Mutable +abstract class J implements @Immutable List<@Immutable Object> {} + +@Mutable +abstract class K implements @ReceiverDependantMutable List<@Immutable Object> {} + +// :: error: (declaration.inconsistent.with.extends.clause) +@Immutable +class L extends @Mutable ArrayList<@Immutable Object> {} + +@Immutable +class M extends @Immutable ArrayList<@Immutable Object> {} + +@Immutable +class N extends @ReceiverDependantMutable ArrayList<@Immutable Object> {} + +abstract class O implements CharSequence {} + +@Immutable +interface ImmutableInterface {} + +// :: error: (type.invalid.annotations.on.use) +@Mutable +abstract class P implements ImmutableInterface<@Mutable Object> {} + +@Immutable +abstract class Q implements ImmutableInterface<@Immutable Object> {} + +// :: error: (type.invalid.annotations.on.use) +@ReceiverDependantMutable +abstract class R implements ImmutableInterface<@ReceiverDependantMutable Object> {} diff --git a/checker/tests/immutability/ComplicatedTest.java b/checker/tests/immutability/ComplicatedTest.java new file mode 100644 index 00000000000..b8b3ba2bc89 --- /dev/null +++ b/checker/tests/immutability/ComplicatedTest.java @@ -0,0 +1,77 @@ +import org.checkerframework.checker.immutability.qual.Immutable; +import org.checkerframework.checker.immutability.qual.Mutable; +import org.checkerframework.checker.immutability.qual.Readonly; +import org.checkerframework.checker.immutability.qual.ReceiverDependantMutable; + +import java.util.ArrayList; + +@ReceiverDependantMutable +class Person { + + protected String name; + protected int age; + protected @ReceiverDependantMutable ArrayList friends; + + public @ReceiverDependantMutable Person( + String name, int age, @ReceiverDependantMutable ArrayList friends) { + this.name = name; + this.age = age; + this.friends = friends; + } + + public String getName(@Readonly Person this) { + return name; + } + + public void setName(@Mutable Person this, String newName) { + name = newName; + } + + public int getAge(@Readonly Person this) { + return age; + } + + public @ReceiverDependantMutable ArrayList getFriends( + @ReceiverDependantMutable Person this) { + return friends; + } +} + +public class ComplicatedTest { + + void testImmutability() { + String name = "tamier"; + int age = 24; + @Immutable ArrayList friends = new @Immutable ArrayList(); + @Immutable Person p = new @Immutable Person(name, age, friends); + String newName = "newName"; + // :: error: (method.invocation.invalid) + p.setName(newName); + // :: error: (method.invocation.invalid) + p.friends.add("newFriend"); + // :: error: (method.invocation.invalid) + p.getFriends().add("newFriend"); + // :: error: (illegal.field.write) + p.name = newName; + // :: error: (illegal.field.write) + p.age = 27; + } + + void testMutability() { + String name = "tamier"; + int age = 24; + @Mutable ArrayList friends = new @Mutable ArrayList(); + @Mutable Person p = new @Mutable Person(name, age, friends); + String newName = "newName"; + // Allow because p is @Mutable + p.setName(newName); + // Allow because p is @Mutable + p.friends.add("newFriend"); + // Allow because p is @Mutable + p.getFriends().add("newFriend"); + // Allow because p is @Mutable + p.name = newName; + // Allow because p is @Mutable + p.age = 27; + } +} diff --git a/checker/tests/immutability/CopyToCast.java b/checker/tests/immutability/CopyToCast.java new file mode 100644 index 00000000000..1e2cc6c5e39 --- /dev/null +++ b/checker/tests/immutability/CopyToCast.java @@ -0,0 +1,26 @@ +import org.checkerframework.checker.immutability.qual.Immutable; +import org.checkerframework.checker.immutability.qual.Mutable; + +public class CopyToCast { + void foo(Object o) { + // No cast.unsafe + String s1 = (@Immutable String) o; + // No cast.unsafe + String s2 = (String) o; + // :: error: (type.invalid.annotations.on.use) + String s3 = (@Mutable String) o; + } + + @Override + public Object clone() throws CloneNotSupportedException { + // TODO Copy method receiver's annotation to super + // TODO Defaults for four Object methods are fixed + CopyToCast oe = (CopyToCast) super.clone(); + return oe; + } + + @Override + public String toString() { + return super.toString(); + } +} diff --git a/checker/tests/immutability/DateCell.java b/checker/tests/immutability/DateCell.java new file mode 100644 index 00000000000..871b3d7f2e0 --- /dev/null +++ b/checker/tests/immutability/DateCell.java @@ -0,0 +1,67 @@ +import org.checkerframework.checker.immutability.qual.Mutable; +import org.checkerframework.checker.immutability.qual.Readonly; +import org.checkerframework.checker.immutability.qual.ReceiverDependantMutable; + +import java.util.Date; + +@ReceiverDependantMutable +public class DateCell { + + @ReceiverDependantMutable Date date; + + @ReceiverDependantMutable + Date getDate(@ReceiverDependantMutable DateCell this) { + return this.date; + } + + @SuppressWarnings({"deprecation"}) + void cellSetHours(@Mutable DateCell this) { + // ReIm argues that viewpoint adapting to lhs(@Mutable here) trasmits the context to current + // "this" via below type rules: + // q(this-cellSetHours) <: q(md) |> q(this-getDate) Which is q(this-cellSetHours) <: + // @Mutable |> @PolyImmutable = @Mutable + // And it gives an counterexample that if we adapt to the receiver of the method invocation, + // we get a not-useful constraint: + // q(this-cellSetHours) <: q(this-cellSetHours) |> q(this-getDate) Which is + // q(this-cellSetHours) <: q(this-cellSetHours) + + // But in fact, we can still transmit that mutability context into current "this" even + // without adapting to lhs. + // q(this-cellSetHours) |> q(ret-getDate) <: q(md) which becomes q(this-cellSetHours) <: + // @Mutable. It still can make current "this" + // to be mutable. + // Truly, viewpoint adaptation to receiver doesn't impose additional constraint on receiver. + // But this makes sense. Because poly + // means that it can be substited by any qualifiers including poly itself. That's exactly + // the purpose of method with poly "this" - + // invocable on all possible types. ReIm also suffers this "not-useful" contraint problem on + // return type adaptation: + // q(md) |> q(ret-getDate) <: q(md) which becomes q(md) <: q(md). So there is no reason for + // ReIm to argue against this "seems-like" + // trivial constraint + @Mutable Date md = this.getDate(); + md.setHours(1); + } + + @SuppressWarnings({"deprecation"}) + void cellGetHours(@Readonly DateCell this) { + // In receiver viewpoint adaptation: + // q(this-cellGetHours) |> @PolyImmutable <: @Readonly => q(this-cellGetHours) <: @Readonly + // So cellGetHours is invocable on + // any types of receiver. In inference, if we prefer top(@Readonly), it still infers current + // "this" to @Readonly. + @Readonly Date rd = this.getDate(); + int hour = rd.getHours(); + } + + // For a method declaration, if type of this, formal parameters and return type are annotated + // with @Readonly, @Immutable, @Mutable, + // adaptating to whatever doesn't make a difference. Only when they are @PolyImmutable, there is + // real difference between the two. + // The most tricky case - receiver, return type are @PolyImmutable is proven to be valid in + // receiver adaptation methodology. + // ReIm doesn't support instatiating poly and readonly objects, so if return type is poly, + // method receiver is also poly. ReIm's + // reasoning using the cellSetHourse, cellGetHours, getDate methods failed to show the necessity + // of viewpoint adapting to lhs. +} diff --git a/checker/tests/immutability/DateCell2.java b/checker/tests/immutability/DateCell2.java new file mode 100644 index 00000000000..e932ffe3377 --- /dev/null +++ b/checker/tests/immutability/DateCell2.java @@ -0,0 +1,27 @@ +import org.checkerframework.checker.immutability.qual.Immutable; +import org.checkerframework.checker.immutability.qual.Mutable; +import org.checkerframework.checker.immutability.qual.PolyMutable; +import org.checkerframework.checker.immutability.qual.ReceiverDependantMutable; + +import java.util.Date; + +@ReceiverDependantMutable +public class DateCell2 { + // :: error: (initialization.field.uninitialized) + @Immutable Date imdate; + + @Immutable + Date getImmutableDate(@PolyMutable DateCell2 this) { + return this.imdate; + } + + /*Not allowed in ReIm. But allowed in PICO*/ + void test1(@Mutable DateCell2 this) { + @Immutable Date imd = this.getImmutableDate(); + } + + void test2(@Immutable DateCell2 this) { + @Immutable DateCell2 waht = new @Immutable DateCell2(); + @Immutable Date imd = this.getImmutableDate(); + } +} diff --git a/checker/tests/immutability/DateCell3.java b/checker/tests/immutability/DateCell3.java new file mode 100644 index 00000000000..37aa40f8c36 --- /dev/null +++ b/checker/tests/immutability/DateCell3.java @@ -0,0 +1,21 @@ +import org.checkerframework.checker.immutability.qual.Immutable; +import org.checkerframework.checker.immutability.qual.Mutable; +import org.checkerframework.checker.immutability.qual.PolyMutable; +import org.checkerframework.checker.immutability.qual.Readonly; + +import java.util.Date; + +public class DateCell3 { + + /*This poly return type can be instantiated according to assignment context now*/ + @PolyMutable + Date getPolyMutableDate(@Readonly DateCell3 this) { + return new @PolyMutable Date(); + } + + /*Allowed in new PICO now*/ + void testGetPolyImmutableDate(@Readonly DateCell3 this) { + @Mutable Date md = this.getPolyMutableDate(); + @Immutable Date imd = this.getPolyMutableDate(); + } +} diff --git a/checker/tests/immutability/DeepMutable.java b/checker/tests/immutability/DeepMutable.java new file mode 100644 index 00000000000..e31f2695542 --- /dev/null +++ b/checker/tests/immutability/DeepMutable.java @@ -0,0 +1,38 @@ +import org.checkerframework.checker.immutability.qual.Immutable; +import org.checkerframework.checker.immutability.qual.Mutable; + +public class DeepMutable { + @Mutable + static class MutableBox {} + + @Immutable + static class ImmutableClass { + + // :: error: (implicit.shallow.immutable) + MutableBox implicit = new MutableBox(); + + @Mutable MutableBox explicit = new MutableBox(); + } + + @Immutable + static class ImmutableGenericEx { + + T t; + + @Immutable + ImmutableGenericEx(T t) { + this.t = t; + } + } + + @Immutable + static class ImmutableGenericIm { + // :: error: (implicit.shallow.immutable) + T t; + + @Immutable + ImmutableGenericIm(T t) { + this.t = t; + } + } +} diff --git a/checker/tests/immutability/DeepRDMDefaultForInstanceField.java b/checker/tests/immutability/DeepRDMDefaultForInstanceField.java new file mode 100644 index 00000000000..609bd2c6444 --- /dev/null +++ b/checker/tests/immutability/DeepRDMDefaultForInstanceField.java @@ -0,0 +1,11 @@ +import org.checkerframework.checker.immutability.qual.Immutable; + +@Immutable +public class DeepRDMDefaultForInstanceField { + // Should have error. Array component should also be defaulted + // to @ReceiverDependantMutable. + // Update: maybe don't need deep default. It's not possible to + // let every field to be in abstract state just by defaulting. + // :: error: (assignment.type.incompatible) + Object[] o = new String @Immutable [] {""}; +} diff --git a/checker/tests/immutability/DeepStaticRDMForbidden.java b/checker/tests/immutability/DeepStaticRDMForbidden.java new file mode 100644 index 00000000000..4a229149e11 --- /dev/null +++ b/checker/tests/immutability/DeepStaticRDMForbidden.java @@ -0,0 +1,10 @@ +import org.checkerframework.checker.immutability.qual.ReceiverDependantMutable; + +public class DeepStaticRDMForbidden { + + // :: error: (static.receiverdependantmutable.forbidden) + static void foo(T p) { + // :: error: (static.receiverdependantmutable.forbidden) + p = null; + } +} diff --git a/checker/tests/immutability/DefaultRDMElementUsageToMutable.java b/checker/tests/immutability/DefaultRDMElementUsageToMutable.java new file mode 100644 index 00000000000..0719f54bf82 --- /dev/null +++ b/checker/tests/immutability/DefaultRDMElementUsageToMutable.java @@ -0,0 +1,27 @@ +import org.checkerframework.checker.immutability.qual.Immutable; +import org.checkerframework.checker.immutability.qual.Mutable; + +import java.util.ArrayList; +import java.util.List; + +class A { + A() {} +} + +@Immutable +class B { + @Immutable + B() {} +} + +public class DefaultRDMElementUsageToMutable { + void foo() { + // ArrayList didn't inherit with @ReceiverDependantMutable + // and is defaulted to @Mutable + @Mutable List list = new ArrayList<>(); + // @Mutable type element gets inheritted @Mutable + @Mutable A a = new A(); + // @Immutable type element gets inheritted @Immutable + @Immutable B b = new B(); + } +} diff --git a/checker/tests/immutability/DiamondTreeProblem.java b/checker/tests/immutability/DiamondTreeProblem.java new file mode 100644 index 00000000000..cc77739733e --- /dev/null +++ b/checker/tests/immutability/DiamondTreeProblem.java @@ -0,0 +1,15 @@ +import org.checkerframework.checker.immutability.qual.Immutable; + +import java.util.ArrayList; +import java.util.List; + +public class DiamondTreeProblem { + + void test1() { + @Immutable List l = new @Immutable ArrayList<>(); + } + + void test2() { + @Immutable List l = new @Immutable ArrayList(); + } +} diff --git a/checker/tests/immutability/EnumConstantNotAlwaysMutable.java b/checker/tests/immutability/EnumConstantNotAlwaysMutable.java new file mode 100644 index 00000000000..8320ab25869 --- /dev/null +++ b/checker/tests/immutability/EnumConstantNotAlwaysMutable.java @@ -0,0 +1,29 @@ +import org.checkerframework.checker.immutability.qual.Immutable; +import org.checkerframework.checker.immutability.qual.Mutable; +import org.checkerframework.checker.immutability.qual.Readonly; +import org.checkerframework.checker.immutability.qual.ReceiverDependantMutable; + +// It's equivalent to having @Immutable on every enum type +enum Kind { + SOME; // Enum constant is also @Immutable +} + +public class EnumConstantNotAlwaysMutable { + + // Shouldn't get warning. Implicitly applied @Immutable + Kind defKind; + // Enum is implicitly @Immutable, so using explicit @Immutable is allowed + @Immutable Kind kind; + // :: error: (type.invalid.annotations.on.use) + @ReceiverDependantMutable Kind invalidKind; + // :: error: (type.invalid.annotations.on.use) + @Mutable Kind invalidKind2; + // no error now + @Readonly Kind invalidKind3; + + // :: error: (initialization.fields.uninitialized) + EnumConstantNotAlwaysMutable() { + // Kind.SOME should be @Immutable + kind = Kind.SOME; + } +} diff --git a/checker/tests/immutability/EnumTests.java b/checker/tests/immutability/EnumTests.java new file mode 100644 index 00000000000..4dc41bdb93a --- /dev/null +++ b/checker/tests/immutability/EnumTests.java @@ -0,0 +1,27 @@ +import org.checkerframework.checker.immutability.qual.Immutable; +import org.checkerframework.checker.immutability.qual.Mutable; + +// Enum is now only immutable by default, not implicit +public class EnumTests { + void foo(/*immutable*/ MyEnum e) { + // :: error: (type.invalid.annotations.on.use) + @Mutable MyEnum mutableRef; + @Immutable MyEnum immutableRef = e; + + @Mutable MutableEnum mutEnumMutRef = MutableEnum.M1; + // :: error: (type.invalid.annotations.on.use) + @Immutable MutableEnum mutEnumImmRef; + } + + /*immutable*/ + private static enum MyEnum { + T1, + T2; + } + + @Mutable + private static enum MutableEnum { + M1, + M2; + } +} diff --git a/checker/tests/immutability/FbcViolatingMethod.java b/checker/tests/immutability/FbcViolatingMethod.java new file mode 100644 index 00000000000..c86c6d3a86d --- /dev/null +++ b/checker/tests/immutability/FbcViolatingMethod.java @@ -0,0 +1,8 @@ +public class FbcViolatingMethod { + FbcViolatingMethod() { + // :: error: (method.invocation.invalid) + foo(); + } + + void foo(FbcViolatingMethod this) {} +} diff --git a/checker/tests/immutability/FieldAssignment.java b/checker/tests/immutability/FieldAssignment.java new file mode 100644 index 00000000000..0de60ac5e74 --- /dev/null +++ b/checker/tests/immutability/FieldAssignment.java @@ -0,0 +1,34 @@ +import org.checkerframework.checker.immutability.qual.Immutable; +import org.checkerframework.checker.immutability.qual.Mutable; +import org.checkerframework.checker.immutability.qual.ReceiverDependantMutable; +import org.checkerframework.checker.initialization.qual.UnderInitialization; + +@ReceiverDependantMutable +public class FieldAssignment { + + @ReceiverDependantMutable Object f; + + void setFWithMutableReceiver( + @UnderInitialization @Mutable FieldAssignment this, @Mutable Object o) { + this.f = new @Mutable Object(); + } + + // TODO This is not specific to PICO type system. InitializationVisitor currently has this issue + // of false positively + // wanrning uninitialized fields when we use instance method to initialiaze fields + public FieldAssignment() { + // :: error: (method.invocation.invalid) + setFWithMutableReceiver(new @Mutable Object()); + } + + void setFWithReceiverDependantMutableReceiver( + @ReceiverDependantMutable FieldAssignment this, @ReceiverDependantMutable Object pimo) { + // :: error: (illegal.field.write) + this.f = pimo; + } + + void setFWithImmutableReceiver(@Immutable FieldAssignment this, @Immutable Object imo) { + // :: error: (illegal.field.write) + this.f = imo; + } +} diff --git a/checker/tests/immutability/FieldsInitialized.java b/checker/tests/immutability/FieldsInitialized.java new file mode 100644 index 00000000000..ee1b08f41ed --- /dev/null +++ b/checker/tests/immutability/FieldsInitialized.java @@ -0,0 +1,31 @@ +import org.checkerframework.checker.immutability.qual.Assignable; +import org.checkerframework.checker.immutability.qual.Immutable; +import org.checkerframework.checker.immutability.qual.Mutable; +import org.checkerframework.checker.immutability.qual.Readonly; +import org.checkerframework.checker.immutability.qual.ReceiverDependantMutable; + +@ReceiverDependantMutable +public class FieldsInitialized { + final @Immutable Object f1; + @Immutable Object f2; + final @ReceiverDependantMutable Object f3; + @ReceiverDependantMutable Object f4; + + @Mutable Object f5; + @Readonly Object f6; + @Assignable @Immutable Object f7; + @Assignable @ReceiverDependantMutable Object f8; + @Assignable @Mutable Object f9; + @Assignable @Readonly Object f10; + + // :: error: (initialization.fields.uninitialized) + @ReceiverDependantMutable + FieldsInitialized() { + f1 = new @Immutable Object(); + f2 = new @Immutable Object(); + f3 = new @ReceiverDependantMutable Object(); + f4 = new @ReceiverDependantMutable Object(); + f5 = new @Mutable Object(); + f6 = new @Immutable Object(); + } +} diff --git a/checker/tests/immutability/ForbidAssignmentCase.java b/checker/tests/immutability/ForbidAssignmentCase.java new file mode 100644 index 00000000000..692247049d1 --- /dev/null +++ b/checker/tests/immutability/ForbidAssignmentCase.java @@ -0,0 +1,71 @@ +import org.checkerframework.checker.immutability.qual.Assignable; +import org.checkerframework.checker.immutability.qual.Immutable; +import org.checkerframework.checker.immutability.qual.Mutable; +import org.checkerframework.checker.immutability.qual.Readonly; +import org.checkerframework.checker.immutability.qual.ReceiverDependantMutable; + +@ReceiverDependantMutable +public class ForbidAssignmentCase { + @Assignable @ReceiverDependantMutable Object f; + + @ReceiverDependantMutable + ForbidAssignmentCase() { + f = new @ReceiverDependantMutable Object(); + } + + // Allowing assignment through @Readonly receiver to @Assignable @ReceiverDependantMutable + // in either way causes errors. So I would forbid this combination in assignment. + // Though we still allow reading this field by @Readonly receiver + static void forbid(@Readonly ForbidAssignmentCase ro, @Mutable ForbidAssignmentCase mo) { + // :: error: (illegal.field.write) + ro.f = new @Immutable Object(); // cannot exclude f out of the abstract state! + ro = mo; // ro.f will be mutable now, and we can use this reference to mutate an + // immutable object + @Readonly Object o = ro.f; // allow reads + } + + // Below are different cases. Because dataflow refinement refines @Readonly to concrete type, + // so all the below don't hit the forbidden case + static void ImmutableObjectCaptureMutableObject() { + @Immutable ForbidAssignmentCase imo = new @Immutable ForbidAssignmentCase(); + @Readonly ForbidAssignmentCase ro = imo; + // @Immutable object captures @Mutable object + // :: error: (assignment.type.incompatible) + ro.f = new @Mutable Object(); + // victim is no longer @Immutable object any more. + @Immutable Object victim = imo.f; + + // But allow below: + ro.f = new @Immutable Object(); + } + + static void ImmutableObjectGetMutableAlias() { + @Mutable ForbidAssignmentCase mo = new @Mutable ForbidAssignmentCase(); + @Readonly ForbidAssignmentCase ro = mo; + // :: error: (assignment.type.incompatible) + ro.f = new @Immutable Object(); + // @Immutable object pointed by field f gets @Mutable alias + @Mutable Object mutableAliasToImmutableObject = mo.f; + + // But allow below: + ro.f = new @Mutable Object(); + } + + static @Mutable Object getMutableAliasForReadonlyArgument(@Readonly Object p) { + @Mutable ForbidAssignmentCase mo = new @Mutable ForbidAssignmentCase(); + @Readonly ForbidAssignmentCase ro = mo; + // :: error: (assignment.type.incompatible) + ro.f = p; + // Got a mutable alias for @Readonly p + return mo.f; + } + + static @Immutable Object getImmutableAliasForReadonlyArgument(@Readonly Object p) { + @Immutable ForbidAssignmentCase imo = new @Immutable ForbidAssignmentCase(); + @Readonly ForbidAssignmentCase ro = imo; + // :: error: (assignment.type.incompatible) + ro.f = p; + // Got an immutable alias for @Readonly p + return imo.f; + } +} diff --git a/checker/tests/immutability/Generic.java b/checker/tests/immutability/Generic.java new file mode 100644 index 00000000000..a1ef2b5c8f7 --- /dev/null +++ b/checker/tests/immutability/Generic.java @@ -0,0 +1,36 @@ +import org.checkerframework.checker.immutability.qual.Immutable; +import org.checkerframework.checker.immutability.qual.Mutable; +import org.checkerframework.checker.immutability.qual.Readonly; + +import java.util.Date; + +/*If upper bound is @Readonly, @Mutable, type parameter is not in the abstract state of + * the entire object*/ +@Immutable +class Wrapper { + T t; + + @Immutable + Wrapper(T t) { + this.t = t; + } +} + +public class Generic { + void test1() { + @Mutable Object arg = new @Mutable Object(); + @Immutable Wrapper<@Mutable Object> wrapper = new @Immutable Wrapper<@Mutable Object>(arg); + /*Since t is not in the abstract state, we can get a mutable object out of an immutable + object. This is just like we have mutable elements in immutable list, those mutable + elements are not in the abstract state of the list*/ + @Mutable Object mo = wrapper.t; + } + + void test2() { + @Mutable Date md = new @Mutable Date(); + @Readonly Date spy = md; + @Immutable Wrapper<@Readonly Date> victim = new @Immutable Wrapper<@Readonly Date>(spy); + /*Same argument as above*/ + md.setTime(123L); + } +} diff --git a/checker/tests/immutability/GenericInterfaces.java b/checker/tests/immutability/GenericInterfaces.java new file mode 100644 index 00000000000..545248d3afe --- /dev/null +++ b/checker/tests/immutability/GenericInterfaces.java @@ -0,0 +1,19 @@ +import org.checkerframework.checker.immutability.qual.Immutable; +import org.checkerframework.checker.immutability.qual.Mutable; +import org.checkerframework.checker.immutability.qual.Readonly; + +interface MIt { + E next(); +} + +class GenericInterfaces { + + void raw() { + @Mutable MIt raw = null; + + // Using optimictic uninferred type arguments, so it is + // allowed + // :: error: (assignment.type.incompatible) + @Immutable Object ro = raw.next(); + } +} diff --git a/checker/tests/immutability/HashCodeSafetyExample.java b/checker/tests/immutability/HashCodeSafetyExample.java new file mode 100644 index 00000000000..19f8281e26d --- /dev/null +++ b/checker/tests/immutability/HashCodeSafetyExample.java @@ -0,0 +1,42 @@ +import org.checkerframework.checker.immutability.qual.Immutable; + +import java.util.HashMap; +import java.util.Map; + +@Immutable +class A { + boolean isIn = false; + + @Override + public int hashCode() { + return isIn ? 1 : 0; + } + + @Override + public boolean equals(Object obj) { + // No cast.unsafe + return isIn == ((A) obj).isIn; + } +} + +public class HashCodeSafetyExample { + public static void main(String[] args) { + A a = new A(); + HashMap m = new HashMap<>(); + m.put(a, "hello"); + System.out.println("HashCode before: " + a.hashCode()); + // :: error: (illegal.field.write) + a.isIn = true; + System.out.println("HashCode after: " + a.hashCode()); + System.out.println(m.get(a)); + m.put(new A(), "WORLD"); + System.out.println("Iterating entries:"); + // Even though using object a whose hashcode is mutated returns null, + // iterating over entryset did return correct mapping between keys and values, + // which is strange and looks like inconsistency. + for (Map.Entry entry : m.entrySet()) { + System.out.println("key: " + entry.getKey()); + System.out.println("value: " + entry.getValue()); + } + } +} diff --git a/checker/tests/immutability/ImmutabilityFactoryPattern.java b/checker/tests/immutability/ImmutabilityFactoryPattern.java new file mode 100644 index 00000000000..bdba9e7cdde --- /dev/null +++ b/checker/tests/immutability/ImmutabilityFactoryPattern.java @@ -0,0 +1,21 @@ +import org.checkerframework.checker.immutability.qual.Immutable; +import org.checkerframework.checker.immutability.qual.Mutable; +import org.checkerframework.checker.immutability.qual.PolyMutable; +import org.checkerframework.checker.immutability.qual.Readonly; + +@Immutable +public class ImmutabilityFactoryPattern { + public @Immutable ImmutabilityFactoryPattern() {} + + @PolyMutable + Object createObject(@Readonly ImmutabilityFactoryPattern this) { + return new @PolyMutable Object(); + } + + static void test() { + @Immutable ImmutabilityFactoryPattern factory = new @Immutable ImmutabilityFactoryPattern(); + // Both typecheck in new PICO + @Mutable Object mo = factory.createObject(); + @Immutable Object imo = factory.createObject(); + } +} diff --git a/checker/tests/immutability/ImmutableClass1.java b/checker/tests/immutability/ImmutableClass1.java new file mode 100644 index 00000000000..9f63f93c1bd --- /dev/null +++ b/checker/tests/immutability/ImmutableClass1.java @@ -0,0 +1,34 @@ +import org.checkerframework.checker.immutability.qual.Immutable; +import org.checkerframework.checker.immutability.qual.Mutable; +import org.checkerframework.checker.immutability.qual.PolyMutable; +import org.checkerframework.checker.immutability.qual.Readonly; +import org.checkerframework.checker.immutability.qual.ReceiverDependantMutable; + +@Immutable +class ImmutableClass1 { + // :: error: (type.invalid.annotations.on.use) + @Mutable + ImmutableClass1(Object o) {} + + // :: error: (type.invalid.annotations.on.use) + @ReceiverDependantMutable + ImmutableClass1() {} + + @Immutable + ImmutableClass1(@Immutable Number n) {} + + void method1(@Readonly ImmutableClass1 this) {} + + void method2(@Immutable ImmutableClass1 this) {} + + // :: error: (type.invalid.annotations.on.use) :: error: (method.receiver.incompatible) + void method3(@ReceiverDependantMutable ImmutableClass1 this) {} + + void method4(@PolyMutable ImmutableClass1 this) {} + + // :: error: (method.receiver.incompatible) :: error: (type.invalid.annotations.on.use) + void method5(@Mutable ImmutableClass1 this) {} + + // when not annotated explictly, default annotations of is inherited from declaration + void method6(ImmutableClass1 this) {} +} diff --git a/checker/tests/immutability/ImmutableClass2.java b/checker/tests/immutability/ImmutableClass2.java new file mode 100644 index 00000000000..678f3129c8d --- /dev/null +++ b/checker/tests/immutability/ImmutableClass2.java @@ -0,0 +1,50 @@ +import org.checkerframework.checker.immutability.qual.Immutable; +import org.checkerframework.checker.immutability.qual.Mutable; +import org.checkerframework.checker.immutability.qual.Readonly; +import org.checkerframework.checker.immutability.qual.ReceiverDependantMutable; + +// ok +@Immutable +class ImmutableClass2 { + @Immutable + ImmutableClass2() {} +} + +// ok +@Immutable +class ImmutableClass3 { + @Immutable + ImmutableClass3() {} +} + +// ok +@Immutable +class ImmutableClass4 { + @Immutable + ImmutableClass4() {} +} + +// ok +@Immutable +class ImmutableClass5 { + @Immutable + ImmutableClass5() {} +} + +@Immutable +class ImmutableClass6 { + @Immutable + ImmutableClass6() {} +} + +@Immutable +class ImmutableClass7 { + @Immutable + ImmutableClass7() {} + + // Should NOT have warnings for type parameter with non-immutable upper bound + // if the type parameter is declared on generic method(?) + S foo(@Immutable ImmutableClass7 this) { + return null; + } +} diff --git a/checker/tests/immutability/ImmutableClass3.java b/checker/tests/immutability/ImmutableClass3.java new file mode 100644 index 00000000000..c6602f895fe --- /dev/null +++ b/checker/tests/immutability/ImmutableClass3.java @@ -0,0 +1,31 @@ +import org.checkerframework.checker.immutability.qual.Assignable; +import org.checkerframework.checker.immutability.qual.Immutable; +import org.checkerframework.checker.immutability.qual.Mutable; +import org.checkerframework.checker.immutability.qual.Readonly; + +@Immutable +class A { + @Assignable T t; + + @Immutable + A(T t) { + this.t = t; + } +} + +@Immutable +class ImmutableClass3 extends A<@Mutable Object> { + @Immutable + ImmutableClass3() { + super(new @Mutable Object()); + } + + void foo(@Immutable ImmutableClass3 this) { + /*This is acceptable. t is not in the abstract state of + the entire object because T has upper bound @Readonly*/ + @Mutable Object mo = this.t; + // Be default, we can't assign to t; But with the assignability dimension, + // we can do that now by annotating @Assignable to t + this.t = new @Mutable Object(); + } +} diff --git a/checker/tests/immutability/ImmutableConstructor.java b/checker/tests/immutability/ImmutableConstructor.java new file mode 100644 index 00000000000..b7c8af0827c --- /dev/null +++ b/checker/tests/immutability/ImmutableConstructor.java @@ -0,0 +1,55 @@ +import org.checkerframework.checker.immutability.qual.Immutable; +import org.checkerframework.checker.immutability.qual.Mutable; +import org.checkerframework.checker.immutability.qual.Readonly; +import org.checkerframework.checker.immutability.qual.ReceiverDependantMutable; + +@Immutable +public class ImmutableConstructor { + + @Readonly Object rof; + @ReceiverDependantMutable Object pif; + @Immutable Object imf; + + // :: error: (initialization.fields.uninitialized) + @Immutable + ImmutableConstructor( + @Mutable Object mo, @ReceiverDependantMutable Object po, @Immutable Object io) {} + + // Even if the first argument is @ReceiverDependantMutable, aliased @Mutable object cannot be + // captured by pif, + // because @Immutable constructor return type only allows @Immutable object to be captured after + // viewpoint adaptation. So it's still safe to have @ReceiverDependantMutable arguemnt in + // immutable constructor + @Immutable + ImmutableConstructor(@ReceiverDependantMutable Object po, @Immutable Object io) { + this.rof = po; + this.rof = io; + + this.pif = io; + // :: error: (assignment.type.incompatible) + this.pif = po; + + this.imf = io; + // :: error: (assignment.type.incompatible) + this.imf = po; + } + + void invokeConstructor( + @Readonly ImmutableConstructor this, + @Readonly Object ro, + @Mutable Object mo, + @ReceiverDependantMutable Object po, + @Immutable Object io) { + new @Immutable ImmutableConstructor(io, io); + + // :: error: (constructor.invocation.invalid) + new @Mutable ImmutableConstructor(mo, io); + + // constructor.invocation.invalid propgates before annotation invalid messages and stops + // :: error: (constructor.invocation.invalid) + new @ReceiverDependantMutable ImmutableConstructor(po, io); + + // :: error: (constructor.invocation.invalid) :: error: (pico.new.invalid) + new @Readonly ImmutableConstructor(ro, io); + } +} diff --git a/checker/tests/immutability/ImmutableDefaultConstructor.java b/checker/tests/immutability/ImmutableDefaultConstructor.java new file mode 100644 index 00000000000..e4b80b9e0b8 --- /dev/null +++ b/checker/tests/immutability/ImmutableDefaultConstructor.java @@ -0,0 +1,9 @@ +import org.checkerframework.checker.immutability.qual.Immutable; + +@Immutable +public class ImmutableDefaultConstructor { + static void foo() { + // Main type of "new" is also inheritted "@Immutable" + @Immutable ImmutableDefaultConstructor l = new ImmutableDefaultConstructor(); + } +} diff --git a/checker/tests/immutability/ImmutableListImmutableElement.java b/checker/tests/immutability/ImmutableListImmutableElement.java new file mode 100644 index 00000000000..efab009b8d8 --- /dev/null +++ b/checker/tests/immutability/ImmutableListImmutableElement.java @@ -0,0 +1,29 @@ +import org.checkerframework.checker.immutability.qual.Immutable; + +import java.util.ArrayList; +import java.util.List; + +@Immutable +class A { + int i; + + @Immutable + A(int i) { + this.i = i; + } +} + +public class ImmutableListImmutableElement { + public static void main(String[] args) { + List l1 = new ArrayList(); + l1.add(new A(0)); + // Wraps in immutable list with the same objects as l1. + // Cannot add/remove elements from immutable list. + // Modifying stored element itself is another question + List l2 = new @Immutable ArrayList(l1); + // :: error: (method.invocation.invalid) + l2.add(new A(1)); + // :: error: (illegal.field.write) + l2.get(0).i = 2; + } +} diff --git a/checker/tests/immutability/ImmutableListProblem.java b/checker/tests/immutability/ImmutableListProblem.java new file mode 100644 index 00000000000..f098f6b8f14 --- /dev/null +++ b/checker/tests/immutability/ImmutableListProblem.java @@ -0,0 +1,64 @@ +import org.checkerframework.checker.immutability.qual.Immutable; +import org.checkerframework.checker.immutability.qual.Readonly; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +// The situations of creating immutable lists, sets, hashmaps are similar. +// If they are called add/put operations, then they can't be immutable. So, +// it's important to initialize their contents when calling constructors. +// One general solution is to create local mutable lists, sets or hashmaps, +// add contents into them and pass it to a wrapper object that is immutable. +// See ImmutableListProblem(Object o1, Object o2, Object o3) for example. +@Immutable +public class ImmutableListProblem { + + List list; + + @Immutable + ImmutableListProblem() { + list = new @Immutable ArrayList(); + // :: error: (method.invocation.invalid) + list.add("hi"); // Any add() operation after list is constructed forbids the list to be + // immutable + } + + @Immutable + ImmutableListProblem(Object o1) { + // One way to construct and immutable list is to pass the contents to the constructor + list = new @Immutable ArrayList(Arrays.asList("hi")); + } + + @Immutable + ImmutableListProblem(Object o1, Object o2) { + // Another way is to use Arrays.asList() + list = Arrays.asList("hi"); + } + + @Immutable + ImmutableListProblem(Object o1, Object o2, Object o3) { + List localList = new ArrayList(); + localList.add("hi"); + localList.add("how"); + localList.add("are"); + localList.add("you"); + // Third way is to create a local mutable list, and wrap it with the immutable list but has + // the same + // content as the mutable list + list = new @Immutable ArrayList(localList); + } + + @Immutable + List createImmutableList(@Readonly ImmutableListProblem this) { + List localList = new ArrayList(); + localList.add("hi"); + localList.add("how"); + localList.add("are"); + localList.add("you"); + // After initializing, forcably cast @Mutable list into @Immutable as long as no @Mutable + // aliases exist + // :: warning: (cast.unsafe) + return (@Immutable List) localList; + } +} diff --git a/checker/tests/immutability/ImmutablePerson.java b/checker/tests/immutability/ImmutablePerson.java new file mode 100644 index 00000000000..1af6e749970 --- /dev/null +++ b/checker/tests/immutability/ImmutablePerson.java @@ -0,0 +1,46 @@ +import org.checkerframework.checker.immutability.qual.*; + +import java.util.*; + +class ImmutablePerson { + + void test(@Immutable List friends, @Immutable List otherFriends) { + @Immutable Person p = new @Immutable Person("name", 25, friends); + + // :: error: (method.invocation.invalid) + p.setName("NewName"); + + // :: error: (illegal.field.write) + p.name = "NewName"; + + // :: error: (illegal.field.write) + p.friends = otherFriends; + + // :: error: (method.invocation.invalid) + p.friends.add("NewFriend"); + + // :: error: (method.invocation.invalid) + p.getFriends().add("NewFriend"); + } +} + +@ReceiverDependantMutable +class Person { + String name; + int age; + List friends; + + public Person(String name, int age, @ReceiverDependantMutable List friends) { + this.name = name; + this.age = age; + this.friends = friends; + } + + public void setName(String name) { + this.name = name; + } + + public List getFriends() { + return friends; + } +} diff --git a/checker/tests/immutability/ImplicitAppliesToMethodReceiver.java b/checker/tests/immutability/ImplicitAppliesToMethodReceiver.java new file mode 100644 index 00000000000..a529529ee8b --- /dev/null +++ b/checker/tests/immutability/ImplicitAppliesToMethodReceiver.java @@ -0,0 +1,5 @@ +public class ImplicitAppliesToMethodReceiver { + void foo() { + double delta = Double.valueOf(1.0); + } +} diff --git a/checker/tests/immutability/IncompatibleCast.java b/checker/tests/immutability/IncompatibleCast.java new file mode 100644 index 00000000000..5514dcf0174 --- /dev/null +++ b/checker/tests/immutability/IncompatibleCast.java @@ -0,0 +1,15 @@ +import org.checkerframework.checker.immutability.qual.Immutable; +import org.checkerframework.checker.immutability.qual.Mutable; + +public class IncompatibleCast { + void foo() { + @Mutable Object p = new Object(); + // call mutating method on p // + + // If only reference doesn't leak, accept casting to @Immutable so that clients + // can have an @Immutable object. This is to allow initializing objects and then + // once finished, cast it to @Immutable and nobody has alias to it. + // :: warning: (cast.unsafe) + Object o = (@Immutable Object) p; + } +} diff --git a/checker/tests/immutability/InitializationBlockProblem.java b/checker/tests/immutability/InitializationBlockProblem.java new file mode 100644 index 00000000000..e7b460bbf43 --- /dev/null +++ b/checker/tests/immutability/InitializationBlockProblem.java @@ -0,0 +1,13 @@ +import org.checkerframework.checker.immutability.qual.Immutable; +import org.checkerframework.checker.immutability.qual.Mutable; +import org.checkerframework.checker.immutability.qual.ReceiverDependantMutable; + +class InitializationBlockProblem { + @ReceiverDependantMutable Object o; + + { + this.o = new @Mutable Object(); + // :: error: (assignment.type.incompatible) + this.o = new @Immutable Object(); + } +} diff --git a/checker/tests/immutability/InvalidAssignability.java b/checker/tests/immutability/InvalidAssignability.java new file mode 100644 index 00000000000..e1bb8a80306 --- /dev/null +++ b/checker/tests/immutability/InvalidAssignability.java @@ -0,0 +1,17 @@ +import org.checkerframework.checker.immutability.qual.Assignable; +import org.checkerframework.checker.immutability.qual.Immutable; + +public class InvalidAssignability { + final @Immutable Object io = null; + // :: error: (initialization.field.uninitialized) + @Immutable Object io2; + // :: error: (initialization.field.uninitialized) + @Assignable @Immutable Object io3; + static final @Immutable Object io4 = null; + // :: error: (initialization.static.field.uninitialized) + static @Assignable @Immutable Object io5; + // :: error: (one.assignability.invalid) + final @Assignable @Immutable Object o = null; + // :: error: (one.assignability.invalid) + static final @Assignable @Immutable Object o2 = null; +} diff --git a/checker/tests/immutability/InvalidBound.java b/checker/tests/immutability/InvalidBound.java new file mode 100644 index 00000000000..f9f15da1877 --- /dev/null +++ b/checker/tests/immutability/InvalidBound.java @@ -0,0 +1,28 @@ +import org.checkerframework.checker.immutability.qual.Immutable; +import org.checkerframework.checker.immutability.qual.Mutable; +import org.checkerframework.checker.immutability.qual.PolyMutable; +import org.checkerframework.checker.immutability.qual.Readonly; +import org.checkerframework.checker.immutability.qual.ReceiverDependantMutable; + +// :: error: (class.bound.invalid) +@Readonly +public class InvalidBound {} + +// :: error: (class.bound.invalid) +@PolyMutable +class A {} + +// ok +@Immutable +class C { + @Immutable + C() {} +} + +// ok +@Mutable +class D {} + +// ok +@ReceiverDependantMutable +class E {} diff --git a/checker/tests/immutability/InvariantFieldInitialized.java b/checker/tests/immutability/InvariantFieldInitialized.java new file mode 100644 index 00000000000..39f62085aed --- /dev/null +++ b/checker/tests/immutability/InvariantFieldInitialized.java @@ -0,0 +1,23 @@ +import org.checkerframework.checker.immutability.qual.Immutable; +import org.checkerframework.checker.immutability.qual.Mutable; +import org.checkerframework.checker.immutability.qual.Readonly; +import org.checkerframework.checker.immutability.qual.ReceiverDependantMutable; + +@Immutable +public class InvariantFieldInitialized { + @Immutable Object o1; + @ReceiverDependantMutable Object o2; + // Below two lines still need initialization, as otherwise there won't + // be opportunity for them to be initialized after @Immutable object is + // constructed. Yes, but they are still outside abstract state. + @Mutable Object o3; + @Readonly Object o4; + + @Immutable + InvariantFieldInitialized() { + o1 = new @Immutable Object(); + o2 = new @Immutable Object(); + o3 = new @Mutable Object(); + o4 = new @Mutable Object(); + } +} diff --git a/checker/tests/immutability/LocalVariableReturned.java b/checker/tests/immutability/LocalVariableReturned.java new file mode 100644 index 00000000000..147b501a032 --- /dev/null +++ b/checker/tests/immutability/LocalVariableReturned.java @@ -0,0 +1,10 @@ +public class LocalVariableReturned { + + LocalVariableReturned foo(int a, int b) { + LocalVariableReturned t = null; + if (a == b) { + t = new LocalVariableReturned(); + } + return t; + } +} diff --git a/checker/tests/immutability/LocalVariableTypeVariable.java b/checker/tests/immutability/LocalVariableTypeVariable.java new file mode 100644 index 00000000000..b6bf4c664b2 --- /dev/null +++ b/checker/tests/immutability/LocalVariableTypeVariable.java @@ -0,0 +1,7 @@ +public class LocalVariableTypeVariable { + void foo() { + // I disable applying local variable defaults to type variable if the type variable is used + // on local variable + N f; + } +} diff --git a/checker/tests/immutability/LocalVariableUsedAsArgument.java b/checker/tests/immutability/LocalVariableUsedAsArgument.java new file mode 100644 index 00000000000..66b3a893bad --- /dev/null +++ b/checker/tests/immutability/LocalVariableUsedAsArgument.java @@ -0,0 +1,69 @@ +import java.util.concurrent.atomic.AtomicInteger; + +// Test case for issue 1727: https://github.com/typetools/checker-framework/issues/1727 + +// @skip-test until the issue is fixed + +class A { + A(B b) {} +} + +class B {} + +public class LocalVariableUsedAsArgument { + + private A a; + + private A foo() { + // Default type for local variable b is @UnknownInitialization @Readonly + B b; + // B b = null; Chaning to this line doesn't have errors + + // // Simplified version of testcase that has the same effect as the while loop + // below. + // if (true) { + // // Statically this is not guaranteed to be executed + // b = new B(); + // } + + // Similar structure in exp4j#Tokenizer + while (true) { + B op = getB(); + if (op == null) { + b = new B(); + break; + } else { + b = op; + break; + } + } + + a = new A(b); // here b is still @UnknownInitialization @Readonly. Why? Is this + // problem in CF generally? Nullness also has + // the s + return a; + } + + private B getB() { + return null; + } + + public static int ThrsafeIncrementSizeUpToLimit(AtomicInteger storagePointer, int limitValue) { + int resultValue; + while (true) { + resultValue = storagePointer.get(); + if (resultValue == limitValue) { + break; + } + // if (ThrsafeCompareExchange(storagePointer, resultValue, (resultValue + + // 1))) { + // break; + // } + } + return resultValue; + } + + // static boolean ThrsafeCompareExchange(AtomicInteger storagePointer, int val1, int val2) { + // return true; + // } +} diff --git a/checker/tests/immutability/MethodReceiverNotInhericClassBound.java b/checker/tests/immutability/MethodReceiverNotInhericClassBound.java new file mode 100644 index 00000000000..d461ea1394a --- /dev/null +++ b/checker/tests/immutability/MethodReceiverNotInhericClassBound.java @@ -0,0 +1,9 @@ +import org.checkerframework.checker.immutability.qual.Immutable; +import org.checkerframework.checker.immutability.qual.Mutable; + +@Immutable +public class MethodReceiverNotInhericClassBound { + + // :: error: (method.receiver.incompatible) :: error: (type.invalid.annotations.on.use) + void bar(@Mutable MethodReceiverNotInhericClassBound this) {} +} diff --git a/checker/tests/immutability/MutableConstructor.java b/checker/tests/immutability/MutableConstructor.java new file mode 100644 index 00000000000..d23983a147c --- /dev/null +++ b/checker/tests/immutability/MutableConstructor.java @@ -0,0 +1,44 @@ +import org.checkerframework.checker.immutability.qual.Immutable; +import org.checkerframework.checker.immutability.qual.Mutable; +import org.checkerframework.checker.immutability.qual.Readonly; +import org.checkerframework.checker.immutability.qual.ReceiverDependantMutable; + +public class MutableConstructor { + + @Readonly Object rof; + @ReceiverDependantMutable Object pif; + @Immutable Object imf; + + @Mutable + MutableConstructor( + @Mutable Object mo, @ReceiverDependantMutable Object po, @Immutable Object io) { + // "this" automatically has the same type as constructor return type. + this.rof = mo; + this.rof = po; + this.rof = io; + + this.pif = mo; + // :: error: (assignment.type.incompatible) + this.pif = po; + // :: error: (assignment.type.incompatible) + this.pif = io; + + // :: error: (assignment.type.incompatible) + this.imf = mo; + // :: error: (assignment.type.incompatible) + this.imf = po; + this.imf = io; + } + + void invokeConstructor( + @Mutable Object mo, @ReceiverDependantMutable Object po, @Immutable Object io) { + new @Mutable MutableConstructor(mo, mo, io); + // :: error: (argument.type.incompatible) + new @Mutable MutableConstructor(mo, po, io); + // The same argument as the one in ImmutableConstructor + // :: error: (constructor.invocation.invalid) + new @ReceiverDependantMutable MutableConstructor(mo, po, io); + // :: error: (constructor.invocation.invalid) + new @Immutable MutableConstructor(mo, io, io); + } +} diff --git a/checker/tests/immutability/MutableField.java b/checker/tests/immutability/MutableField.java new file mode 100644 index 00000000000..9102e8103a4 --- /dev/null +++ b/checker/tests/immutability/MutableField.java @@ -0,0 +1,12 @@ +import org.checkerframework.checker.immutability.qual.Mutable; + +public class MutableField { + + // Allow mutable field now + @Mutable Object f; + static @Mutable Object sf = new @Mutable Object(); + + MutableField() { + f = new @Mutable Object(); + } +} diff --git a/checker/tests/immutability/NewAnnonymousClass.java b/checker/tests/immutability/NewAnnonymousClass.java new file mode 100644 index 00000000000..ad29c2fb999 --- /dev/null +++ b/checker/tests/immutability/NewAnnonymousClass.java @@ -0,0 +1,28 @@ +import java.util.Date; + +import qual.Immutable; + +@Immutable +class A { + @Immutable + A() {} +} + +// @skip-test Until we can get bound annotation on the super type that an anonymous class +// inherits from +public class NewAnnonymousClass { + protected static A funLUDecompose() { + // Even though there is no explicit @Immutable annotation on new, new A() still gets + // @Immutable type. Is this expected behaviour? + return new A() { + Date d = new @Immutable Date(); + // Below are not allowed by Java. Will get "Illegal static declaration in inner + // class " + // static @ReceiverDependantMutable Object o; + // static { + // o = null; + // } + // static @ReceiverDependantMutable Object foo() {return null;} + }; + } +} diff --git a/checker/tests/immutability/NotEveryInstFieldDefaultToRDM.java b/checker/tests/immutability/NotEveryInstFieldDefaultToRDM.java new file mode 100644 index 00000000000..cddf9ce79dd --- /dev/null +++ b/checker/tests/immutability/NotEveryInstFieldDefaultToRDM.java @@ -0,0 +1,24 @@ +import org.checkerframework.checker.immutability.qual.Immutable; +import org.checkerframework.checker.immutability.qual.Mutable; +import org.checkerframework.checker.immutability.qual.ReceiverDependantMutable; + +@ReceiverDependantMutable +public class NotEveryInstFieldDefaultToRDM { + // :: error: (assignment.type.incompatible) + @ReceiverDependantMutable B b1 = new B(); + B b2 = new @ReceiverDependantMutable B(); + @Mutable C c = new @Mutable C(); + @Mutable D d = new @Mutable D(); + E e = new @Immutable E(); +} + +@ReceiverDependantMutable +class B {} + +class C {} + +@Mutable +class D {} + +@Immutable +class E {} diff --git a/checker/tests/immutability/ObjectIdentityMethodTest.java b/checker/tests/immutability/ObjectIdentityMethodTest.java new file mode 100644 index 00000000000..e2d538e5fc3 --- /dev/null +++ b/checker/tests/immutability/ObjectIdentityMethodTest.java @@ -0,0 +1,121 @@ +import org.checkerframework.checker.immutability.qual.*; + +@ReceiverDependantMutable +class A { + @Assignable @Mutable B b; + + @ReceiverDependantMutable + A() {} + + void bar(@Readonly A this) {} +} + +class B {} + +class Super { + void foo() {} +} + +public class ObjectIdentityMethodTest extends Super { + static A a0; + /*Fields NOT in abstract states*/ + @Assignable A a1; + @Mutable A a2; + @Readonly A a3; + /*Fields in abstract state*/ + A a4; // default is @RDA @RDM + @Immutable A a5; + final A a6; + final @Immutable A a7; + + // :: error: (initialization.fields.uninitialized) + ObjectIdentityMethodTest() { + a6 = new A(); + a7 = new @Immutable A(); + } + + @ObjectIdentityMethod + public void testFieldAcess() { + // :: warning: (object.identity.static.field.access.forbidden) :: warning: + // (object.identity.method.invocation.invalid) + a0.bar(); + // :: warning: (object.identity.field.access.invalid) :: warning: + // (object.identity.method.invocation.invalid) + a1.bar(); + // :: warning: (object.identity.field.access.invalid) :: warning: + // (object.identity.method.invocation.invalid) + a2.bar(); + // :: warning: (object.identity.field.access.invalid) :: warning: + // (object.identity.method.invocation.invalid) + a3.bar(); + // Transitively check bar() is object identity method => deep object identity check! + // :: warning: (object.identity.method.invocation.invalid) + a4.bar(); + // Transitively check b is in abstract state => deep object identity check! + // :: warning: (object.identity.field.access.invalid) + Object o = a4.b; + // :: warning: (object.identity.method.invocation.invalid) + a5.bar(); + // :: warning: (object.identity.method.invocation.invalid) + a6.bar(); + // :: warning: (object.identity.method.invocation.invalid) + a7.bar(); + + /*With explicit "this"*/ + // :: warning: (object.identity.field.access.invalid) :: warning: + // (object.identity.method.invocation.invalid) + this.a1.bar(); + // :: warning: (object.identity.field.access.invalid) :: warning: + // (object.identity.method.invocation.invalid) + this.a2.bar(); + // :: warning: (object.identity.field.access.invalid) :: warning: + // (object.identity.method.invocation.invalid) + this.a3.bar(); + // :: warning: (object.identity.method.invocation.invalid) + this.a4.bar(); + // Similar argument for deep object identity check as above + // :: warning: (object.identity.field.access.invalid) + Object o2 = this.a4.b; + // :: warning: (object.identity.method.invocation.invalid) + this.a5.bar(); + // :: warning: (object.identity.method.invocation.invalid) + this.a6.bar(); + // :: warning: (object.identity.method.invocation.invalid) + this.a7.bar(); + } + + @ObjectIdentityMethod + void testMethodInvocation(ObjectIdentityMethodTest p, A a) { + // :: warning: (object.identity.method.invocation.invalid) + foo(); + // :: warning: (object.identity.method.invocation.invalid) + this.foo(); + // :: warning: (object.identity.method.invocation.invalid) + super.foo(); + // TODO Should these two method invocations also be checked? It's not trivial to only check + // method invocations on the transitively reachable objects from field. Right now, they are + // also checked + // :: warning: (object.identity.method.invocation.invalid) + p.foo(); + // :: warning: (object.identity.method.invocation.invalid) + a.bar(); + } + + void foo() {} + + @Override + public int hashCode() { + int result = super.hashCode(); + // :: warning: (object.identity.field.access.invalid) + result += a1.hashCode(); + // :: warning: (object.identity.field.access.invalid) + result += a2.hashCode(); + // :: warning: (object.identity.field.access.invalid) + result += a3.hashCode(); + result += a4.hashCode(); + result += a5.hashCode(); + result += a6.hashCode(); + result += a7.hashCode(); + return result; + } +} diff --git a/checker/tests/immutability/ObjectMethods.java b/checker/tests/immutability/ObjectMethods.java new file mode 100644 index 00000000000..5a813c1fc5b --- /dev/null +++ b/checker/tests/immutability/ObjectMethods.java @@ -0,0 +1,164 @@ +import org.checkerframework.checker.immutability.qual.Immutable; +import org.checkerframework.checker.immutability.qual.Mutable; +import org.checkerframework.checker.immutability.qual.Readonly; +import org.checkerframework.checker.immutability.qual.ReceiverDependantMutable; + +public class ObjectMethods { + // Don't have any warnings now + @Override + public int hashCode() { + return super.hashCode(); + } + + @Override + public boolean equals(Object o) { + return super.equals(o); + } + + @Override + protected Object clone() throws CloneNotSupportedException { + return super.clone(); + } + + @Override + public String toString() { + return super.toString(); + } +} + +@Immutable +class ObjectMethods2 { + + @Immutable + ObjectMethods2() {} + + @Override + public int hashCode() { + return super.hashCode(); + } + + @Override + public boolean equals(Object o) { + return super.equals(o); + } + + @Override + protected @Immutable Object clone(@Immutable ObjectMethods2 this) + throws CloneNotSupportedException { + return super.clone(); + } + + @Override + public String toString() { + return super.toString(); + } +} + +@ReceiverDependantMutable +class ObjectMethods3 { + + @ReceiverDependantMutable + ObjectMethods3() {} + + @Override + public int hashCode() { + return super.hashCode(); + } + + @Override + public boolean equals(Object o) { + return super.equals(o); + } + + @Override + protected @ReceiverDependantMutable Object clone(@ReceiverDependantMutable ObjectMethods3 this) + throws CloneNotSupportedException { + return super.clone(); + } + + @Override + public String toString() { + return super.toString(); + } +} + +class ObjectMethods4 { + @Override + public int hashCode(@Readonly ObjectMethods4 this) { + return super.hashCode(); + } + + @Override + public boolean equals(@Readonly ObjectMethods4 this, @Readonly Object o) { + return super.equals(o); + } + + @Override + protected Object clone(@Readonly ObjectMethods4 this) throws CloneNotSupportedException { + // :: warning: (cast.unsafe) + return (@Mutable Object) super.clone(); + } + + @Override + public String toString(@Readonly ObjectMethods4 this) { + return super.toString(); + } +} + +@Immutable +class ObjectMethods5 { + + @Immutable + ObjectMethods5() {} + + @Override + public int hashCode(@Readonly ObjectMethods5 this) { + return super.hashCode(); + } + + @Override + public boolean equals(@Readonly ObjectMethods5 this, @Readonly Object o) { + return super.equals(o); + } + + @Override + protected @Immutable Object clone(@Readonly ObjectMethods5 this) + throws CloneNotSupportedException { + // :: warning: (cast.unsafe) + return (@Immutable Object) super.clone(); + } + + @Override + public String toString(@Readonly ObjectMethods5 this) { + return super.toString(); + } +} + +@ReceiverDependantMutable +class ObjectMethods6 { + + @Immutable + ObjectMethods6() {} + + @Override + public int hashCode(@Readonly ObjectMethods6 this) { + return super.hashCode(); + } + + @Override + public boolean equals(@Readonly ObjectMethods6 this, @Readonly Object o) { + return super.equals(o); + } + + @Override + protected @ReceiverDependantMutable Object clone(@Readonly ObjectMethods6 this) + throws CloneNotSupportedException { + // No cast.unsafe + return (@ReceiverDependantMutable Object) super.clone(); + } + + @Override + public String toString(@Readonly ObjectMethods6 this) { + return super.toString(); + } +} diff --git a/checker/tests/immutability/OnlyOneModifierIsUse.java b/checker/tests/immutability/OnlyOneModifierIsUse.java new file mode 100644 index 00000000000..f31f45002f8 --- /dev/null +++ b/checker/tests/immutability/OnlyOneModifierIsUse.java @@ -0,0 +1,12 @@ +import org.checkerframework.checker.immutability.qual.Immutable; +import org.checkerframework.checker.immutability.qual.Readonly; + +public class OnlyOneModifierIsUse { + + // :: error: (type.invalid.conflicting.annos) + // :: error: (initialization.field.uninitialized) + @Readonly @Immutable Object field; + // :: error: (type.invalid.conflicting.annos) + // :: error: (initialization.field.uninitialized) + String @Readonly @Immutable [] array; +} diff --git a/checker/tests/immutability/OverrideEquals.java b/checker/tests/immutability/OverrideEquals.java new file mode 100644 index 00000000000..3e531ea82d5 --- /dev/null +++ b/checker/tests/immutability/OverrideEquals.java @@ -0,0 +1,32 @@ +import org.checkerframework.checker.immutability.qual.Mutable; +import org.checkerframework.checker.immutability.qual.Readonly; + +class A { + void foo(@Readonly Object o) {} +} + +public class OverrideEquals extends A { + @Override + void foo(@Readonly Object o) {} + + @Override + public boolean equals(Object o) { + return super.equals(o); + } + + @Override + public Object clone() { + try { + return super.clone(); + } catch (CloneNotSupportedException exc) { + throw new InternalError(); // should never happen since we are cloneable + } + } +} + +class SubOverrideEquals extends OverrideEquals { + @Override + public boolean equals(@Readonly Object o) { + return super.equals(new @Mutable Object()); + } +} diff --git a/checker/tests/immutability/Planet.java b/checker/tests/immutability/Planet.java new file mode 100644 index 00000000000..9b2921665c7 --- /dev/null +++ b/checker/tests/immutability/Planet.java @@ -0,0 +1,92 @@ +import org.checkerframework.checker.immutability.qual.Immutable; +import org.checkerframework.checker.immutability.qual.Mutable; +import org.checkerframework.checker.immutability.qual.Readonly; + +/** Planet is an immutable class, since there is no way to change its state after construction. */ +@Immutable +public class Planet { + /** Final primitive data is always immutable. */ + private double fMass; + + /** An immutable object field. (String objects never change state.) */ + private String fName; + + /** + * An immutable object field. The state of this immutable field can never be changed by anyone. + */ + private @Immutable Date fDateOfDiscovery; + + public @Immutable Planet(double aMass, String aName, @Immutable Date aDateOfDiscovery) { + fMass = aMass; + fName = aName; + // No need to copy aDateOfDiscovery, as it's @Immutable + fDateOfDiscovery = aDateOfDiscovery; + } + + /** + * Returns a primitive value. + * + *

The caller can do whatever they want with the return value, without affecting the + * internals of this class. Why? Because this is a primitive value. The caller sees its "own" + * double that simply has the same value as fMass. + */ + public double getMass(@Readonly Planet this) { + return fMass; + } + + /** + * Returns an immutable object. + * + *

The caller gets a direct reference to the internal field. But this is not dangerous, since + * String is immutable and cannot be changed. + */ + public String getName(@Readonly Planet this) { + return fName; + } + + // /** + // * Returns a mutable object - likely bad style. + // * + // * The caller gets a direct reference to the internal field. This is usually dangerous, + // * since the Date object state can be changed both by this class and its caller. + // * That is, this class is no longer in complete control of fDate. + // */ + // public Date getDateOfDiscovery() { + // return fDateOfDiscovery; + // } + + /** Not need to return a defensive copy of the field. */ + public @Immutable Date getDateOfDiscovery(@Readonly Planet this) { + return fDateOfDiscovery; + } + + @Override + public String toString() { + // TODO Handle case in which fDateOfDiscovery is not @Immutable + return "Name: " + fName + " mass: " + fMass + " date of discovery: " + fDateOfDiscovery; + } + + public static void main(String[] args) { + @Immutable Date discoveryDate = new @Immutable Date(); + // :: error: (type.invalid.annotations.on.use) + @Mutable Planet mPlanet; + // :: error: (constructor.invocation.invalid) + mPlanet = new @Mutable Planet(1, "Earth", discoveryDate); + @Immutable Planet imPlanet = new @Immutable Planet(1, "Earth", discoveryDate); + // None of the fields are allowed to be modified on an immutable object + // :: error: (illegal.field.write) + imPlanet.fMass = 2; + // :: error: (illegal.field.write) + imPlanet.fName = "Jupitor"; + // :: error: (illegal.field.write) + imPlanet.fDateOfDiscovery = new @Immutable Date(); + // :: error: (method.invocation.invalid) + imPlanet.fDateOfDiscovery.setTime(123L); + // Object returned by getter method is neither modifiable + // :: error: (method.invocation.invalid) + imPlanet.getDateOfDiscovery().setTime(123L); + // Caller cannot mutate date object passed into imPlanet object + // :: error: (method.invocation.invalid) + discoveryDate.setTime(123L); + } +} diff --git a/checker/tests/immutability/PolyMutableBug.java b/checker/tests/immutability/PolyMutableBug.java new file mode 100644 index 00000000000..c5891bace65 --- /dev/null +++ b/checker/tests/immutability/PolyMutableBug.java @@ -0,0 +1,22 @@ +// @skip-test TypeArgInferenceUtil#assignedTo() right now doesn't handle correctly +// the case where one method invocation is used as the actual receiver for another +// method invocation but the first method invocation is not directly called method, +// e.g. in a paranthesis. +public class PolyMutableBug { + void foo(A a) { + // Having parentheis here causes StackOverFlowError + // It causes ((MemberSelectTree) methodInvocation.getMethodSelect()).getExpression() + // in TypeArgInferenceUtil to return a ParenthesizedTree instead of MethodInvocationTree + (a.subtract()).multiply(); + } +} + +class A { + A subtract() { + return this; + } + + A multiply() { + return this; + } +} diff --git a/checker/tests/immutability/PolyMutableOnConstructorParameters.java b/checker/tests/immutability/PolyMutableOnConstructorParameters.java new file mode 100644 index 00000000000..cdf7d8a55fd --- /dev/null +++ b/checker/tests/immutability/PolyMutableOnConstructorParameters.java @@ -0,0 +1,14 @@ +import org.checkerframework.checker.immutability.qual.Immutable; +import org.checkerframework.checker.immutability.qual.PolyMutable; + +@Immutable +public class PolyMutableOnConstructorParameters { + @Immutable + PolyMutableOnConstructorParameters(@PolyMutable Object o) {} + + public static void main(String[] args) { + @Immutable + PolyMutableOnConstructorParameters o1 = + new @Immutable PolyMutableOnConstructorParameters(new @Immutable Object()); + } +} diff --git a/checker/tests/immutability/Polymorphism.java b/checker/tests/immutability/Polymorphism.java new file mode 100644 index 00000000000..b63a367f63c --- /dev/null +++ b/checker/tests/immutability/Polymorphism.java @@ -0,0 +1,60 @@ +import org.checkerframework.checker.immutability.qual.Immutable; +import org.checkerframework.checker.immutability.qual.Mutable; +import org.checkerframework.checker.immutability.qual.PolyMutable; +import org.checkerframework.checker.immutability.qual.ReceiverDependantMutable; + +@ReceiverDependantMutable +class B { + @PolyMutable + B getObject() { + return null; + } + + @PolyMutable + B getSecondObject(@PolyMutable B this) { + return null; + } + + @PolyMutable + B getThirdObject(@Mutable B this) { + return null; + } + + @Immutable + B getForthObject() { + return this.getThirdObject(); + } +} + +public class Polymorphism { + void test1(@Mutable B mb) { + @Mutable Object l = mb.getObject(); + @Immutable Object r = mb.getObject(); + } + + void test2(@Mutable B mb) { + @Mutable Object l = mb.getSecondObject(); + // TODO Should be poly.invocation.error something... + // :: error: (assignment.type.incompatible) + @Immutable Object r = mb.getSecondObject(); + } + + void test3(@Immutable B imb) { + // TODO Should be poly.invocation.error something... + // :: error: (assignment.type.incompatible) + @Mutable Object l = imb.getSecondObject(); + @Immutable Object r = imb.getSecondObject(); + } + + void test4(@Mutable B b) { + // This correctly typechecks + @Immutable Object r = b.getObject().getThirdObject(); + } + + // TODO Poly return type used on poly receiver. This is not yet implemented yet in CF + void test5(@Mutable B b) { + // TODO Should typecheck. + // :: error: (assignment.type.incompatible) + @Immutable Object r = b.getSecondObject().getSecondObject(); + } +} diff --git a/checker/tests/immutability/PolymorphismProblem.java b/checker/tests/immutability/PolymorphismProblem.java new file mode 100644 index 00000000000..c5751ca2aa9 --- /dev/null +++ b/checker/tests/immutability/PolymorphismProblem.java @@ -0,0 +1,23 @@ +import org.checkerframework.checker.immutability.qual.Immutable; +import org.checkerframework.checker.immutability.qual.PolyMutable; +import org.checkerframework.checker.immutability.qual.Readonly; +import org.checkerframework.checker.immutability.qual.ReceiverDependantMutable; + +class A { + @ReceiverDependantMutable + Object read(@Readonly A this, @PolyMutable Object p) { + return new @ReceiverDependantMutable Object(); + } +} + +class PolymorphismProblem { + @PolyMutable + Object foo(@PolyMutable A a) { + // Typecheck now. Only when the declared type is @PolyMutable, after viewpoint adadptation, + // it becomes @SubsitutablePolyMutable, and then will be resolved by QualifierPolymorphism + // Note: viewpoint adaptation(ATF) happens before QualfierPolymorphism(GATF) in current + // implementation + @PolyMutable Object result = a.read(new @Immutable Object()); + return result; + } +} diff --git a/checker/tests/immutability/Primitive.java b/checker/tests/immutability/Primitive.java new file mode 100644 index 00000000000..f5158b8c3fe --- /dev/null +++ b/checker/tests/immutability/Primitive.java @@ -0,0 +1,32 @@ +import org.checkerframework.checker.immutability.qual.Immutable; +import org.checkerframework.checker.immutability.qual.Mutable; +import org.checkerframework.checker.immutability.qual.Readonly; +import org.checkerframework.checker.immutability.qual.ReceiverDependantMutable; + +@Immutable +public class Primitive { + // In the abstract state + int implicitImmutableInt; + @Immutable int validInt; + // If you want to exclude primitive(including boxed primitive) and String from + // abstract state, use @Readonly to do this, but not @Mutable, because they can't + // be mutated conceptually. + // :: error: (type.invalid.annotations.on.use) + @Readonly int implicitOverridenInt; + // :: error: (type.invalid.annotations.on.use) + @Mutable int invalidInt; + // :: error: (type.invalid.annotations.on.use) + @ReceiverDependantMutable int invalidInt2; + + // :: error: (initialization.fields.uninitialized) + @Immutable + Primitive() { + // Allowed within constructor + implicitImmutableInt = 0; + } + + void mutateFields(@Immutable Primitive this) { + // :: error: (illegal.field.write) + implicitImmutableInt = 1; + } +} diff --git a/checker/tests/immutability/Primitive2.java b/checker/tests/immutability/Primitive2.java new file mode 100644 index 00000000000..4f68c14e9df --- /dev/null +++ b/checker/tests/immutability/Primitive2.java @@ -0,0 +1,19 @@ +class A { + int size() { + return 0; + } +} + +public class Primitive2 { + void foo(A a) { + double mean1 = mean(a); + } + + static double mean(A a) { + return sum(a) / a.size(); + } + + static double sum(A a) { + return 1.0; + } +} diff --git a/checker/tests/immutability/Primitive3.java b/checker/tests/immutability/Primitive3.java new file mode 100644 index 00000000000..10f0f52db36 --- /dev/null +++ b/checker/tests/immutability/Primitive3.java @@ -0,0 +1,18 @@ +class Word { + Object get(int i) { + return null; + } +} + +public class Primitive3 { + + void foo(Word word) { + String[] params = {}; + // TODO + // I reenable type cast safety checking when the cast type is implicitly immutable. + // Why should we suppress warning just because cast type is implicitly immutable? + // That doesn't make any sense. Am I right? + // No cast.unsafe + params[0] = (String) word.get(0); + } +} diff --git a/checker/tests/immutability/Primitive4.java b/checker/tests/immutability/Primitive4.java new file mode 100644 index 00000000000..a4c968285fb --- /dev/null +++ b/checker/tests/immutability/Primitive4.java @@ -0,0 +1,9 @@ +class A { + Object elements; +} + +public class Primitive4 { + boolean foo(A p1, A p2) { + return p1.elements == p2.elements; + } +} diff --git a/checker/tests/immutability/PritimitveReturn.java b/checker/tests/immutability/PritimitveReturn.java new file mode 100644 index 00000000000..70de204aa3a --- /dev/null +++ b/checker/tests/immutability/PritimitveReturn.java @@ -0,0 +1,31 @@ +// As referred to NullnessPropagationTreeAnnotator#visitBinary() and +// NullnessPropagationTreeAnnotator#visitUnary(): result type for these two types +// are always @Initialized. +public class PritimitveReturn { + + // Shouldn't have return.type.incompatible: @UnderInitialization and @Initialized + public static double binomial() { + + double binomial = 1.0; + int i = 1; + if (false) { + // Here, previously before this commit, binomial becomes UnderInitialization@ + binomial *= 1.0 / (double) (i--); + } + // Shouldn't have @UnderInitialization anymore for binomial + return binomial; + } + + // Similar to above + public static double fac1() { + + long d = 1; + long i = 1; + if (false) { + // Similar to above + d *= i--; + } + // Similar to above + return -d; + } +} diff --git a/checker/tests/immutability/RDMAllowedAsMethodReceiver.java b/checker/tests/immutability/RDMAllowedAsMethodReceiver.java new file mode 100644 index 00000000000..724cb1afe0e --- /dev/null +++ b/checker/tests/immutability/RDMAllowedAsMethodReceiver.java @@ -0,0 +1,15 @@ +import org.checkerframework.checker.immutability.qual.Immutable; +import org.checkerframework.checker.immutability.qual.Mutable; +import org.checkerframework.checker.immutability.qual.ReceiverDependantMutable; + +@Immutable +class RDMAllowedAsMethodReceiver { + // :: error: (type.invalid.annotations.on.use) :: error: (method.receiver.incompatible) + void foo(@ReceiverDependantMutable RDMAllowedAsMethodReceiver this) {} +} + +@Mutable +class AnotherExample { + // :: error: (type.invalid.annotations.on.use) :: error: (method.receiver.incompatible) + void foo(@ReceiverDependantMutable AnotherExample this) {} +} diff --git a/checker/tests/immutability/RDMBug.java b/checker/tests/immutability/RDMBug.java new file mode 100644 index 00000000000..0a5a07f8f2f --- /dev/null +++ b/checker/tests/immutability/RDMBug.java @@ -0,0 +1,16 @@ +import org.checkerframework.checker.immutability.qual.Immutable; +import org.checkerframework.checker.immutability.qual.Mutable; +import org.checkerframework.checker.immutability.qual.Readonly; + +@Immutable +class RDMBug { + @Mutable Object o; + @Readonly Object o2; + + void foo(@Immutable RDMBug this) { + // :: error: (illegal.field.write) + this.o = new @Mutable Object(); + // :: error: (illegal.field.write) + this.o2 = new @Immutable Object(); + } +} diff --git a/checker/tests/immutability/RDMField.java b/checker/tests/immutability/RDMField.java new file mode 100644 index 00000000000..d11cd41af5c --- /dev/null +++ b/checker/tests/immutability/RDMField.java @@ -0,0 +1,44 @@ +import org.checkerframework.checker.immutability.qual.*; + +public class RDMField { + + @Mutable + private static class MutableClass { + int field = 0; + } + + @ReceiverDependantMutable + private static class RDMHolder { + + // :: error: (type.invalid.annotations.on.use) + @ReceiverDependantMutable MutableClass field = new MutableClass(); + @Mutable MutableClass mutableField = new MutableClass(); + + public @PolyMutable MutableClass getField(@PolyMutable RDMHolder this) { + return field; + } + + public void setField(@Mutable RDMHolder this, MutableClass field) { + this.field = field; + } + + void asImmutable(@Immutable RDMHolder r) { + // :: error: (illegal.field.write) + r.field.field = 1; + // :: error: (illegal.field.write) + r.getField().field = 1; + // :: error: (method.invocation.invalid) + r.setField(new MutableClass()); + } + } + + @Immutable + private static class ImmutableHolder { + // :: error: (type.invalid.annotations.on.use) + @ReceiverDependantMutable MutableClass field = new MutableClass(); + + public @PolyMutable MutableClass getField(@PolyMutable ImmutableHolder this) { + return field; + } + } +} diff --git a/checker/tests/immutability/RDMFieldInst.java b/checker/tests/immutability/RDMFieldInst.java new file mode 100644 index 00000000000..631362020b6 --- /dev/null +++ b/checker/tests/immutability/RDMFieldInst.java @@ -0,0 +1,31 @@ +import org.checkerframework.checker.immutability.qual.*; + +public class RDMFieldInst { + @Mutable + private static class MutableBox {} + + @Immutable + private static class ImmutableBox {} + + @ReceiverDependantMutable + private static class RDMBox {} + + @Immutable + private static class ImmutableClass { + // :: error: (type.invalid.annotations.on.use) + @ReceiverDependantMutable MutableBox mutableBoxInRDM; + } + + @Mutable + private static class MutableClass { + @ReceiverDependantMutable MutableBox mutableBoxInRDM = new MutableBox(); + + @ReceiverDependantMutable RDMBox rdmBoxInRDMnewM = new @Mutable RDMBox(); + // :: error: (assignment.type.incompatible) + @ReceiverDependantMutable RDMBox rdmBoxInRDMnewI = new @Immutable RDMBox(); + // :: error: (assignment.type.incompatible) + @ReceiverDependantMutable RDMBox rdmBoxInRDMnewRDM = new @ReceiverDependantMutable RDMBox(); + // :: error: (type.invalid.annotations.on.use) + @ReceiverDependantMutable ImmutableBox immutableBoxInRDM = new ImmutableBox(); + } +} diff --git a/checker/tests/immutability/RGB.java b/checker/tests/immutability/RGB.java new file mode 100644 index 00000000000..b4b0198dacb --- /dev/null +++ b/checker/tests/immutability/RGB.java @@ -0,0 +1,75 @@ +import org.checkerframework.checker.immutability.qual.Immutable; +import org.checkerframework.checker.immutability.qual.Mutable; +import org.checkerframework.checker.immutability.qual.Readonly; +import org.checkerframework.checker.immutability.qual.ReceiverDependantMutable; +import org.checkerframework.checker.initialization.qual.UnknownInitialization; + +// Inspire by: https://docs.oracle.com/javase/tutorial/essential/concurrency/imstrat.html +// Allow both mutable and immutable instance creation +// Allow having getters and setters, don't need to remove them +// fields don't need to be declared with "final" +// Don't need defensive copy(even though not applicable in this example) +@ReceiverDependantMutable +public class RGB { + + // Values must be between 0 and 255. + private int red; + private int green; + private int blue; + private String name; + + private void check(@UnknownInitialization @Readonly RGB this, int red, int green, int blue) { + if (red < 0 || red > 255 || green < 0 || green > 255 || blue < 0 || blue > 255) { + throw new IllegalArgumentException(); + } + } + + public RGB(int red, int green, int blue, String name) { + check(red, green, blue); + this.red = red; + this.green = green; + this.blue = blue; + this.name = name; + } + + public void set(int red, int green, int blue, String name) { + check(red, green, blue); + synchronized (this) { + this.red = red; + this.green = green; + this.blue = blue; + this.name = name; + } + } + + public synchronized int getRGB(@Readonly RGB this) { + return ((red << 16) | (green << 8) | blue); + } + + public synchronized String getName(@Readonly RGB this) { + return name; + } + + public synchronized void invert() { + red = 255 - red; + green = 255 - green; + blue = 255 - blue; + name = "Inverse of " + name; + } + + public static void main(String[] args) { + @Immutable RGB immutable = new @Immutable RGB(0, 0, 0, "black"); + // :: error: (method.invocation.invalid) + immutable.set(1, 1, 1, "what"); + // :: error: (method.invocation.invalid) + immutable.invert(); + immutable.getName(); + immutable.getRGB(); + + @Mutable RGB mutable = new @Mutable RGB(255, 255, 255, "white"); + mutable.set(1, 1, 1, "what"); + mutable.invert(); + mutable.getName(); + mutable.getRGB(); + } +} diff --git a/checker/tests/immutability/RawList.java b/checker/tests/immutability/RawList.java new file mode 100644 index 00000000000..8e5a5022086 --- /dev/null +++ b/checker/tests/immutability/RawList.java @@ -0,0 +1,49 @@ +import org.checkerframework.checker.immutability.qual.Readonly; + +import java.util.AbstractList; + +public abstract class RawList extends AbstractList { + + // What method does it override? + // What should be the type if no type parameter on class declaration + @Override + @SuppressWarnings("unchecked") + public boolean add(Object o) { + return super.add(o); + } + + @Override + @SuppressWarnings("unchecked") + public void add(int i, Object o) { + super.add(i, o); + } + + @Override + @SuppressWarnings("unchecked") + public Object set(int i, Object o) { + return super.set(i, o); + } +} + +abstract class MyList extends AbstractList { + + @Override + public E get(@Readonly MyList this, int i) { + return null; + } + + @Override + public boolean add(E e) { + return super.add(e); + } + + @Override + public void add(int i, E e) { + super.add(i, e); + } + + @Override + public E set(int i, E e) { + return super.set(i, e); + } +} diff --git a/checker/tests/immutability/RawType.java b/checker/tests/immutability/RawType.java new file mode 100644 index 00000000000..d046bd129cb --- /dev/null +++ b/checker/tests/immutability/RawType.java @@ -0,0 +1,30 @@ +import java.util.ArrayList; +import java.util.Iterator; + +interface A { + + public void foo(); +} + +public class RawType { + + private ArrayList list; + + protected void foo() { + for (Iterator i = list.iterator(); i.hasNext(); ) { + // Iterator is raw type here. After JDK1.5, it're represented as if there is type + // argument + // "? extends @Mutable Object"(a range of types below @Mutable Object), which is passed + // to + // type parameter "E extends @Readonly Object"(one fixtd type below @Readonly Object). + // Since + // any type under @Mutable Object is below @Readonly Object, "? extends @Mutable Object" + // is + // a valid type argument. foo() method expects a @Mutable A receiver, like above, + // "? extends @Mutable Object" is a valid actual receiver(subtype of @Mutable Object) so + // the + // method invocation typechecks + ((A) i.next()).foo(); + } + } +} diff --git a/checker/tests/immutability/ReadonlyConstructor.java b/checker/tests/immutability/ReadonlyConstructor.java new file mode 100644 index 00000000000..56f45317b8d --- /dev/null +++ b/checker/tests/immutability/ReadonlyConstructor.java @@ -0,0 +1,8 @@ +import org.checkerframework.checker.immutability.qual.Readonly; + +public class ReadonlyConstructor { + + // :: error: (constructor.return.invalid) + @Readonly + ReadonlyConstructor() {} +} diff --git a/checker/tests/immutability/ReadonlyMayCaptureMutable.java b/checker/tests/immutability/ReadonlyMayCaptureMutable.java new file mode 100644 index 00000000000..35e64b7257a --- /dev/null +++ b/checker/tests/immutability/ReadonlyMayCaptureMutable.java @@ -0,0 +1,28 @@ +import org.checkerframework.checker.immutability.qual.Immutable; +import org.checkerframework.checker.immutability.qual.Mutable; +import org.checkerframework.checker.immutability.qual.Readonly; +import org.checkerframework.checker.immutability.qual.ReceiverDependantMutable; + +@ReceiverDependantMutable +public class ReadonlyMayCaptureMutable { + static @Mutable Object smf = new @Mutable Object(); + + @Readonly Object rof; + + @ReceiverDependantMutable + ReadonlyMayCaptureMutable() { + // Not a problem anymore, because readonly field is out of the abstract state + rof = smf; + } + + @Immutable + ReadonlyMayCaptureMutable(@Immutable Object o) { + // The same argument applies as above + rof = smf; + } + + @Mutable + ReadonlyMayCaptureMutable(Object o1, Object o2) { + rof = smf; + } +} diff --git a/checker/tests/immutability/ReceiverDependantMutableConstructor.java b/checker/tests/immutability/ReceiverDependantMutableConstructor.java new file mode 100644 index 00000000000..05263b815b0 --- /dev/null +++ b/checker/tests/immutability/ReceiverDependantMutableConstructor.java @@ -0,0 +1,64 @@ +import org.checkerframework.checker.immutability.qual.Immutable; +import org.checkerframework.checker.immutability.qual.Mutable; +import org.checkerframework.checker.immutability.qual.Readonly; +import org.checkerframework.checker.immutability.qual.ReceiverDependantMutable; + +@ReceiverDependantMutable +public class ReceiverDependantMutableConstructor { + + @Readonly Object rof; + @ReceiverDependantMutable Object pif; + @Immutable Object imf; + + // :: error: (initialization.fields.uninitialized) + @ReceiverDependantMutable + ReceiverDependantMutableConstructor( + @Mutable Object mo, @ReceiverDependantMutable Object po, @Immutable Object io) {} + + @ReceiverDependantMutable + ReceiverDependantMutableConstructor(@ReceiverDependantMutable Object po, @Immutable Object io) { + this.rof = po; + this.rof = io; + + this.pif = po; + // :: error: (assignment.type.incompatible) + this.pif = io; + + // :: error: (assignment.type.incompatible) + this.imf = po; + this.imf = io; + } + + void invokeConstructor( + @Readonly Object ro, + @Mutable Object mo, + @ReceiverDependantMutable Object po, + @Immutable Object io) { + new @Mutable ReceiverDependantMutableConstructor(mo, io); + // :: error: (argument.type.incompatible) + new @Mutable ReceiverDependantMutableConstructor(ro, io); + // :: error: (argument.type.incompatible) + new @Mutable ReceiverDependantMutableConstructor(po, io); + // :: error: (argument.type.incompatible) + new @Mutable ReceiverDependantMutableConstructor(io, io); + + new @ReceiverDependantMutable ReceiverDependantMutableConstructor(po, io); + // :: error: (argument.type.incompatible) + new @ReceiverDependantMutable ReceiverDependantMutableConstructor(ro, io); + // :: error: (argument.type.incompatible) + new @ReceiverDependantMutable ReceiverDependantMutableConstructor(mo, io); + // :: error: (argument.type.incompatible) + new @ReceiverDependantMutable ReceiverDependantMutableConstructor(io, io); + + new @Immutable ReceiverDependantMutableConstructor(io, io); + // :: error: (argument.type.incompatible) + new @Immutable ReceiverDependantMutableConstructor(ro, io); + // :: error: (argument.type.incompatible) + new @Immutable ReceiverDependantMutableConstructor(mo, io); + // :: error: (argument.type.incompatible) + new @Immutable ReceiverDependantMutableConstructor(po, io); + + // :: error: (pico.new.invalid) + new @Readonly ReceiverDependantMutableConstructor(ro, io); + } +} diff --git a/checker/tests/immutability/ReceiverTypeOutsideConstructor.java b/checker/tests/immutability/ReceiverTypeOutsideConstructor.java new file mode 100644 index 00000000000..623101e0e29 --- /dev/null +++ b/checker/tests/immutability/ReceiverTypeOutsideConstructor.java @@ -0,0 +1,154 @@ +import org.checkerframework.checker.immutability.qual.Immutable; +import org.checkerframework.checker.immutability.qual.Mutable; +import org.checkerframework.checker.immutability.qual.ReceiverDependantMutable; + +import java.util.Date; + +// Immutable class +@Immutable +class A { + @ReceiverDependantMutable Date d = new @Immutable Date(); + + { + // Bound annotation applies to "this" perfectly now. So no need to take action. + d = new @Immutable Date(); + } + + @Immutable + A() { + d = new @Immutable Date(); + } + + // :: error: (type.invalid.annotations.on.use) + @ReceiverDependantMutable + A(Object o1) { + d = new @ReceiverDependantMutable Date(); + } + + // :: error: (type.invalid.annotations.on.use) + @Mutable + A(Object o1, Object o2) { + d = new @Mutable Date(); + } +} + +@Immutable +class AIMS extends A {} + +// :: error: (type.invalid.annotations.on.use) :: error: (super.invocation.invalid) +@ReceiverDependantMutable +class ARDMS extends A {} + +// :: error: (type.invalid.annotations.on.use) :: error: (super.invocation.invalid) +@Mutable +class AMS extends A {} + +// :: error: (type.invalid.annotations.on.use) :: error: (super.invocation.invalid) +class AUNKS extends A {} + +// ReceiverDependantMutable class +@ReceiverDependantMutable +class B { + @ReceiverDependantMutable Date d = new @ReceiverDependantMutable Date(); + + { + // Bound annotation applies to "this" perfectly now. So no need to take action. + d = new @ReceiverDependantMutable Date(); + } + + // ok + @Immutable + B() { + d = new @Immutable Date(); + } + + // ok + @ReceiverDependantMutable + B(Object o1) { + d = new @ReceiverDependantMutable Date(); + } + + // ok + @Mutable + B(Object o1, Object o2) { + d = new @Mutable Date(); + } +} + +@Immutable +class BIMS extends B {} + +// :: error: (super.invocation.invalid) +@ReceiverDependantMutable +class BRDMS extends B {} + +// :: error: (super.invocation.invalid) +@Mutable +class BMS extends B {} + +// mutable by default(TODO Does this make sense compared to defaulting to +// receiver-dependant-mutable?) +// :: error: (super.invocation.invalid) +class BUNKS extends B {} + +// Mutable class +@Mutable +class C { + @ReceiverDependantMutable Date d = new @Mutable Date(); + + { + // Bound annotation applies to "this" perfectly now. So no need to take action. + d = new @Mutable Date(); + } + + // :: error: (type.invalid.annotations.on.use) + @Immutable + C() { + d = new @Immutable Date(); + } + + // :: error: (type.invalid.annotations.on.use) + @ReceiverDependantMutable + C(Object o1) { + d = new @ReceiverDependantMutable Date(); + } + + @Mutable + C(Object o1, Object o2) { + d = new @Mutable Date(); + } +} + +// :: error: (type.invalid.annotations.on.use) +@Immutable +class CIMS extends C {} + +// :: error: (type.invalid.annotations.on.use) :: error: (super.invocation.invalid) +@ReceiverDependantMutable +class CRDMS extends C {} + +// :: error: (super.invocation.invalid) +@Mutable +class CMS extends C {} + +// :: error: (super.invocation.invalid) +class CUNKS extends C {} + +class D { + @ReceiverDependantMutable Date d = new @Mutable Date(); + + { + d = new @Mutable Date(); + } +} + +@Immutable +interface E { + void foo(@Immutable E this); +} + +@Immutable +public class ReceiverTypeOutsideConstructor implements E { + @Override + public void foo(@Immutable ReceiverTypeOutsideConstructor this) {} +} diff --git a/checker/tests/immutability/RefineFromNull.java b/checker/tests/immutability/RefineFromNull.java new file mode 100644 index 00000000000..5412f7edfac --- /dev/null +++ b/checker/tests/immutability/RefineFromNull.java @@ -0,0 +1,36 @@ +import org.checkerframework.checker.immutability.qual.Immutable; +import org.checkerframework.checker.immutability.qual.Mutable; +import org.checkerframework.checker.immutability.qual.Readonly; + +class Acceptor { + static void accept1(@Mutable Object o) {} + + static void accept2(@Immutable Object o) {} +} + +public class RefineFromNull { + void test1() { + // Should we allow propagation of @Bottom towards declared type? + // We should, otherwise, refined type is always top(lub with top is top) + @Readonly Object rowNames = null; + // TODO Should we give warning here because of refined @Bottom? It will warn if we enforce + // forbidding @Bottom in Validator => We don't warn @Bottom anymore, it's internal qualifier + // now, and internal usage is always valid + Acceptor.accept1(rowNames); + Acceptor.accept2(rowNames); + } + + void test2() { + String s = null; + Acceptor.accept1(s); + Acceptor.accept2(s); + } + + void test3(@Readonly Object o) { + @Readonly Object lo = o; + // :: error: (argument.type.incompatible) + Acceptor.accept1(lo); + // :: error: (argument.type.incompatible) + Acceptor.accept2(lo); + } +} diff --git a/checker/tests/immutability/ShouldFailButDidnot.java b/checker/tests/immutability/ShouldFailButDidnot.java new file mode 100644 index 00000000000..5dbb6935eb9 --- /dev/null +++ b/checker/tests/immutability/ShouldFailButDidnot.java @@ -0,0 +1,10 @@ +import org.checkerframework.checker.immutability.qual.Immutable; +import org.checkerframework.checker.immutability.qual.Mutable; +import org.checkerframework.checker.immutability.qual.Readonly; + +class ShouldFailButDidnot { + void foo() { + @Readonly Object o = new @Immutable Object(); + o = new @Mutable Object(); + } +} diff --git a/checker/tests/immutability/Static.java b/checker/tests/immutability/Static.java new file mode 100644 index 00000000000..359a58d98ae --- /dev/null +++ b/checker/tests/immutability/Static.java @@ -0,0 +1,53 @@ +import org.checkerframework.checker.immutability.qual.Immutable; +import org.checkerframework.checker.immutability.qual.Mutable; +import org.checkerframework.checker.immutability.qual.PolyMutable; +import org.checkerframework.checker.immutability.qual.Readonly; +import org.checkerframework.checker.immutability.qual.ReceiverDependantMutable; + +@ReceiverDependantMutable +public class Static { + // :: error: (static.receiverdependantmutable.forbidden) + static @ReceiverDependantMutable Object o = new @ReceiverDependantMutable Object(); + static Object oo; + + @ReceiverDependantMutable Object f; + + @ReceiverDependantMutable + Static() { + f = o; + } + + void twoRuntimeSemantics() { + @Immutable Static ims = new @Immutable Static(); + @Immutable Object alias1 = ims.f; + @Mutable Static ms = new @Mutable Static(); + @Mutable Object alias2 = ms.f; + // Call mutating methods on alias2 and static field o has two runtime semantics + // .... + } + + // :: error: (static.receiverdependantmutable.forbidden) + static @ReceiverDependantMutable Object readStaticReceiverDependantMutableField( + @ReceiverDependantMutable Object p) { + return o; + // TODO Avoid warnings for receiverdependantmutable fields in anonymous class + } + + static { + oo = new @Mutable Object(); + // :: error: (static.receiverdependantmutable.forbidden) + @Readonly Object ro = (@ReceiverDependantMutable Object) o; + // :: error: (static.receiverdependantmutable.forbidden) + new @ReceiverDependantMutable Object(); + } + + // :: error: (static.receiverdependantmutable.forbidden) + static @Readonly Object o2 = new @ReceiverDependantMutable Object(); + + static @PolyMutable Object createPolyObject(@Immutable Object p) { + return new @PolyMutable Object(); + } + + // TODO Hackily implemented. Should better implement it + static @Mutable Object o3 = createPolyObject(new @Immutable Object()); +} diff --git a/checker/tests/immutability/StaticFields.java b/checker/tests/immutability/StaticFields.java new file mode 100644 index 00000000000..a3bf66ceefa --- /dev/null +++ b/checker/tests/immutability/StaticFields.java @@ -0,0 +1,40 @@ +import org.checkerframework.checker.immutability.qual.Immutable; +import org.checkerframework.checker.immutability.qual.Mutable; + +import java.util.Date; + +// In these two cases, types are not applied computed annotations: +// 1) Has explicit annotation, e.g. d2 and d3 +// 2) Has bound annotation on Element, e.g. d. It can be from source file or stub file(jdk.astub) +public class StaticFields { + // (this case again) Not implicitly immutable type - Handled by PICOTreeAnnotator on declaration + // Update: d is equivalent to having explicit annotations now. TreeAnnotators belong to + // addComputedAnnotations() + // phase, and they don't have any effect now(provided those TreeAnnotators don't always + // replaceAnnotations, and + // this is true for PICOTreeAnnotators: they all repect existing annotations from source and + // elements) + // Update2: d no longer gets inheritted @ReceiverDependantMutable anymore. It goes back to + // original + // implementation - PICOTreeAnnotator adds @Mutable to non-implicitly immutable type. This is + // the result + // of not inheritting @ReceiverDependantMutable to the usage because it creates unexpected + // noises. + // As a result, d has no annotation on it, and it falls through to PICOTreeAnnotator and gets + // added + // @Mutable. + static Date d; + static Integer i; // Implicitly immutable type - Handled by PICOImplicitsTypeAnnotator + static @Mutable Date d2; + static @Immutable Date d3; + + static { + // (not the case anymore) new instance creation also has @ReceiverDependantMutable type + // which is from element declaration in stub file(bound) + d = new Date(); // (this case again) Handled by addComputedTypeAnnotations(Element, + // AnnotatedTypeMirror) when used + i = 2; // Handled by PICOImplicitsTypeAnnotator when used + d2 = new @Mutable Date(); + d3 = new @Immutable Date(); + } +} diff --git a/checker/tests/immutability/SuperClass.java b/checker/tests/immutability/SuperClass.java new file mode 100644 index 00000000000..476e56a963d --- /dev/null +++ b/checker/tests/immutability/SuperClass.java @@ -0,0 +1,46 @@ +import org.checkerframework.checker.immutability.qual.Immutable; +import org.checkerframework.checker.immutability.qual.Mutable; +import org.checkerframework.checker.immutability.qual.ReceiverDependantMutable; + +import java.util.Date; + +@ReceiverDependantMutable +public class SuperClass { + @ReceiverDependantMutable Date p; + + @Immutable + SuperClass(@Immutable Date p) { + this.p = p; + } + + void maliciouslyModifyDate(@Mutable SuperClass this) { + p.setTime(2L); + } +} + +class SubClass extends SuperClass { + @Mutable + SubClass() { + // :: error: (super.invocation.invalid) + super(new @Immutable Date(1L)); + } + + public static void main(String[] args) { + @Mutable SubClass victim = new @Mutable SubClass(); + victim.maliciouslyModifyDate(); + } +} + +@ReceiverDependantMutable +class AnotherSubClass extends SuperClass { + @ReceiverDependantMutable + AnotherSubClass() { + // :: error: (super.invocation.invalid) + super(new @Immutable Date(1L)); + } + + public static void main(String[] args) { + @Mutable SubClass victim = new @Mutable SubClass(); + victim.maliciouslyModifyDate(); + } +} diff --git a/checker/tests/immutability/SuperClass2.java b/checker/tests/immutability/SuperClass2.java new file mode 100644 index 00000000000..3aeec5d56d0 --- /dev/null +++ b/checker/tests/immutability/SuperClass2.java @@ -0,0 +1,50 @@ +import org.checkerframework.checker.immutability.qual.Immutable; +import org.checkerframework.checker.immutability.qual.Mutable; +import org.checkerframework.checker.immutability.qual.ReceiverDependantMutable; +import org.checkerframework.checker.initialization.qual.NotOnlyInitialized; +import org.checkerframework.checker.initialization.qual.UnderInitialization; + +import java.util.Date; + +@ReceiverDependantMutable +class Thief { + @NotOnlyInitialized @ReceiverDependantMutable SuperClass2 victimCaptured; + + @ReceiverDependantMutable + Thief(@UnderInitialization @ReceiverDependantMutable SuperClass2 victimCaptured) { + this.victimCaptured = victimCaptured; + } +} + +@ReceiverDependantMutable +public class SuperClass2 { + @ReceiverDependantMutable Date p; + @NotOnlyInitialized @ReceiverDependantMutable Thief thief; + + @Mutable + SuperClass2(@Mutable Date p) { + this.p = p; + // "this" escapes constructor and gets captured by thief + this.thief = new @Mutable Thief(this); + } +} + +@Immutable +class SubClass2 extends SuperClass2 { + @Immutable + SubClass2() { + // This is not ok any more + // :: error: (super.invocation.invalid) + super(new @Mutable Date()); + } +} + +@ReceiverDependantMutable +class AnotherSubClass2 extends SuperClass2 { + @ReceiverDependantMutable + AnotherSubClass2() { + // This is not ok any more + // :: error: (super.invocation.invalid) + super(new @Mutable Date()); + } +} diff --git a/checker/tests/immutability/SuperClass3.java b/checker/tests/immutability/SuperClass3.java new file mode 100644 index 00000000000..7970b66f77c --- /dev/null +++ b/checker/tests/immutability/SuperClass3.java @@ -0,0 +1,38 @@ +import org.checkerframework.checker.immutability.qual.Immutable; +import org.checkerframework.checker.immutability.qual.Mutable; +import org.checkerframework.checker.immutability.qual.ReceiverDependantMutable; + +import java.util.Date; + +@ReceiverDependantMutable +public class SuperClass3 { + @ReceiverDependantMutable Date p; + + @ReceiverDependantMutable + SuperClass3(@ReceiverDependantMutable Date p) { + this.p = p; + } +} + +class SubClass3 extends SuperClass3 { + @Mutable + SubClass3() { + super(new @Mutable Date(1L)); + } +} + +@Immutable +class AnotherSubClass3 extends SuperClass3 { + @Immutable + AnotherSubClass3() { + super(new @Immutable Date(1L)); + } +} + +@ReceiverDependantMutable +class ThirdSubClass3 extends SuperClass3 { + @ReceiverDependantMutable + ThirdSubClass3() { + super(new @ReceiverDependantMutable Date(1L)); + } +} diff --git a/checker/tests/immutability/SuperMethodInvocation.java b/checker/tests/immutability/SuperMethodInvocation.java new file mode 100644 index 00000000000..9fdb5ed2a19 --- /dev/null +++ b/checker/tests/immutability/SuperMethodInvocation.java @@ -0,0 +1,54 @@ +import org.checkerframework.checker.immutability.qual.Immutable; +import org.checkerframework.checker.immutability.qual.Mutable; +import org.checkerframework.checker.immutability.qual.Readonly; +import org.checkerframework.checker.immutability.qual.ReceiverDependantMutable; + +@ReceiverDependantMutable +public class SuperMethodInvocation { + @ReceiverDependantMutable Object f; + + @ReceiverDependantMutable + SuperMethodInvocation() { + this.f = new @ReceiverDependantMutable Object(); + } + + void foo(@Mutable SuperMethodInvocation this) { + this.f = new @Mutable Object(); + } +} + +@Immutable +class Subclass extends SuperMethodInvocation { + + @Immutable + Subclass() { + // TODO Still need to investigate if it's proper to allow such reassignment + // We may as well say "f is alreayd initializaed" so f can't be reassigned. + // The way to implement it is to check @UnderInitialization(SuperMethodInvocation.class) + // and f is within the class hierarchy range Object.class ~ SuperMethodInvocation.class, + // so forbid reassigning it. + this.f = new @Immutable Object(); + } + + // Yes, the overriding method can be contravariant(going to supertype) in terms of + // receiver and formal parameters. This ensures that all the existing method invocation + // won't break just because maybe some days later, the method is overriden in the + // subclass :) + @Override + void foo(@Readonly Subclass this) { + // But this super method invocation definitely shouldn't typecheck. "super" has the same + // mutability as the declared "this" parameter. Because the declared receiver can now + // be passed in @Immutable objects, if we allowed this super invocation, then its abstract + // state will be changed and immutability guarantee will be compromised. So, we still + // retain the standard/default typechecking rules for calling super method using "super" + // :: error: (method.invocation.invalid) + super.foo(); + } + + public static void main(String[] args) { + // Example that illustrates the point above is here: calling foo() method will alter the + // abstract state of sub object, which should be @Immutable + @Immutable Subclass sub = new @Immutable Subclass(); + sub.foo(); + } +} diff --git a/checker/tests/immutability/SupportedBuilderPattern.java b/checker/tests/immutability/SupportedBuilderPattern.java new file mode 100644 index 00000000000..2bf38b58eba --- /dev/null +++ b/checker/tests/immutability/SupportedBuilderPattern.java @@ -0,0 +1,56 @@ +import org.checkerframework.checker.immutability.qual.Immutable; +import org.checkerframework.checker.immutability.qual.Mutable; +import org.checkerframework.checker.immutability.qual.PolyMutable; +import org.checkerframework.checker.immutability.qual.ReceiverDependantMutable; + +import java.util.Date; + +@ReceiverDependantMutable +public class SupportedBuilderPattern { + private final int id; + private String address; + private @Immutable Date date; + + private @ReceiverDependantMutable SupportedBuilderPattern(Builder builder) { + this.id = builder.id; + this.address = builder.address; + this.date = builder.date; + } + + public static class Builder { + private final int id; + private String address; + private @Immutable Date date; + + // :: error: (initialization.fields.uninitialized) + public Builder(int id) { + this.id = id; + } + + public Builder withAddress(String address) { + this.address = address; + return this; + } + + public Builder withDate(@Immutable Date date) { + this.date = date; + return this; + } + + public @PolyMutable SupportedBuilderPattern build() { + return new @PolyMutable SupportedBuilderPattern(this); + } + } +} + +class Test { + public static void main(String[] args) { + SupportedBuilderPattern.@Mutable Builder builder = new SupportedBuilderPattern.Builder(0); + @Mutable + SupportedBuilderPattern msbp = + builder.withAddress("10 King St.").withDate(new @Immutable Date()).build(); + @Immutable + SupportedBuilderPattern imsbp = + builder.withAddress("1 Lester St.").withDate(new @Immutable Date()).build(); + } +} diff --git a/checker/tests/immutability/ThrowableOverridingError.java b/checker/tests/immutability/ThrowableOverridingError.java new file mode 100644 index 00000000000..a8137b5537f --- /dev/null +++ b/checker/tests/immutability/ThrowableOverridingError.java @@ -0,0 +1,16 @@ +import org.checkerframework.checker.immutability.qual.Readonly; + +class A extends Throwable { + @Override + public String getMessage(@Readonly A this) { + return super.getMessage(); + } +} + +public class ThrowableOverridingError extends Throwable { + + @Override + public String getMessage() { + return super.getMessage(); + } +} diff --git a/checker/tests/immutability/Transitive.java b/checker/tests/immutability/Transitive.java new file mode 100644 index 00000000000..5f85d964eb8 --- /dev/null +++ b/checker/tests/immutability/Transitive.java @@ -0,0 +1,45 @@ +import org.checkerframework.checker.immutability.qual.Readonly; + +public class Transitive { + + // class A, B, C are not annotated to test transitive mutability by default. + + static class A { + B b; + + public B getB() { + return b; + } + } + + static class B { + int field = 0; + C c; + + public C getC() { + return c; + } + } + + static class C { + int field = 0; + } + + static class Caller { + void test(@Readonly A a) { + // :: error: (illegal.field.write) + a.b.field = 1; + // :: error: (method.invocation.invalid) + a.getB().field = 1; + + // :: error: (illegal.field.write) + a.b.c.field = 1; + // :: error: (method.invocation.invalid) + a.getB().getC().field = 1; + // :: error: (method.invocation.invalid) + a.b.getC().field = 1; + // :: error: (method.invocation.invalid) + a.getB().c.field = 1; + } + } +} diff --git a/checker/tests/immutability/TwoDimensionalArray.java b/checker/tests/immutability/TwoDimensionalArray.java new file mode 100644 index 00000000000..fac41d2c065 --- /dev/null +++ b/checker/tests/immutability/TwoDimensionalArray.java @@ -0,0 +1,25 @@ +import org.checkerframework.checker.immutability.qual.Immutable; +import org.checkerframework.checker.immutability.qual.Mutable; +import org.checkerframework.checker.immutability.qual.ReceiverDependantMutable; + +// A (main modifier is here) [] [] +public class TwoDimensionalArray { + + public double @ReceiverDependantMutable [][] test() { + @Immutable + double @ReceiverDependantMutable [] @Mutable [] C = + new @Immutable double @ReceiverDependantMutable [0][0]; + for (@Immutable int i = 0; i < 0; i++) { + for (@Immutable int j = 0; j < 0; j++) { + // Array C's main modifier is @ReceiverDependantMutable, so mutating C is not + // allowed + // :: error: (illegal.array.write) + C[i] = new double[] {1.0}; + // But C[i] is double @Mutable [](mutable array of double elements), so mutating + // C[i] is ALLOWED + C[i][j] = 1.0; + } + } + return C; + } +} diff --git a/checker/tests/immutability/TypeParameterFieldRDA.java b/checker/tests/immutability/TypeParameterFieldRDA.java new file mode 100644 index 00000000000..7938df72700 --- /dev/null +++ b/checker/tests/immutability/TypeParameterFieldRDA.java @@ -0,0 +1,21 @@ +import org.checkerframework.checker.immutability.qual.Immutable; +import org.checkerframework.checker.immutability.qual.Mutable; + +@Immutable +public class TypeParameterFieldRDA { + T t; // RDA + + TypeParameterFieldRDA(T t) { + this.t = t; + } +} + +class A { + static void foo() { + @Immutable + TypeParameterFieldRDA<@Mutable Object> t = + new TypeParameterFieldRDA<>(new @Mutable Object()); + // :: error: (illegal.field.write) + t.t = new @Mutable Object(); + } +} diff --git a/checker/tests/immutability/UnaryAndCompoundAssignment.java b/checker/tests/immutability/UnaryAndCompoundAssignment.java new file mode 100644 index 00000000000..0a4bd6b189e --- /dev/null +++ b/checker/tests/immutability/UnaryAndCompoundAssignment.java @@ -0,0 +1,21 @@ +import org.checkerframework.checker.immutability.qual.*; + +public class UnaryAndCompoundAssignment { + + int counter = 0; + + public static void main(String[] args) { + UnaryAndCompoundAssignment t = new UnaryAndCompoundAssignment(); + t.next(); + } + + public void next(@Readonly UnaryAndCompoundAssignment this) { + int lcouter = 0; + lcouter++; + // :: error: (illegal.field.write) + counter++; + lcouter += 5; + // :: error: (illegal.field.write) + counter += 5; + } +} diff --git a/checker/tests/immutability/UnaryOperators.java b/checker/tests/immutability/UnaryOperators.java new file mode 100644 index 00000000000..4f1bb792fc8 --- /dev/null +++ b/checker/tests/immutability/UnaryOperators.java @@ -0,0 +1,13 @@ +public class UnaryOperators { + void test() { + int result = +1; + int a = result--; + int b = result++; + result = -result; + System.out.println(result); + boolean success = false; + System.out.println(success); + Integer i = 0; + i += 2; + } +} diff --git a/checker/tests/immutability/UnsupportedCarBuilder.java b/checker/tests/immutability/UnsupportedCarBuilder.java new file mode 100644 index 00000000000..a2cddf839a8 --- /dev/null +++ b/checker/tests/immutability/UnsupportedCarBuilder.java @@ -0,0 +1,73 @@ +import org.checkerframework.checker.immutability.qual.Immutable; +import org.checkerframework.checker.immutability.qual.Readonly; +import org.checkerframework.checker.immutability.qual.ReceiverDependantMutable; + +/** + * This builder pattern is NOT supported if the builder internally holds the constructed object + * rather than having the representation states. Update: this is fake builder pattern from + * Wikipedia! + */ +public class UnsupportedCarBuilder { + @ReceiverDependantMutable + class Car { + private int wheels; + private String color; + + // :: error: (initialization.fields.uninitialized) + private @Immutable Car() {} + + public String getColor(@Readonly Car this) { + return color; + } + + private void setColor(final String color) { + this.color = color; + } + + public int getWheels(@Readonly Car this) { + return wheels; + } + + private void setWheels(final int wheels) { + this.wheels = wheels; + } + + @Override + public String toString(@Readonly Car this) { + return "Car [wheels = " + wheels + ", color = " + color + "]"; + } + } + + private @Immutable Car car; + + public UnsupportedCarBuilder() { + car = new @Immutable Car(); + } + + public UnsupportedCarBuilder setColor(final String color) { + // :: error: (method.invocation.invalid) + car.setColor(color); + return this; + } + + public UnsupportedCarBuilder setWheels(final int wheels) { + // :: error: (method.invocation.invalid) + car.setWheels(wheels); + return this; + } + + public static UnsupportedCarBuilder getBuilder() { + return new UnsupportedCarBuilder(); + } + + public @Immutable Car build() { + return car; + } +} + +class A { + void foo() { + UnsupportedCarBuilder.@Immutable Car car = + UnsupportedCarBuilder.getBuilder().setColor("red").setWheels(4).build(); + } +} diff --git a/checker/tests/immutability/ViewpointAdaptationRules.java b/checker/tests/immutability/ViewpointAdaptationRules.java new file mode 100644 index 00000000000..5db418854ac --- /dev/null +++ b/checker/tests/immutability/ViewpointAdaptationRules.java @@ -0,0 +1,177 @@ +import org.checkerframework.checker.immutability.qual.Assignable; +import org.checkerframework.checker.immutability.qual.Immutable; +import org.checkerframework.checker.immutability.qual.Mutable; +import org.checkerframework.checker.immutability.qual.Readonly; +import org.checkerframework.checker.immutability.qual.ReceiverDependantMutable; + +@ReceiverDependantMutable +public class ViewpointAdaptationRules { + + @Assignable @Readonly Object rof; + @ReceiverDependantMutable Object rdmf; + @Immutable Object imf; + @Assignable @Mutable Object mf; + + @ReceiverDependantMutable + ViewpointAdaptationRules( + @Readonly Object rof, + @ReceiverDependantMutable Object rdmf, + @Immutable Object imf, + @Mutable Object mf) { + this.rof = rof; + this.rdmf = rdmf; + this.imf = imf; + this.mf = mf; + } + + void mutatableReceiver( + @Mutable ViewpointAdaptationRules this, + @Mutable Object mo, + @ReceiverDependantMutable Object po, + @Immutable Object io, + @Readonly Object ro) { + this.rof = mo; + this.rdmf = mo; + // :: error: (assignment.type.incompatible) + this.imf = mo; + this.mf = mo; + + this.rof = po; + // :: error: (assignment.type.incompatible) + this.rdmf = po; + // :: error: (assignment.type.incompatible) + this.imf = po; + // :: error: (assignment.type.incompatible) + this.mf = po; + + this.rof = io; + // :: error: (assignment.type.incompatible) + this.rdmf = io; + this.imf = io; + // :: error: (assignment.type.incompatible) + this.mf = io; + + this.rof = ro; + // :: error: (assignment.type.incompatible) + this.rdmf = ro; + // :: error: (assignment.type.incompatible) + this.imf = ro; + // :: error: (assignment.type.incompatible) + this.mf = ro; + } + + void receiverDependantMutableReceiver( + @ReceiverDependantMutable ViewpointAdaptationRules this, + @Mutable Object mo, + @ReceiverDependantMutable Object po, + @Immutable Object io, + @Readonly Object ro) { + + this.rof = mo; + // :: error: (assignment.type.incompatible) :: error: (illegal.field.write) + this.rdmf = mo; + // :: error: (assignment.type.incompatible) :: error: (illegal.field.write) + this.imf = mo; + this.mf = mf; + + this.rof = po; + // :: error: (illegal.field.write) + this.rdmf = po; + // :: error: (assignment.type.incompatible) :: error: (illegal.field.write) + this.imf = po; + // :: error: (assignment.type.incompatible) + this.mf = po; + + this.rof = io; + // :: error: (assignment.type.incompatible) :: error: (illegal.field.write) + this.rdmf = io; + // :: error: (illegal.field.write) + this.imf = io; + // :: error: (assignment.type.incompatible) + this.mf = io; + + this.rof = ro; + // :: error: (assignment.type.incompatible) :: error: (illegal.field.write) + this.rdmf = ro; + // :: error: (assignment.type.incompatible) :: error: (illegal.field.write) + this.imf = ro; + // :: error: (assignment.type.incompatible) + this.mf = ro; + } + + void ImmutableReceiver( + @Immutable ViewpointAdaptationRules this, + @Mutable Object mo, + @ReceiverDependantMutable Object po, + @Immutable Object io, + @Readonly Object ro) { + this.rof = mo; + // :: error: (assignment.type.incompatible) :: error: (illegal.field.write) + this.rdmf = mo; + // :: error: (assignment.type.incompatible) :: error: (illegal.field.write) + this.imf = mo; + this.mf = mf; + + this.rof = po; + // :: error: (assignment.type.incompatible) :: error: (illegal.field.write) + this.rdmf = po; + // :: error: (assignment.type.incompatible) :: error: (illegal.field.write) + this.imf = po; + // :: error: (assignment.type.incompatible) + this.mf = po; + + this.rof = io; + // :: error: (illegal.field.write) + this.rdmf = io; + // :: error: (illegal.field.write) + this.imf = io; + // :: error: (assignment.type.incompatible) + this.mf = io; + + this.rof = ro; + // :: error: (assignment.type.incompatible) :: error: (illegal.field.write) + this.rdmf = ro; + // :: error: (assignment.type.incompatible) :: error: (illegal.field.write) + this.imf = ro; + // :: error: (assignment.type.incompatible) + this.mf = ro; + } + + void ReadonlyReceiver( + @Readonly ViewpointAdaptationRules this, + @Mutable Object mo, + @ReceiverDependantMutable Object po, + @Immutable Object io, + @Readonly Object ro) { + // this.rof = mo; + // :: error: (illegal.field.write) + this.rdmf = mo; + // :: error: (assignment.type.incompatible) :: error: (illegal.field.write) + this.imf = mo; + this.mf = mo; + + this.rof = po; + // :: error: (illegal.field.write) + this.rdmf = po; + // :: error: (assignment.type.incompatible) :: error: (illegal.field.write) + this.imf = po; + // :: error: (assignment.type.incompatible) + this.mf = po; + + this.rof = io; + // :: error: (illegal.field.write) + this.rdmf = io; + // :: error: (illegal.field.write) + this.imf = io; + // :: error: (assignment.type.incompatible) + this.mf = io; + + this.rof = ro; + // :: error: (illegal.field.write) + this.rdmf = ro; + // :: error: (assignment.type.incompatible) :: error: (illegal.field.write) + this.imf = ro; + // :: error: (assignment.type.incompatible) + this.mf = ro; + } +} diff --git a/checker/tests/immutability/WildcardExplicitLowerBound.java b/checker/tests/immutability/WildcardExplicitLowerBound.java new file mode 100644 index 00000000000..4120786a5a4 --- /dev/null +++ b/checker/tests/immutability/WildcardExplicitLowerBound.java @@ -0,0 +1,31 @@ +import org.checkerframework.checker.immutability.qual.Immutable; +import org.checkerframework.checker.immutability.qual.ReceiverDependantMutable; + +import java.util.List; + +class A {} + +@Immutable +class B { + @Immutable + B() {} +} + +@ReceiverDependantMutable +class C { + @ReceiverDependantMutable + C() {} +} + +public class WildcardExplicitLowerBound { + + // Doesn't allow explicit usage of @Bottom on explicit lower bound anymore, as it adds + // difficulty to validate correct usage of that, because in type validator, dataflow + // refinement's result might also causes its warning. There is no way of differentiating + // explicit usage and internal usage right now. + void test1(List l) {} + + void test2(List l) {} + + void test3(List l) {} +} From 3ac4412e5eb0a55268526b0e220351b17ac247d1 Mon Sep 17 00:00:00 2001 From: Aosen Xiong Date: Fri, 10 May 2024 14:24:16 -0400 Subject: [PATCH 02/11] Don't format util the error report location is fixed --- build.gradle | 2 ++ 1 file changed, 2 insertions(+) diff --git a/build.gradle b/build.gradle index b22ea27744a..aa73e2e0672 100644 --- a/build.gradle +++ b/build.gradle @@ -256,6 +256,8 @@ allprojects { '**/returnsreceiverdelomboked/*', '**/build/**', '*/dist/**', + // Don't format util the error report location is fixed + 'checker/tests/immutability/*' ] if (!isJava14plus) { doNotFormat += ['**/*record*/'] From 01fa01cbcae0955bac74434afe085a13d7b00d85 Mon Sep 17 00:00:00 2001 From: Aosen Xiong Date: Fri, 10 May 2024 14:25:00 -0400 Subject: [PATCH 03/11] Fix pico type check error --- .../checker/immutability/PICOChecker.java | 1 - .../immutability/PICONoInitAnalysis.java | 1 - .../immutability/PICONoInitVisitor.java | 93 +----- .../test/junit/ImmutabilityTypecheckTest.java | 8 +- .../tests/immutability/AssignableExample.java | 6 +- .../tests/immutability/CompatabilityBEI1.java | 6 +- .../tests/immutability/CompatibilityBEI2.java | 12 +- .../tests/immutability/FieldsInitialized.java | 3 +- .../tests/immutability/ImmutableClass1.java | 6 +- .../immutability/ImmutableConstructor.java | 3 +- checker/tests/immutability/InvalidBound.java | 6 +- checker/tests/immutability/Planet.java | 2 + checker/tests/immutability/Primitive.java | 3 +- .../immutability/ReadonlyConstructor.java | 3 +- .../ReceiverDependantMutableConstructor.java | 3 +- .../ReceiverTypeOutsideConstructor.java | 39 +-- checker/tests/immutability/Static.java | 1 + checker/tests/immutability/jdk.astub | 300 ++++++++++++++++++ 18 files changed, 342 insertions(+), 154 deletions(-) create mode 100644 checker/tests/immutability/jdk.astub diff --git a/checker/src/main/java/org/checkerframework/checker/immutability/PICOChecker.java b/checker/src/main/java/org/checkerframework/checker/immutability/PICOChecker.java index 918d65c2fcf..4abdb540810 100644 --- a/checker/src/main/java/org/checkerframework/checker/immutability/PICOChecker.java +++ b/checker/src/main/java/org/checkerframework/checker/immutability/PICOChecker.java @@ -7,7 +7,6 @@ import java.util.Map.Entry; import java.util.Set; -/** Created by mier on 20/06/17. */ @SupportedOptions({"printFbcErrors"}) public class PICOChecker extends InitializationChecker { diff --git a/checker/src/main/java/org/checkerframework/checker/immutability/PICONoInitAnalysis.java b/checker/src/main/java/org/checkerframework/checker/immutability/PICONoInitAnalysis.java index 1f6fba80bd2..95d5995493d 100644 --- a/checker/src/main/java/org/checkerframework/checker/immutability/PICONoInitAnalysis.java +++ b/checker/src/main/java/org/checkerframework/checker/immutability/PICONoInitAnalysis.java @@ -7,7 +7,6 @@ import javax.lang.model.type.TypeMirror; -/** Created by mier on 15/08/17. */ public class PICONoInitAnalysis extends CFAbstractAnalysis { diff --git a/checker/src/main/java/org/checkerframework/checker/immutability/PICONoInitVisitor.java b/checker/src/main/java/org/checkerframework/checker/immutability/PICONoInitVisitor.java index 8e269fa90a8..4c549ad00ad 100644 --- a/checker/src/main/java/org/checkerframework/checker/immutability/PICONoInitVisitor.java +++ b/checker/src/main/java/org/checkerframework/checker/immutability/PICONoInitVisitor.java @@ -47,7 +47,6 @@ import javax.lang.model.type.TypeKind; import javax.lang.model.type.TypeMirror; -/** Created by mier on 20/06/17. Enforce PICO type rules. */ public class PICONoInitVisitor extends BaseTypeVisitor { private final boolean shouldOutputFbcError; @@ -89,26 +88,6 @@ public boolean isValidUse( return true; } - // // allow RDM on mutable fields with enclosing class bounded with mutable - // if (tree instanceof VariableTree && useType.isDeclaration()) { - // VariableElement element = - // TreeUtils.elementFromDeclaration((VariableTree)tree); - // if (element.getKind() == ElementKind.FIELD && - // ElementUtils.enclosingClass(element) != null) { - // Set enclosingBound = - // atypeFactory.getTypeDeclarationBounds( - // - // Objects.requireNonNull(ElementUtils.enclosingClass(element)).asType()); - // - // if(declarationType.hasAnnotation(MUTABLE) - // && useType.hasAnnotation(RECEIVER_DEPENDANT_MUTABLE) - // && AnnotationUtils.containsSameByName(enclosingBound, MUTABLE)) { - // return true; - // } - // } - // - // } - AnnotationMirror declared = declarationType.getAnnotationInHierarchy(READONLY); AnnotationMirror used = useType.getAnnotationInHierarchy(READONLY); @@ -194,23 +173,8 @@ protected void checkConstructorInvocation( } /*Copied Code End*/ - // TODO fix inference counterpart, not here - // // CF base check disabled by InitializationVisitor - // // if no explicit anno it must inherited from class decl - // AnnotationMirror declAnno = - // constructor.getReturnType().getAnnotationInHierarchy(READONLY); - // AnnotationMirror useAnno = invocation.getAnnotationInHierarchy(READONLY); - // declAnno = declAnno == null ? MUTABLE : declAnno; - // - // if(useAnno != null && !AnnotationUtils.areSameByName(declAnno, POLY_MUTABLE) && - // !isAdaptedSubtype(useAnno, declAnno)) { - // checker.reportError(newClassTree, "type.invalid.annotations.on.use", declAnno, - // useAnno); - // } - // The immutability return qualifier of the constructor (returnType) must be supertype of - // the - // constructor invocation immutability qualifier(invocation). + // the constructor invocation immutability qualifier(invocation). if (!atypeFactory .getQualifierHierarchy() .isSubtypeQualifiersOnly( @@ -498,39 +462,6 @@ public Void visitMethodInvocation(MethodInvocationTree node, Void p) { return null; } - // @Override - // protected void checkFieldsInitialized( - // Tree blockNode, - // boolean staticFields, - // PICONoInitStore store, - // List receiverAnnotations) { - // // If a class doesn't have constructor, it cannot be initialized as @Immutable, - // therefore no - // // need to check uninitialized fields - // if (TreeUtils.isClassTree(blockNode)) return; - // if (blockNode.getKind() == Kind.METHOD && TreeUtils.isConstructor((MethodTree) - // blockNode)) { - // // Only raise errors when in @Immutable or @ReceiverDependantMutable constructors. - // As - // // @Mutable constructor can initialized - // // those fields out of constructor - // MethodTree methodTree = (MethodTree) blockNode; - // AnnotatedExecutableType executableType = - // atypeFactory.getAnnotatedType(methodTree); - // AnnotatedDeclaredType constructorReturnType = - // (AnnotatedDeclaredType) executableType.getReturnType(); - // // Only care abstract state initialization in @Immutable and - // @ReceiverDependantMutable - // // constructors, as @Mutable constructors - // // only allows instantiating @Mutable objects and fields can be initialized later - // if (!(constructorReturnType.hasAnnotation(IMMUTABLE) - // || constructorReturnType.hasAnnotation(RECEIVER_DEPENDANT_MUTABLE))) { - // return; - // } - // } - // super.checkFieldsInitialized(blockNode, staticFields, store, receiverAnnotations); - // } - @Override protected AnnotationMirrorSet getExceptionParameterLowerBoundAnnotations() { AnnotationMirrorSet result = new AnnotationMirrorSet(); @@ -549,8 +480,7 @@ protected AnnotationMirrorSet getThrowUpperBoundAnnotations() { public void processClassTree(ClassTree node) { TypeElement typeElement = TreeUtils.elementFromDeclaration(node); // TODO Don't process anonymous class. I'm not even sure if whether - // processClassTree(ClassTree) is - // called on anonymous class tree + // processClassTree(ClassTree) is called on anonymous class tree if (typeElement.toString().contains("anonymous")) { super.processClassTree(node); return; @@ -593,25 +523,6 @@ public void processClassTree(ClassTree node) { } } } - - // // field of mutable class cannot use RDM in immutable class - // // Condition: - // // * Class decl == Immutable - // // * Member is field (variable) - // // * Member's declared bound == Mutable - // // * Member's use anno == RDM - // if (bound.hasAnnotation(IMMUTABLE)) { - // for(Tree member : node.getMembers()) { - // if(member.getKind() == Kind.VARIABLE) { - // AnnotatedTypeMirror fieldAtm = atypeFactory.getAnnotatedType(member); - // if (fieldAtm.hasAnnotation(RECEIVER_DEPENDANT_MUTABLE) && - // - // AnnotationUtils.containsSameByName(atypeFactory.getTypeDeclarationBounds(fieldAtm.getUnderlyingType()), MUTABLE)) { - // checker.reportError(member, "test-key-1"); - // } - // } - // } - // } super.processClassTree(node); } diff --git a/checker/src/test/java/org/checkerframework/checker/test/junit/ImmutabilityTypecheckTest.java b/checker/src/test/java/org/checkerframework/checker/test/junit/ImmutabilityTypecheckTest.java index 0cb0e8563f6..ff6edc8bad4 100644 --- a/checker/src/test/java/org/checkerframework/checker/test/junit/ImmutabilityTypecheckTest.java +++ b/checker/src/test/java/org/checkerframework/checker/test/junit/ImmutabilityTypecheckTest.java @@ -8,7 +8,13 @@ public class ImmutabilityTypecheckTest extends CheckerFrameworkPerFileTest { public ImmutabilityTypecheckTest(File testFile) { - super(testFile, PICOChecker.class, "immutability", "-Anomsgtext", "-Anocheckjdk"); + super( + testFile, + PICOChecker.class, + "immutability", + "-Astubs=tests/immutability/jdk.astub", + "-Anomsgtext", + "-Anocheckjdk"); } @Parameters diff --git a/checker/tests/immutability/AssignableExample.java b/checker/tests/immutability/AssignableExample.java index 0f3bcb2d644..14c2ce66ec6 100644 --- a/checker/tests/immutability/AssignableExample.java +++ b/checker/tests/immutability/AssignableExample.java @@ -12,8 +12,7 @@ public class AssignableExample { @Assignable @Immutable Date assignableDate; // :: error: (initialization.fields.uninitialized) - @Mutable - AssignableExample() { + @Mutable AssignableExample() { o = new @Immutable Object(); } @@ -29,8 +28,7 @@ void foo2(@Mutable AssignableExample this) { } // :: error: (super.invocation.invalid) -@ReceiverDependantMutable -class Subclass extends AssignableExample { +@ReceiverDependantMutable class Subclass extends AssignableExample { void bar(@Immutable Subclass this) { // :: error: (illegal.field.write) this.date = new @Immutable Date(); diff --git a/checker/tests/immutability/CompatabilityBEI1.java b/checker/tests/immutability/CompatabilityBEI1.java index 28a826a5261..231192af741 100644 --- a/checker/tests/immutability/CompatabilityBEI1.java +++ b/checker/tests/immutability/CompatabilityBEI1.java @@ -33,15 +33,13 @@ class H extends Object {} class I implements @Mutable Cloneable {} // :: error: (declaration.inconsistent.with.implements.clause) -@Mutable -class J implements @Immutable Cloneable {} +@Mutable class J implements @Immutable Cloneable {} @Mutable class K implements @ReceiverDependantMutable Cloneable {} // :: error: (declaration.inconsistent.with.extends.clause) -@Immutable -class L extends @Mutable Object {} +@Immutable class L extends @Mutable Object {} @Immutable class M extends @Immutable Object {} diff --git a/checker/tests/immutability/CompatibilityBEI2.java b/checker/tests/immutability/CompatibilityBEI2.java index 857f53c525e..53e4fa5c10d 100644 --- a/checker/tests/immutability/CompatibilityBEI2.java +++ b/checker/tests/immutability/CompatibilityBEI2.java @@ -27,15 +27,13 @@ class H extends ArrayList<@Immutable Object> {} abstract class I implements @Mutable List<@Immutable Object> {} // :: error: (declaration.inconsistent.with.implements.clause) -@Mutable -abstract class J implements @Immutable List<@Immutable Object> {} +@Mutable abstract class J implements @Immutable List<@Immutable Object> {} @Mutable abstract class K implements @ReceiverDependantMutable List<@Immutable Object> {} // :: error: (declaration.inconsistent.with.extends.clause) -@Immutable -class L extends @Mutable ArrayList<@Immutable Object> {} +@Immutable class L extends @Mutable ArrayList<@Immutable Object> {} @Immutable class M extends @Immutable ArrayList<@Immutable Object> {} @@ -49,12 +47,10 @@ abstract class O implements CharSequence {} interface ImmutableInterface {} // :: error: (type.invalid.annotations.on.use) -@Mutable -abstract class P implements ImmutableInterface<@Mutable Object> {} +@Mutable abstract class P implements ImmutableInterface<@Mutable Object> {} @Immutable abstract class Q implements ImmutableInterface<@Immutable Object> {} // :: error: (type.invalid.annotations.on.use) -@ReceiverDependantMutable -abstract class R implements ImmutableInterface<@ReceiverDependantMutable Object> {} +@ReceiverDependantMutable abstract class R implements ImmutableInterface<@ReceiverDependantMutable Object> {} diff --git a/checker/tests/immutability/FieldsInitialized.java b/checker/tests/immutability/FieldsInitialized.java index ee1b08f41ed..6d6d88cc8cf 100644 --- a/checker/tests/immutability/FieldsInitialized.java +++ b/checker/tests/immutability/FieldsInitialized.java @@ -19,8 +19,7 @@ public class FieldsInitialized { @Assignable @Readonly Object f10; // :: error: (initialization.fields.uninitialized) - @ReceiverDependantMutable - FieldsInitialized() { + @ReceiverDependantMutable FieldsInitialized() { f1 = new @Immutable Object(); f2 = new @Immutable Object(); f3 = new @ReceiverDependantMutable Object(); diff --git a/checker/tests/immutability/ImmutableClass1.java b/checker/tests/immutability/ImmutableClass1.java index 9f63f93c1bd..e8f80bde1e1 100644 --- a/checker/tests/immutability/ImmutableClass1.java +++ b/checker/tests/immutability/ImmutableClass1.java @@ -7,12 +7,10 @@ @Immutable class ImmutableClass1 { // :: error: (type.invalid.annotations.on.use) - @Mutable - ImmutableClass1(Object o) {} + @Mutable ImmutableClass1(Object o) {} // :: error: (type.invalid.annotations.on.use) - @ReceiverDependantMutable - ImmutableClass1() {} + @ReceiverDependantMutable ImmutableClass1() {} @Immutable ImmutableClass1(@Immutable Number n) {} diff --git a/checker/tests/immutability/ImmutableConstructor.java b/checker/tests/immutability/ImmutableConstructor.java index b7c8af0827c..4d88ea8fd21 100644 --- a/checker/tests/immutability/ImmutableConstructor.java +++ b/checker/tests/immutability/ImmutableConstructor.java @@ -11,8 +11,7 @@ public class ImmutableConstructor { @Immutable Object imf; // :: error: (initialization.fields.uninitialized) - @Immutable - ImmutableConstructor( + @Immutable ImmutableConstructor( @Mutable Object mo, @ReceiverDependantMutable Object po, @Immutable Object io) {} // Even if the first argument is @ReceiverDependantMutable, aliased @Mutable object cannot be diff --git a/checker/tests/immutability/InvalidBound.java b/checker/tests/immutability/InvalidBound.java index f9f15da1877..e1af6914b09 100644 --- a/checker/tests/immutability/InvalidBound.java +++ b/checker/tests/immutability/InvalidBound.java @@ -5,12 +5,10 @@ import org.checkerframework.checker.immutability.qual.ReceiverDependantMutable; // :: error: (class.bound.invalid) -@Readonly -public class InvalidBound {} +@Readonly public class InvalidBound {} // :: error: (class.bound.invalid) -@PolyMutable -class A {} +@PolyMutable class A {} // ok @Immutable diff --git a/checker/tests/immutability/Planet.java b/checker/tests/immutability/Planet.java index 9b2921665c7..2a151a9f3e0 100644 --- a/checker/tests/immutability/Planet.java +++ b/checker/tests/immutability/Planet.java @@ -2,6 +2,8 @@ import org.checkerframework.checker.immutability.qual.Mutable; import org.checkerframework.checker.immutability.qual.Readonly; +import java.util.Date; + /** Planet is an immutable class, since there is no way to change its state after construction. */ @Immutable public class Planet { diff --git a/checker/tests/immutability/Primitive.java b/checker/tests/immutability/Primitive.java index f5158b8c3fe..dde1ab53920 100644 --- a/checker/tests/immutability/Primitive.java +++ b/checker/tests/immutability/Primitive.java @@ -19,8 +19,7 @@ public class Primitive { @ReceiverDependantMutable int invalidInt2; // :: error: (initialization.fields.uninitialized) - @Immutable - Primitive() { + @Immutable Primitive() { // Allowed within constructor implicitImmutableInt = 0; } diff --git a/checker/tests/immutability/ReadonlyConstructor.java b/checker/tests/immutability/ReadonlyConstructor.java index 56f45317b8d..99b951dbc46 100644 --- a/checker/tests/immutability/ReadonlyConstructor.java +++ b/checker/tests/immutability/ReadonlyConstructor.java @@ -3,6 +3,5 @@ public class ReadonlyConstructor { // :: error: (constructor.return.invalid) - @Readonly - ReadonlyConstructor() {} + @Readonly ReadonlyConstructor() {} } diff --git a/checker/tests/immutability/ReceiverDependantMutableConstructor.java b/checker/tests/immutability/ReceiverDependantMutableConstructor.java index 05263b815b0..223125c6d43 100644 --- a/checker/tests/immutability/ReceiverDependantMutableConstructor.java +++ b/checker/tests/immutability/ReceiverDependantMutableConstructor.java @@ -11,8 +11,7 @@ public class ReceiverDependantMutableConstructor { @Immutable Object imf; // :: error: (initialization.fields.uninitialized) - @ReceiverDependantMutable - ReceiverDependantMutableConstructor( + @ReceiverDependantMutable ReceiverDependantMutableConstructor( @Mutable Object mo, @ReceiverDependantMutable Object po, @Immutable Object io) {} @ReceiverDependantMutable diff --git a/checker/tests/immutability/ReceiverTypeOutsideConstructor.java b/checker/tests/immutability/ReceiverTypeOutsideConstructor.java index 623101e0e29..3a633908dd1 100644 --- a/checker/tests/immutability/ReceiverTypeOutsideConstructor.java +++ b/checker/tests/immutability/ReceiverTypeOutsideConstructor.java @@ -20,14 +20,12 @@ class A { } // :: error: (type.invalid.annotations.on.use) - @ReceiverDependantMutable - A(Object o1) { + @ReceiverDependantMutable A(Object o1) { d = new @ReceiverDependantMutable Date(); } // :: error: (type.invalid.annotations.on.use) - @Mutable - A(Object o1, Object o2) { + @Mutable A(Object o1, Object o2) { d = new @Mutable Date(); } } @@ -36,19 +34,16 @@ class A { class AIMS extends A {} // :: error: (type.invalid.annotations.on.use) :: error: (super.invocation.invalid) -@ReceiverDependantMutable -class ARDMS extends A {} +@ReceiverDependantMutable class ARDMS extends A {} // :: error: (type.invalid.annotations.on.use) :: error: (super.invocation.invalid) -@Mutable -class AMS extends A {} +@Mutable class AMS extends A {} // :: error: (type.invalid.annotations.on.use) :: error: (super.invocation.invalid) class AUNKS extends A {} // ReceiverDependantMutable class -@ReceiverDependantMutable -class B { +@ReceiverDependantMutable class B { @ReceiverDependantMutable Date d = new @ReceiverDependantMutable Date(); { @@ -79,12 +74,10 @@ class B { class BIMS extends B {} // :: error: (super.invocation.invalid) -@ReceiverDependantMutable -class BRDMS extends B {} +@ReceiverDependantMutable class BRDMS extends B {} // :: error: (super.invocation.invalid) -@Mutable -class BMS extends B {} +@Mutable class BMS extends B {} // mutable by default(TODO Does this make sense compared to defaulting to // receiver-dependant-mutable?) @@ -92,8 +85,7 @@ class BMS extends B {} class BUNKS extends B {} // Mutable class -@Mutable -class C { +@Mutable class C { @ReceiverDependantMutable Date d = new @Mutable Date(); { @@ -102,14 +94,12 @@ class C { } // :: error: (type.invalid.annotations.on.use) - @Immutable - C() { + @Immutable C() { d = new @Immutable Date(); } // :: error: (type.invalid.annotations.on.use) - @ReceiverDependantMutable - C(Object o1) { + @ReceiverDependantMutable C(Object o1) { d = new @ReceiverDependantMutable Date(); } @@ -120,16 +110,13 @@ class C { } // :: error: (type.invalid.annotations.on.use) -@Immutable -class CIMS extends C {} +@Immutable class CIMS extends C {} // :: error: (type.invalid.annotations.on.use) :: error: (super.invocation.invalid) -@ReceiverDependantMutable -class CRDMS extends C {} +@ReceiverDependantMutable class CRDMS extends C {} // :: error: (super.invocation.invalid) -@Mutable -class CMS extends C {} +@Mutable class CMS extends C {} // :: error: (super.invocation.invalid) class CUNKS extends C {} diff --git a/checker/tests/immutability/Static.java b/checker/tests/immutability/Static.java index 359a58d98ae..ee07e9e3472 100644 --- a/checker/tests/immutability/Static.java +++ b/checker/tests/immutability/Static.java @@ -28,6 +28,7 @@ void twoRuntimeSemantics() { // :: error: (static.receiverdependantmutable.forbidden) static @ReceiverDependantMutable Object readStaticReceiverDependantMutableField( + // :: error: (static.receiverdependantmutable.forbidden) @ReceiverDependantMutable Object p) { return o; // TODO Avoid warnings for receiverdependantmutable fields in anonymous class diff --git a/checker/tests/immutability/jdk.astub b/checker/tests/immutability/jdk.astub new file mode 100644 index 00000000000..995518cadbb --- /dev/null +++ b/checker/tests/immutability/jdk.astub @@ -0,0 +1,300 @@ +import org.checkerframework.checker.immutability.qual.Mutable; +import org.checkerframework.checker.immutability.qual.Immutable; +import org.checkerframework.checker.immutability.qual.ReceiverDependantMutable; +import org.checkerframework.checker.immutability.qual.Readonly; +import org.checkerframework.checker.immutability.qual.ObjectIdentityMethod; +import java.util.Collection; + +package java.lang; + +@ReceiverDependantMutable +class Object { + @ReceiverDependantMutable Object(); + Class getClass(@Readonly Object this); + String toString(@Readonly Object this); + int hashCode(@Readonly Object this); + boolean equals(@Readonly Object this, @Readonly Object var1); + @ReceiverDependantMutable Object clone(@ReceiverDependantMutable Object this); + @ObjectIdentityMethod + final native Class getClass(); +} + +class String { + int length(@Immutable String this); + char charAt(@Immutable String this, int var1); + String replace(@Readonly CharSequence target, @Readonly CharSequence replacement); + boolean contains(@Readonly CharSequence s); + String substring(@Immutable String this, int var1); + String substring(@Immutable String this, int var1, int var2); + String toString(@Immutable String this); + boolean equals(@Immutable Object var1); + static String valueOf(@Readonly Object var0); + static String format(String var0, @Readonly Object @Readonly ... var1); + static String format(@Readonly Locale l, String format, @Readonly Object @Readonly ... var1); +} + +class StringBuilder { + StringBuilder append(@Readonly Object var1); +} + +class StringBuffer { + int length(@Readonly StringBuffer this); + int capacity(@Readonly StringBuffer this); + StringBuffer append(@Readonly Object obj); + String substring(@Readonly StringBuffer this, int start); + CharSequence subSequence(@Readonly StringBuffer this, int start, int end); + String substring(@Readonly StringBuffer this, int start, int end); + int indexOf(@Readonly StringBuffer this, String str); + int indexOf(@Readonly StringBuffer this, String str, int fromIndex); + int lastIndexOf(@Readonly StringBuffer this, String str); + int lastIndexOf(@Readonly StringBuffer this, String str, int fromIndex); +} + +@ReceiverDependantMutable +class Throwable { + String getMessage(@ReceiverDependantMutable Throwable this); + String getLocalizedMessage(@ReceiverDependantMutable Throwable this); + Throwable getCause(@ReceiverDependantMutable Throwable this); + void printStackTrace(@ReceiverDependantMutable Throwable this); + void printStackTrace(@ReceiverDependantMutable Throwable this, PrintStream var1); + void printStackTrace(@ReceiverDependantMutable Throwable this, Throwable.PrintStreamOrWriter var1); +} + +@ReceiverDependantMutable +interface CharSequence { + int length(@Readonly CharSequence this); + char charAt(@Readonly CharSequence this, int index); + CharSequence subSequence(@Readonly CharSequence this, int start, int end); + public default IntStream chars(@Readonly CharSequence this); + public default IntStream codePoints(@Readonly CharSequence this); +} + +@ReceiverDependantMutable +class RuntimeException { + @ReceiverDependantMutable RuntimeException(@Readonly Throwable var1); + @ReceiverDependantMutable RuntimeException(String var1, @Readonly Throwable var2, boolean var3, boolean var4); +} + +@ReceiverDependantMutable +class IndexOutOfBoundsException {} + +@Immutable +class Enum> { + @Immutable Enum(String name, int ordinal); + int ordinal(@Immutable Enum this); +} + +@ReceiverDependantMutable +interface Cloneable {} + +@ReceiverDependantMutable +interface Comparable {} + +package java.util; + +@ReceiverDependantMutable +class Properties { + @Readonly Object put(@Immutable Object key, @Readonly Object value); +} + +interface Iterator {} + +@ReceiverDependantMutable +class Date { + @ReceiverDependantMutable Date(); + @ReceiverDependantMutable Date(long var1); + int getHours(@ReceiverDependantMutable Date this); +} + +@ReceiverDependantMutable +interface Collection { + boolean contains(@Readonly Collection this, @Readonly Object o); +} + +@ReceiverDependantMutable +public abstract class AbstractCollection implements Collection { + public abstract int size(@Readonly AbstractCollection this); +} + +@ReceiverDependantMutable +class ArrayList { + @ReceiverDependantMutable ArrayList(); + @ReceiverDependantMutable ArrayList(@Readonly Collection var1); + boolean add(E var1); + boolean addAll(@Readonly Collection c); + E get(@Readonly ArrayList this, int index); + int size(@Readonly ArrayList this); + boolean isEmpty(@Readonly ArrayList this); + boolean contains(@Readonly ArrayList this, @Readonly Object o); + int indexOf(@Readonly ArrayList this, @Readonly Object o); + int lastIndexOf(@Readonly ArrayList this, @Readonly Object o); + Iterator iterator(@Readonly ArrayList this); +} + +@ReceiverDependantMutable +interface List { + int size(@Readonly List this); + boolean isEmpty(@Readonly List this); + Iterator iterator(@Readonly List this); + Object[] toArray(@Readonly List this); + T[] toArray(@Readonly List this, T[] a); + boolean containsAll(@Readonly List this, @Readonly Collection c); + E get(@Readonly List this, int index); + boolean contains(@Readonly List this, @Readonly Object o); + boolean remove(@Readonly Object o); + boolean removeAll(@Readonly Collection c); + boolean addAll(@Readonly Collection c); + boolean addAll(int index, @Readonly Collection c); + int indexOf(@Readonly List this, @Readonly Object o); + int lastIndexOf(@Readonly List this, @Readonly Object o); + ListIterator listIterator(@Readonly List this); + ListIterator listIterator(@Readonly List this, int index); +} + +@ReceiverDependantMutable +class AbstractList { + @ReceiverDependantMutable AbstractList(); + void add(@Mutable AbstractList this, int var1, E var2); +} + +@ReceiverDependantMutable +interface Set { + int size(@Readonly Set this); + boolean isEmpty(@Readonly Set this); + boolean contains(@Readonly Set this, @Readonly Object var1); + Iterator iterator(@Readonly Set this); + Object[] toArray(@Readonly Set this); + T[] toArray(@Readonly Set this, T[] a); + boolean containsAll(@Readonly Set this, @Readonly Collection c); + boolean remove(@Readonly Object o); + boolean addAll(@Readonly Collection c); +} + +@ReceiverDependantMutable +class HashSet { + @ReceiverDependantMutable HashSet(); + @ReceiverDependantMutable HashSet(@Readonly Collection var1); + boolean contains(@Readonly HashSet this, @Readonly Object var1); + boolean remove(@Readonly Object var1); +} + +@ReceiverDependantMutable +interface Map { + int size(@Readonly Map this); + boolean isEmpty(@Readonly Map this); + boolean containsKey(@Readonly Map this, @Readonly Object var1); + boolean containsValue(@Readonly Map this, @Readonly Object value); + V get(@Readonly Map this, @Readonly Object var1); + V remove(@Readonly Object key); + void putAll(@Readonly Map m); + Set keySet(@Readonly Map this); + Collection values(@Readonly Map this); + Set> entrySet(@Readonly Map this); +} + +@ReceiverDependantMutable +class HashMap { + @ReceiverDependantMutable HashMap(); + @ReceiverDependantMutable HashMap(@Readonly Map var1); + V get(@Readonly HashMap this, @Readonly Object key); + boolean containsKey(@Readonly HashMap this, @Readonly Object key); + boolean containsValue(@Readonly HashMap this, @Readonly Object value); +} + +class Collections { + static @Immutable List unmodifiableList(@Readonly List list); +} + +class StringJoiner { + StringJoiner(@Readonly CharSequence delimiter); + StringJoiner(@Readonly CharSequence delimiter, @Readonly CharSequence prefix, @Readonly CharSequence suffix); + StringJoiner add(@Readonly CharSequence newElement); +} + +class Arrays { + static @Immutable List asList(T @Readonly ... var0); + static String toString(int @Readonly [] var0); + static boolean equals(float @Readonly [] var0, float @Readonly [] var1); + static boolean equals(double @Readonly [] var0, double @Readonly [] var1); + static T[] copyOf(T @Readonly [] original, int newLength); +} + +class Objects { + static int hashCode(@Readonly Object o); + static boolean equals(@Readonly Object a, @Readonly Object b); +} + +@ReceiverDependantMutable +class Stack { + E peek(@ReceiverDependantMutable Stack this); + boolean empty(@ReceiverDependantMutable Stack this); +} + +@ReceiverDependantMutable +class Vector { + boolean isEmpty(@Readonly Vector this); +} + +@ReceiverDependantMutable +class Hashtable { + V get(@Readonly Hashtable this, @Readonly Object key); + boolean containsKey(@Readonly Hashtable this, @Readonly Object key); +} + +package java.util.logging; +class Logger { + void log(@Readonly Level level, String msg, @Readonly Throwable thrown); +} + +package java.util.regex; +class Pattern { + Matcher matcher(@Readonly CharSequence input); + static boolean matches(String regex, @Readonly CharSequence input); + String[] split(@Readonly CharSequence input, int limit); + String[] split(@Readonly CharSequence input); + static final int countChars(@Readonly CharSequence seq, int index, int lengthInCodePoints); + static final int countCodePoints(@Readonly CharSequence seq); +} + +package java.io; + +@ReceiverDependantMutable +class PrintStream { + void print(@ReceiverDependantMutable PrintStream this, String var1); + PrintStream printf(@ReceiverDependantMutable PrintStream this, String var1, @Readonly Object @Readonly ... var2); + PrintStream format(String format, @Readonly Object @Readonly ... args); +} + +@ReceiverDependantMutable +class PrintWriter { + PrintWriter printf(@ReceiverDependantMutable PrintWriter this, String var1, @Readonly Object @Readonly ... var2); +} + +@ReceiverDependantMutable +class File { + @ReceiverDependantMutable File(@Readonly File parent, String child); + boolean isFile(@Readonly File this); + String[] list(@Readonly File this); + String getPath(@Readonly File this); + long length(@Readonly File this); + String getName(@Readonly File this); +} + +@ReceiverDependantMutable +class FileInputStream { + @ReceiverDependantMutable FileInputStream(@Readonly File file); +} + +class ObjectOutputStream { + void writeObject(@Readonly Object obj); +} + +@ReceiverDependantMutable +interface Serializable {} + +package java.awt; + +@ReceiverDependantMutable +class Container { + void add(@Readonly Component comp, @Readonly Object constraints); +} From e835413cda1a2d413505ebf4a770805489a9c72c Mon Sep 17 00:00:00 2001 From: Aosen Xiong Date: Fri, 10 May 2024 14:26:47 -0400 Subject: [PATCH 04/11] Add SKIP-REQUIRE-JAVADOC util all javadoc is written --- SKIP-REQUIRE-JAVADOC | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 SKIP-REQUIRE-JAVADOC diff --git a/SKIP-REQUIRE-JAVADOC b/SKIP-REQUIRE-JAVADOC new file mode 100644 index 00000000000..e69de29bb2d From 9cfae05c1e3993a204688c29e4cf072e40cd2f85 Mon Sep 17 00:00:00 2001 From: Aosen Xiong Date: Fri, 10 May 2024 15:29:22 -0400 Subject: [PATCH 05/11] Remove wildcard import --- .../PICONoInitAnnotatedTypeFactory.java | 32 ++++++++++++++++--- .../immutability/PICONoInitVisitor.java | 8 ++++- .../checker/immutability/PICOValidator.java | 4 ++- 3 files changed, 37 insertions(+), 7 deletions(-) diff --git a/checker/src/main/java/org/checkerframework/checker/immutability/PICONoInitAnnotatedTypeFactory.java b/checker/src/main/java/org/checkerframework/checker/immutability/PICONoInitAnnotatedTypeFactory.java index f6850c2410b..59cb34302a3 100644 --- a/checker/src/main/java/org/checkerframework/checker/immutability/PICONoInitAnnotatedTypeFactory.java +++ b/checker/src/main/java/org/checkerframework/checker/immutability/PICONoInitAnnotatedTypeFactory.java @@ -1,6 +1,9 @@ package org.checkerframework.checker.immutability; -import static org.checkerframework.checker.immutability.PICOAnnotationMirrorHolder.*; +import static org.checkerframework.checker.immutability.PICOAnnotationMirrorHolder.IMMUTABLE; +import static org.checkerframework.checker.immutability.PICOAnnotationMirrorHolder.MUTABLE; +import static org.checkerframework.checker.immutability.PICOAnnotationMirrorHolder.READONLY; +import static org.checkerframework.checker.immutability.PICOAnnotationMirrorHolder.RECEIVER_DEPENDANT_MUTABLE; import com.sun.source.tree.BinaryTree; import com.sun.source.tree.IdentifierTree; @@ -22,17 +25,36 @@ import org.checkerframework.checker.initialization.InitializationFieldAccessTreeAnnotator; import org.checkerframework.common.basetype.BaseTypeChecker; import org.checkerframework.framework.qual.RelevantJavaTypes; -import org.checkerframework.framework.type.*; +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.NoElementQualifierHierarchy; +import org.checkerframework.framework.type.QualifierHierarchy; +import org.checkerframework.framework.type.ViewpointAdapter; import org.checkerframework.framework.type.treeannotator.ListTreeAnnotator; import org.checkerframework.framework.type.treeannotator.LiteralTreeAnnotator; import org.checkerframework.framework.type.treeannotator.PropagationTreeAnnotator; import org.checkerframework.framework.type.treeannotator.TreeAnnotator; -import org.checkerframework.framework.type.typeannotator.*; -import org.checkerframework.javacutil.*; +import org.checkerframework.framework.type.typeannotator.DefaultForTypeAnnotator; +import org.checkerframework.framework.type.typeannotator.DefaultQualifierForUseTypeAnnotator; +import org.checkerframework.framework.type.typeannotator.IrrelevantTypeAnnotator; +import org.checkerframework.framework.type.typeannotator.ListTypeAnnotator; +import org.checkerframework.framework.type.typeannotator.PropagationTypeAnnotator; +import org.checkerframework.framework.type.typeannotator.TypeAnnotator; +import org.checkerframework.javacutil.AnnotationMirrorSet; +import org.checkerframework.javacutil.AnnotationUtils; +import org.checkerframework.javacutil.ElementUtils; +import org.checkerframework.javacutil.TreePathUtil; +import org.checkerframework.javacutil.TreeUtils; import java.lang.annotation.Annotation; -import java.util.*; +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; diff --git a/checker/src/main/java/org/checkerframework/checker/immutability/PICONoInitVisitor.java b/checker/src/main/java/org/checkerframework/checker/immutability/PICONoInitVisitor.java index 4c549ad00ad..88aed83f0cc 100644 --- a/checker/src/main/java/org/checkerframework/checker/immutability/PICONoInitVisitor.java +++ b/checker/src/main/java/org/checkerframework/checker/immutability/PICONoInitVisitor.java @@ -32,7 +32,13 @@ import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedExecutableType; import org.checkerframework.framework.type.QualifierHierarchy; import org.checkerframework.framework.util.AnnotatedTypes; -import org.checkerframework.javacutil.*; +import org.checkerframework.javacutil.AnnotationMirrorSet; +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; +import org.checkerframework.javacutil.TypesUtils; import java.util.HashMap; import java.util.Map; diff --git a/checker/src/main/java/org/checkerframework/checker/immutability/PICOValidator.java b/checker/src/main/java/org/checkerframework/checker/immutability/PICOValidator.java index 7e1fe75e04c..1a8024f5caf 100644 --- a/checker/src/main/java/org/checkerframework/checker/immutability/PICOValidator.java +++ b/checker/src/main/java/org/checkerframework/checker/immutability/PICOValidator.java @@ -1,7 +1,9 @@ package org.checkerframework.checker.immutability; -import static org.checkerframework.checker.immutability.PICOAnnotationMirrorHolder.*; +import static org.checkerframework.checker.immutability.PICOAnnotationMirrorHolder.BOTTOM; +import static org.checkerframework.checker.immutability.PICOAnnotationMirrorHolder.IMMUTABLE; import static org.checkerframework.checker.immutability.PICOAnnotationMirrorHolder.MUTABLE; +import static org.checkerframework.checker.immutability.PICOAnnotationMirrorHolder.RECEIVER_DEPENDANT_MUTABLE; import com.sun.source.tree.Tree; import com.sun.source.tree.Tree.Kind; From a5c4dc6566b8d6c95b6b0ef06bb58ace2023ffcd Mon Sep 17 00:00:00 2001 From: Aosen Xiong Date: Fri, 10 May 2024 15:32:58 -0400 Subject: [PATCH 06/11] Annotate compiler message --- .../checker/immutability/PICONoInitVisitor.java | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/checker/src/main/java/org/checkerframework/checker/immutability/PICONoInitVisitor.java b/checker/src/main/java/org/checkerframework/checker/immutability/PICONoInitVisitor.java index 88aed83f0cc..2882c7e5cde 100644 --- a/checker/src/main/java/org/checkerframework/checker/immutability/PICONoInitVisitor.java +++ b/checker/src/main/java/org/checkerframework/checker/immutability/PICONoInitVisitor.java @@ -117,7 +117,10 @@ private boolean isAdaptedSubtype(AnnotationMirror lhs, AnnotationMirror rhs) { @Override protected boolean commonAssignmentCheck( - Tree varTree, ExpressionTree valueExp, String errorKey, Object... extraArgs) { + Tree varTree, + ExpressionTree valueExp, + @CompilerMessageKey String errorKey, + Object... extraArgs) { AnnotatedTypeMirror var = atypeFactory.getAnnotatedTypeLhs(varTree); assert var != null : "no variable found for tree: " + varTree; @@ -591,7 +594,7 @@ protected boolean commonAssignmentCheck( AnnotatedTypeMirror varType, AnnotatedTypeMirror valueType, Tree valueTree, - String errorKey, + @CompilerMessageKey String errorKey, Object... extraArgs) { // TODO: WORKAROUND: anonymous class handling if (TypesUtils.isAnonymous(valueType.getUnderlyingType())) { From ba1b98201a57a3c861f03a9ba63b45f55b99f3a3 Mon Sep 17 00:00:00 2001 From: Aosen Xiong Date: Fri, 10 May 2024 17:13:57 -0400 Subject: [PATCH 07/11] Remove mirror holder --- .../ObjectIdentityMethodEnforcer.java | 17 +- .../PICOAnnotationMirrorHolder.java | 39 --- .../checker/immutability/PICOChecker.java | 1 - .../PICONoInitAnnotatedTypeFactory.java | 236 +++--------------- .../checker/immutability/PICONoInitStore.java | 1 - .../immutability/PICONoInitTransfer.java | 1 - .../checker/immutability/PICONoInitValue.java | 1 - .../immutability/PICONoInitVisitor.java | 123 ++++----- .../checker/immutability/PICOTypeUtil.java | 68 ++--- .../checker/immutability/PICOValidator.java | 35 ++- .../immutability/PICOViewpointAdapter.java | 50 +--- 11 files changed, 157 insertions(+), 415 deletions(-) delete mode 100644 checker/src/main/java/org/checkerframework/checker/immutability/PICOAnnotationMirrorHolder.java diff --git a/checker/src/main/java/org/checkerframework/checker/immutability/ObjectIdentityMethodEnforcer.java b/checker/src/main/java/org/checkerframework/checker/immutability/ObjectIdentityMethodEnforcer.java index d4704f579b3..6d368c4ba11 100644 --- a/checker/src/main/java/org/checkerframework/checker/immutability/ObjectIdentityMethodEnforcer.java +++ b/checker/src/main/java/org/checkerframework/checker/immutability/ObjectIdentityMethodEnforcer.java @@ -1,8 +1,5 @@ package org.checkerframework.checker.immutability; -import static org.checkerframework.checker.immutability.PICOAnnotationMirrorHolder.MUTABLE; -import static org.checkerframework.checker.immutability.PICOAnnotationMirrorHolder.READONLY; - import com.sun.source.tree.IdentifierTree; import com.sun.source.tree.MemberSelectTree; import com.sun.source.tree.MethodInvocationTree; @@ -21,12 +18,12 @@ public class ObjectIdentityMethodEnforcer extends TreePathScanner { - private PICONoInitAnnotatedTypeFactory typeFactory; + private PICONoInitAnnotatedTypeFactory atypeFactory; private BaseTypeChecker checker; private ObjectIdentityMethodEnforcer( PICONoInitAnnotatedTypeFactory typeFactory, BaseTypeChecker checker) { - this.typeFactory = typeFactory; + this.atypeFactory = typeFactory; this.checker = checker; } @@ -54,7 +51,7 @@ private void checkMethod(MethodInvocationTree node, Element elt) { return; // Doesn't check static method invocation because it doesn't access instance // field } - if (!PICOTypeUtil.isObjectIdentityMethod((ExecutableElement) elt, typeFactory)) { + if (!PICOTypeUtil.isObjectIdentityMethod((ExecutableElement) elt, atypeFactory)) { // Report warning since invoked method is not only dependant on abstract state fields, // but we // don't know whether this method invocation's result flows into the hashcode or not. @@ -86,7 +83,7 @@ private void checkField(Tree node, Element elt) { if (ElementUtils.isStatic(elt)) { checker.reportWarning(node, "object.identity.static.field.access.forbidden", elt); } else { - if (!isInAbstractState(elt, typeFactory)) { + if (!isInAbstractState(elt, atypeFactory)) { // Report warning since accessed field is not within abstract state checker.reportWarning(node, "object.identity.field.access.invalid", elt); } @@ -100,9 +97,11 @@ private boolean isInAbstractState(Element elt, PICONoInitAnnotatedTypeFactory ty boolean in = true; if (PICOTypeUtil.isAssignableField(elt, typeFactory)) { in = false; - } else if (AnnotatedTypes.containsModifier(typeFactory.getAnnotatedType(elt), MUTABLE)) { + } else if (AnnotatedTypes.containsModifier( + typeFactory.getAnnotatedType(elt), atypeFactory.MUTABLE)) { in = false; - } else if (AnnotatedTypes.containsModifier(typeFactory.getAnnotatedType(elt), READONLY)) { + } else if (AnnotatedTypes.containsModifier( + typeFactory.getAnnotatedType(elt), atypeFactory.READONLY)) { in = false; } diff --git a/checker/src/main/java/org/checkerframework/checker/immutability/PICOAnnotationMirrorHolder.java b/checker/src/main/java/org/checkerframework/checker/immutability/PICOAnnotationMirrorHolder.java deleted file mode 100644 index 388652ddf6d..00000000000 --- a/checker/src/main/java/org/checkerframework/checker/immutability/PICOAnnotationMirrorHolder.java +++ /dev/null @@ -1,39 +0,0 @@ -package org.checkerframework.checker.immutability; - -import org.checkerframework.checker.immutability.qual.Bottom; -import org.checkerframework.checker.immutability.qual.Immutable; -import org.checkerframework.checker.immutability.qual.Mutable; -import org.checkerframework.checker.immutability.qual.PolyMutable; -import org.checkerframework.checker.immutability.qual.Readonly; -import org.checkerframework.checker.immutability.qual.ReceiverDependantMutable; -import org.checkerframework.checker.initialization.qual.Initialized; -import org.checkerframework.framework.source.SourceChecker; -import org.checkerframework.javacutil.AnnotationBuilder; - -import javax.lang.model.element.AnnotationMirror; -import javax.lang.model.util.Elements; - -/** A holder class that holds AnnotationMirrors that are shared by PICO and PICOInfer. */ -public class PICOAnnotationMirrorHolder { - - public static AnnotationMirror READONLY; - public static AnnotationMirror MUTABLE; - public static AnnotationMirror POLY_MUTABLE; - public static AnnotationMirror RECEIVER_DEPENDANT_MUTABLE; - public static AnnotationMirror IMMUTABLE; - public static AnnotationMirror BOTTOM; - public static AnnotationMirror COMMITED; - - public static void init(SourceChecker checker) { - Elements elements = checker.getElementUtils(); - READONLY = AnnotationBuilder.fromClass(elements, Readonly.class); - MUTABLE = AnnotationBuilder.fromClass(elements, Mutable.class); - POLY_MUTABLE = AnnotationBuilder.fromClass(elements, PolyMutable.class); - RECEIVER_DEPENDANT_MUTABLE = - AnnotationBuilder.fromClass(elements, ReceiverDependantMutable.class); - IMMUTABLE = AnnotationBuilder.fromClass(elements, Immutable.class); - BOTTOM = AnnotationBuilder.fromClass(elements, Bottom.class); - - COMMITED = AnnotationBuilder.fromClass(elements, Initialized.class); - } -} diff --git a/checker/src/main/java/org/checkerframework/checker/immutability/PICOChecker.java b/checker/src/main/java/org/checkerframework/checker/immutability/PICOChecker.java index 4abdb540810..e313609ef8a 100644 --- a/checker/src/main/java/org/checkerframework/checker/immutability/PICOChecker.java +++ b/checker/src/main/java/org/checkerframework/checker/immutability/PICOChecker.java @@ -20,7 +20,6 @@ public Class getTargetCheckerClass() { @Override public void initChecker() { super.initChecker(); - PICOAnnotationMirrorHolder.init(this); } @Override diff --git a/checker/src/main/java/org/checkerframework/checker/immutability/PICONoInitAnnotatedTypeFactory.java b/checker/src/main/java/org/checkerframework/checker/immutability/PICONoInitAnnotatedTypeFactory.java index 59cb34302a3..99986f19de2 100644 --- a/checker/src/main/java/org/checkerframework/checker/immutability/PICONoInitAnnotatedTypeFactory.java +++ b/checker/src/main/java/org/checkerframework/checker/immutability/PICONoInitAnnotatedTypeFactory.java @@ -1,10 +1,5 @@ package org.checkerframework.checker.immutability; -import static org.checkerframework.checker.immutability.PICOAnnotationMirrorHolder.IMMUTABLE; -import static org.checkerframework.checker.immutability.PICOAnnotationMirrorHolder.MUTABLE; -import static org.checkerframework.checker.immutability.PICOAnnotationMirrorHolder.READONLY; -import static org.checkerframework.checker.immutability.PICOAnnotationMirrorHolder.RECEIVER_DEPENDANT_MUTABLE; - import com.sun.source.tree.BinaryTree; import com.sun.source.tree.IdentifierTree; import com.sun.source.tree.MethodTree; @@ -42,6 +37,7 @@ import org.checkerframework.framework.type.typeannotator.ListTypeAnnotator; import org.checkerframework.framework.type.typeannotator.PropagationTypeAnnotator; import org.checkerframework.framework.type.typeannotator.TypeAnnotator; +import org.checkerframework.javacutil.AnnotationBuilder; import org.checkerframework.javacutil.AnnotationMirrorSet; import org.checkerframework.javacutil.AnnotationUtils; import org.checkerframework.javacutil.ElementUtils; @@ -71,7 +67,7 @@ * TypeAnnotators. It also applies implicits to method receiver that is not so by default in super * implementation. */ -// TODO Use @Immutable for classes that extends those predefined immutable classess like String or +// TODO Use @Immutable for classes that extends those predefined immutable classes like String or // Number // and explicitly annotated classes with @Immutable on its declaration public class PICONoInitAnnotatedTypeFactory @@ -79,6 +75,16 @@ public class PICONoInitAnnotatedTypeFactory PICONoInitValue, PICONoInitStore, PICONoInitTransfer, PICONoInitAnalysis> implements ViewpointAdapterGettable { + public final AnnotationMirror READONLY = AnnotationBuilder.fromClass(elements, Readonly.class); + public final AnnotationMirror MUTABLE = AnnotationBuilder.fromClass(elements, Mutable.class); + public final AnnotationMirror POLY_MUTABLE = + AnnotationBuilder.fromClass(elements, PolyMutable.class); + public final AnnotationMirror RECEIVER_DEPENDANT_MUTABLE = + AnnotationBuilder.fromClass(elements, ReceiverDependantMutable.class); + public final AnnotationMirror IMMUTABLE = + AnnotationBuilder.fromClass(elements, Immutable.class); + public final AnnotationMirror BOTTOM = AnnotationBuilder.fromClass(elements, Bottom.class); + public PICONoInitAnnotatedTypeFactory(BaseTypeChecker checker) { super(checker); postInit(); @@ -154,32 +160,13 @@ public ParameterizedExecutableType constructorFromUse(NewClassTree tree) { } ParameterizedExecutableType mType = super.constructorFromUse(tree); AnnotatedExecutableType method = mType.executableType; - if (!hasExplicitAnnos && method.getReturnType().hasAnnotation(RECEIVER_DEPENDANT_MUTABLE)) { + if (!hasExplicitAnnos + && method.getReturnType().hasAnnotation(ReceiverDependantMutable.class)) { method.getReturnType().replaceAnnotation(MUTABLE); } return mType; } - // /** Just to transfer the method from super class to package */ - // @Override - // protected boolean isInitializationAnnotation(AnnotationMirror anno) { - // return super.isInitializationAnnotation(anno); - // } - // - // @Override - // public AnnotationMirror getFieldInvariantAnnotation() { - // return IMMUTABLE; - // } - // - // /** This affects what fields pico warns not initialized in constructors */ - // @Override - // protected boolean hasFieldInvariantAnnotation( - // AnnotatedTypeMirror type, VariableElement fieldElement) { - // // This affects which fields should be guaranteed to be initialized: - // // Fields of any immutability should be initialized. - // return !PICOTypeUtil.isAssignableField(fieldElement, this); - // } - /** Forbid applying top annotations to type variables if they are used on local variables */ @Override public boolean getShouldDefaultTypeVarLocals() { @@ -194,140 +181,11 @@ public boolean getShouldDefaultTypeVarLocals() { public void addComputedTypeAnnotations(Element elt, AnnotatedTypeMirror type) { PICOTypeUtil.addDefaultForField(this, type, elt); PICOTypeUtil.defaultConstructorReturnToClassBound(this, elt, type); - // PICOTypeUtil.applyImmutableToEnumAndEnumConstant(type); super.addComputedTypeAnnotations(elt, type); } - /** This method gets lhs WITH flow sensitive refinement */ - // TODO Should refactor super class to avoid too much duplicate code. - // This method is pretty hacky right now. - // @Override - // public AnnotatedTypeMirror getAnnotatedTypeLhs(Tree lhsTree) { - // boolean oldComputingAnnotatedTypeMirrorOfLHS = computingAnnotatedTypeMirrorOfLHS; - // computingAnnotatedTypeMirrorOfLHS = true; - // - // AnnotatedTypeMirror result; - // boolean oldShouldCache = shouldCache; - // // Don't cache the result because getAnnotatedType(lhsTree) could - // // be called from elsewhere and would expect flow-sensitive type refinements. - // shouldCache = false; - // switch (lhsTree.getKind()) { - // case VARIABLE: - // case IDENTIFIER: - // case MEMBER_SELECT: - // case ARRAY_ACCESS: - // result = getAnnotatedType(lhsTree); - // break; - // default: - // if (TreeUtils.isTypeTree(lhsTree)) { - // // lhsTree is a type tree at the pseudo assignment of a returned - // expression to declared return type. - // result = getAnnotatedType(lhsTree); - // } else { - // throw new BugInCF( - // "GenericAnnotatedTypeFactory: Unexpected tree passed to - // getAnnotatedTypeLhs. " - // + "lhsTree: " - // + lhsTree - // + " Tree.Kind: " - // + lhsTree.getKind()); - // } - // } - // shouldCache = oldShouldCache; - // - // computingAnnotatedTypeMirrorOfLHS = oldComputingAnnotatedTypeMirrorOfLHS; - // return result; - // } - - // /**Handles invoking static methods with polymutable on its declaration*/ - // @Override - // public ParameterizedExecutableType methodFromUse(ExpressionTree tree, ExecutableElement - // methodElt, AnnotatedTypeMirror receiverType) { - // ParameterizedExecutableType pair = super.methodFromUse(tree, methodElt, receiverType); - // // We want to replace polymutable with substitutablepolymutable when we invoke static - // methods - // if (ElementUtils.isStatic(methodElt)) { - // AnnotatedExecutableType methodType = pair.executableType; - // AnnotatedTypeMirror returnType = methodType.getReturnType(); - // if (returnType.hasAnnotation(POLY_MUTABLE)) { - // // Only substitute polymutable but not other qualifiers! Missing the if - // statement - // // caused bugs before! - // returnType.replaceAnnotation(SUBSTITUTABLE_POLY_MUTABLE); - // } - // List parameterTypes = methodType.getParameterTypes(); - // for (AnnotatedTypeMirror p : parameterTypes) { - // if (returnType.hasAnnotation(POLY_MUTABLE)) { - // p.replaceAnnotation(SUBSTITUTABLE_POLY_MUTABLE); - // } - // } - // } - // return pair; - // } - - // protected class PICOQualifierHierarchy extends MostlyNoElementQualifierHierarchy { - // protected PICOQualifierHierarchy(Collection> - // qualifierClasses, Elements elements, GenericAnnotatedTypeFactory atypeFactory) { - // super(qualifierClasses, elements, atypeFactory); - // } - // - // // public PICOQualifierHierarchy(MultiGraphFactory f, Object[] arg) { - // // super(f, arg); - // // } - // - // // @Override - // // public boolean isSubtype(AnnotationMirror subAnno, AnnotationMirror - // superAnno) { - // // if (isInitializationAnnotation(subAnno) || - // // isInitializationAnnotation(superAnno)) { - // // return this.isSubtypeInitialization(subAnno, superAnno); - // // } - // // return super.isSubtype(subAnno, superAnno); - // // } - // - // @Override - // protected boolean isSubtypeWithElements( - // AnnotationMirror subAnno, - // QualifierKind subKind, - // AnnotationMirror superAnno, - // QualifierKind superKind) { - // return super.isSubtypeQualifiersOnly(subAnno, superAnno); - // } - // - // // @Override - // // public AnnotationMirror leastUpperBound(AnnotationMirror a1, - // AnnotationMirror a2) - // // { - // // if (isInitializationAnnotation(a1) || isInitializationAnnotation(a2)) { - // // return this.leastUpperBoundInitialization(a1, a2); - // // } - // // return super.leastUpperBound(a1, a2); - // // } - // - // @Override - // protected AnnotationMirror leastUpperBoundWithElements( - // AnnotationMirror a1, - // QualifierKind q1, - // AnnotationMirror a2, - // QualifierKind q2, - // QualifierKind lub) { - // return super.leastUpperBoundQualifiersOnly(a1, a2); - // } - // - // @Override - // protected AnnotationMirror greatestLowerBoundWithElements( - // AnnotationMirror annotationMirror, - // QualifierKind qualifierKind, - // AnnotationMirror annotationMirror1, - // QualifierKind qualifierKind1, - // QualifierKind qualifierKind2) { - // return super.greatestLowerBoundQualifiersOnly(annotationMirror, - // annotationMirror1); - // } - // } - /** Tree Annotators */ - public static class PICOPropagationTreeAnnotator extends PropagationTreeAnnotator { + public class PICOPropagationTreeAnnotator extends PropagationTreeAnnotator { public PICOPropagationTreeAnnotator(AnnotatedTypeFactory atypeFactory) { super(atypeFactory); } @@ -340,42 +198,19 @@ public Void visitNewArray(NewArrayTree tree, AnnotatedTypeMirror type) { AnnotatedTypeMirror componentType = ((AnnotatedTypeMirror.AnnotatedArrayType) type).getComponentType(); boolean noExplicitATM = false; - if (!componentType.hasAnnotation(RECEIVER_DEPENDANT_MUTABLE)) { + if (!componentType.hasAnnotation(ReceiverDependantMutable.class)) { noExplicitATM = true; } super.visitNewArray(tree, type); // if new explicit anno before, but RDM after, the RDM must come from the type // declaration bound // however, for type has declaration bound as RDM, its default use is mutable. - if (noExplicitATM && componentType.hasAnnotation(RECEIVER_DEPENDANT_MUTABLE)) { + if (noExplicitATM && componentType.hasAnnotation(ReceiverDependantMutable.class)) { componentType.replaceAnnotation(MUTABLE); } return null; } - // - // /**Add immutable to the result type of a binary operation if the result type is - // implicitly immutable*/ - // @Override - // public Void visitBinary(BinaryTree node, AnnotatedTypeMirror type) { - // applyImmutableIfImplicitlyImmutable(type);// Usually there isn't existing - // annotation on binary trees, but to be safe, run it first - // super.visitBinary(node, type); - // // NullnessPropagationTreeAnnotator says result type of binary tree is always - // @Initialized. So replace it - // // with COMMITED here. - // applyCommitedIfSupported(atypeFactory, type); - // return null; - // } - - // @Override - // public Void visitUnary(UnaryTree node, AnnotatedTypeMirror type) { - // super.visitUnary(node, type); - // // Same reason as above - // applyCommitedIfSupported(atypeFactory, type); - // return null; - // } - // @Override public Void visitTypeCast(TypeCastTree node, AnnotatedTypeMirror type) { boolean hasExplicitAnnos = false; @@ -383,7 +218,7 @@ public Void visitTypeCast(TypeCastTree node, AnnotatedTypeMirror type) { hasExplicitAnnos = true; } super.visitTypeCast(node, type); - if (!hasExplicitAnnos && type.hasAnnotation(RECEIVER_DEPENDANT_MUTABLE)) { + if (!hasExplicitAnnos && type.hasAnnotation(ReceiverDependantMutable.class)) { type.replaceAnnotation(MUTABLE); } return null; @@ -393,13 +228,6 @@ public Void visitTypeCast(TypeCastTree node, AnnotatedTypeMirror type) { public Void visitBinary(BinaryTree node, AnnotatedTypeMirror type) { return null; } - - // private void applyCommitedIfSupported(AnnotatedTypeFactory annotatedTypeFactory, - // AnnotatedTypeMirror type) { - // if (annotatedTypeFactory.isSupportedQualifier(COMMITED)) { - // type.replaceAnnotation(COMMITED); - // } - // } } @Override @@ -475,7 +303,7 @@ public AnnotatedTypeMirror getTypeOfExtendsImplements(Tree clause) { } /** Apply defaults for static fields with non-implicitly immutable types */ - public static class PICOTreeAnnotator extends TreeAnnotator { + public class PICOTreeAnnotator extends TreeAnnotator { public PICOTreeAnnotator(AnnotatedTypeFactory atypeFactory) { super(atypeFactory); } @@ -489,7 +317,8 @@ public Void visitMethod(MethodTree node, AnnotatedTypeMirror p) { // See: // https://github.com/opprop/checker-framework/blob/master/framework/src/org/checkerframework/framework/type/AnnotatedTypeFactory.java#L1593 // for why constructor return is not applied class bound annotation - PICOTypeUtil.defaultConstructorReturnToClassBound(atypeFactory, element, p); + PICOTypeUtil.defaultConstructorReturnToClassBound( + (PICONoInitAnnotatedTypeFactory) atypeFactory, element, p); return super.visitMethod(node, p); } @@ -497,8 +326,8 @@ public Void visitMethod(MethodTree node, AnnotatedTypeMirror p) { @Override public Void visitVariable(VariableTree node, AnnotatedTypeMirror annotatedTypeMirror) { VariableElement element = TreeUtils.elementFromDeclaration(node); - PICOTypeUtil.addDefaultForField(atypeFactory, annotatedTypeMirror, element); - // PICOTypeUtil.applyImmutableToEnumAndEnumConstant(annotatedTypeMirror); + PICOTypeUtil.addDefaultForField( + (PICONoInitAnnotatedTypeFactory) atypeFactory, annotatedTypeMirror, element); return super.visitVariable(node, annotatedTypeMirror); } @@ -510,7 +339,7 @@ public Void visitBinary(BinaryTree node, AnnotatedTypeMirror type) { } /** Type Annotators */ - public static class PICOTypeAnnotator extends TypeAnnotator { + public class PICOTypeAnnotator extends TypeAnnotator { public PICOTypeAnnotator(AnnotatedTypeFactory typeFactory) { super(typeFactory); @@ -568,8 +397,7 @@ protected DefaultQualifierForUseTypeAnnotator createDefaultForUseTypeAnnotator() } // @DefaultQFU - public static class PICOQualifierForUseTypeAnnotator - extends DefaultQualifierForUseTypeAnnotator { + public class PICOQualifierForUseTypeAnnotator extends DefaultQualifierForUseTypeAnnotator { public PICOQualifierForUseTypeAnnotator(AnnotatedTypeFactory typeFactory) { super(typeFactory); @@ -647,7 +475,7 @@ protected Void scan(AnnotatedTypeMirror type, Void p) { public static class PICOSuperClauseAnnotator extends TreeAnnotator { - public PICOSuperClauseAnnotator(AnnotatedTypeFactory atypeFactory) { + public PICOSuperClauseAnnotator(PICONoInitAnnotatedTypeFactory atypeFactory) { super(atypeFactory); } @@ -669,16 +497,20 @@ private void addDefaultFromMain(Tree tree, AnnotatedTypeMirror mirror) { // Here only explicit annotation on super clause have effect because framework default // rule is overriden if (isSuperClause(path) - && (!mirror.hasAnnotationInHierarchy(READONLY) + && (!mirror.hasAnnotationInHierarchy( + ((PICONoInitAnnotatedTypeFactory) atypeFactory).READONLY) || atypeFactory .getQualifierHierarchy() .findAnnotationInHierarchy( TreeUtils.typeOf(tree).getAnnotationMirrors(), - READONLY) + ((PICONoInitAnnotatedTypeFactory) atypeFactory) + .READONLY) == null)) { AnnotatedTypeMirror enclosing = atypeFactory.getAnnotatedType(TreePathUtil.enclosingClass(path)); - AnnotationMirror mainBound = enclosing.getAnnotationInHierarchy(READONLY); + AnnotationMirror mainBound = + enclosing.getAnnotationInHierarchy( + ((PICONoInitAnnotatedTypeFactory) atypeFactory).READONLY); mirror.replaceAnnotation(mainBound); // System.err.println("ANNOT: ADDED DEFAULT FOR: " + mirror); } @@ -702,7 +534,7 @@ public Void visitParameterizedType( } } - public static class PICOEnumDefaultAnnotator extends TypeAnnotator { + public class PICOEnumDefaultAnnotator extends TypeAnnotator { // Defaulting only applies the same annotation to all class declarations // We need this to "only default enums" to immutable diff --git a/checker/src/main/java/org/checkerframework/checker/immutability/PICONoInitStore.java b/checker/src/main/java/org/checkerframework/checker/immutability/PICONoInitStore.java index e0253d7f5bd..e0218780a51 100644 --- a/checker/src/main/java/org/checkerframework/checker/immutability/PICONoInitStore.java +++ b/checker/src/main/java/org/checkerframework/checker/immutability/PICONoInitStore.java @@ -6,7 +6,6 @@ import java.util.Map; -/** Created by mier on 15/08/17. */ public class PICONoInitStore extends CFAbstractStore { protected Map initializedFields; diff --git a/checker/src/main/java/org/checkerframework/checker/immutability/PICONoInitTransfer.java b/checker/src/main/java/org/checkerframework/checker/immutability/PICONoInitTransfer.java index a7bd5e3b71d..549f790f6e8 100644 --- a/checker/src/main/java/org/checkerframework/checker/immutability/PICONoInitTransfer.java +++ b/checker/src/main/java/org/checkerframework/checker/immutability/PICONoInitTransfer.java @@ -12,7 +12,6 @@ import javax.lang.model.element.VariableElement; -/** Created by mier on 15/08/17. */ public class PICONoInitTransfer extends CFAbstractTransfer { diff --git a/checker/src/main/java/org/checkerframework/checker/immutability/PICONoInitValue.java b/checker/src/main/java/org/checkerframework/checker/immutability/PICONoInitValue.java index cfe55bf207a..c53f65f2903 100644 --- a/checker/src/main/java/org/checkerframework/checker/immutability/PICONoInitValue.java +++ b/checker/src/main/java/org/checkerframework/checker/immutability/PICONoInitValue.java @@ -6,7 +6,6 @@ import javax.lang.model.type.TypeMirror; -/** Created by mier on 15/08/17. */ public class PICONoInitValue extends CFAbstractValue { public PICONoInitValue( CFAbstractAnalysis analysis, diff --git a/checker/src/main/java/org/checkerframework/checker/immutability/PICONoInitVisitor.java b/checker/src/main/java/org/checkerframework/checker/immutability/PICONoInitVisitor.java index 2882c7e5cde..b8e4a3d61f5 100644 --- a/checker/src/main/java/org/checkerframework/checker/immutability/PICONoInitVisitor.java +++ b/checker/src/main/java/org/checkerframework/checker/immutability/PICONoInitVisitor.java @@ -1,11 +1,5 @@ package org.checkerframework.checker.immutability; -import static org.checkerframework.checker.immutability.PICOAnnotationMirrorHolder.BOTTOM; -import static org.checkerframework.checker.immutability.PICOAnnotationMirrorHolder.IMMUTABLE; -import static org.checkerframework.checker.immutability.PICOAnnotationMirrorHolder.MUTABLE; -import static org.checkerframework.checker.immutability.PICOAnnotationMirrorHolder.POLY_MUTABLE; -import static org.checkerframework.checker.immutability.PICOAnnotationMirrorHolder.READONLY; -import static org.checkerframework.checker.immutability.PICOAnnotationMirrorHolder.RECEIVER_DEPENDANT_MUTABLE; import static org.checkerframework.javacutil.TreePathUtil.isTopLevelAssignmentInInitializerBlock; import com.sun.source.tree.ArrayAccessTree; @@ -23,6 +17,11 @@ import com.sun.source.tree.VariableTree; import org.checkerframework.checker.compilermsgs.qual.CompilerMessageKey; +import org.checkerframework.checker.immutability.qual.Immutable; +import org.checkerframework.checker.immutability.qual.Mutable; +import org.checkerframework.checker.immutability.qual.PolyMutable; +import org.checkerframework.checker.immutability.qual.Readonly; +import org.checkerframework.checker.immutability.qual.ReceiverDependantMutable; import org.checkerframework.common.basetype.BaseTypeChecker; import org.checkerframework.common.basetype.BaseTypeVisitor; import org.checkerframework.common.basetype.TypeValidator; @@ -55,12 +54,11 @@ public class PICONoInitVisitor extends BaseTypeVisitor { - private final boolean shouldOutputFbcError; final Map fbcViolatedMethods; public PICONoInitVisitor(BaseTypeChecker checker) { super(checker); - shouldOutputFbcError = checker.hasOption("printFbcErrors"); + boolean shouldOutputFbcError = checker.hasOption("printFbcErrors"); fbcViolatedMethods = shouldOutputFbcError ? new HashMap<>() : null; } @@ -90,12 +88,12 @@ public boolean isValidUse( AnnotatedDeclaredType declarationType, AnnotatedDeclaredType useType, Tree tree) { // FIXME workaround for poly anno, remove after fix substitutable poly and add poly vp rules - if (useType.hasAnnotation(POLY_MUTABLE)) { + if (useType.hasAnnotation(PolyMutable.class)) { return true; } - AnnotationMirror declared = declarationType.getAnnotationInHierarchy(READONLY); - AnnotationMirror used = useType.getAnnotationInHierarchy(READONLY); + AnnotationMirror declared = declarationType.getAnnotationInHierarchy(atypeFactory.READONLY); + AnnotationMirror used = useType.getAnnotationInHierarchy(atypeFactory.READONLY); return isAdaptedSubtype(used, declared); } @@ -104,8 +102,8 @@ public boolean isValidUse( public boolean isValidUse(AnnotatedTypeMirror.AnnotatedArrayType type, Tree tree) { // You don't need adapted subtype if the decl bound is guaranteed to be RDM. // That simply means that any use is valid except bottom. - AnnotationMirror used = type.getAnnotationInHierarchy(READONLY); - return !AnnotationUtils.areSame(used, BOTTOM); + AnnotationMirror used = type.getAnnotationInHierarchy(atypeFactory.READONLY); + return !AnnotationUtils.areSame(used, atypeFactory.BOTTOM); } private boolean isAdaptedSubtype(AnnotationMirror lhs, AnnotationMirror rhs) { @@ -187,8 +185,8 @@ protected void checkConstructorInvocation( if (!atypeFactory .getQualifierHierarchy() .isSubtypeQualifiersOnly( - invocation.getAnnotationInHierarchy(READONLY), - returnType.getAnnotationInHierarchy(READONLY))) { + invocation.getAnnotationInHierarchy(atypeFactory.READONLY), + returnType.getAnnotationInHierarchy(atypeFactory.READONLY))) { checker.reportError( newClassTree, "constructor.invocation.invalid", invocation, returnType); } @@ -203,8 +201,8 @@ public Void visitMethod(MethodTree node, Void p) { if (TreeUtils.isConstructor(node)) { AnnotatedDeclaredType constructorReturnType = (AnnotatedDeclaredType) executableType.getReturnType(); - if (constructorReturnType.hasAnnotation(READONLY) - || constructorReturnType.hasAnnotation(POLY_MUTABLE)) { + if (constructorReturnType.hasAnnotation(Readonly.class) + || constructorReturnType.hasAnnotation(PolyMutable.class)) { checker.reportError(node, "constructor.return.invalid", constructorReturnType); return super.visitMethod(node, p); } @@ -216,16 +214,18 @@ public Void visitMethod(MethodTree node, Void p) { AnnotatedDeclaredType declareReceiverType = executableType.getReceiverType(); if (declareReceiverType != null) { if (bound != null - && !bound.hasAnnotation(RECEIVER_DEPENDANT_MUTABLE) + && !bound.hasAnnotation(ReceiverDependantMutable.class) && !atypeFactory .getQualifierHierarchy() .isSubtypeQualifiersOnly( - declareReceiverType.getAnnotationInHierarchy(READONLY), - bound.getAnnotationInHierarchy(READONLY)) - // Below three are allowed on declared receiver types of instance methods in + declareReceiverType.getAnnotationInHierarchy( + atypeFactory.READONLY), + bound.getAnnotationInHierarchy(atypeFactory.READONLY)) + // Below three are allowed on declared receiver types of + // instance methods in // either @Mutable class or @Immutable class - && !declareReceiverType.hasAnnotation(READONLY) - && !declareReceiverType.hasAnnotation(POLY_MUTABLE)) { + && !declareReceiverType.hasAnnotation(Readonly.class) + && !declareReceiverType.hasAnnotation(PolyMutable.class)) { checker.reportError(node, "method.receiver.incompatible", declareReceiverType); } } @@ -338,17 +338,13 @@ private boolean allowWrite(AnnotatedTypeMirror receiverType, ExpressionTree vari // the field // is declared as final, Java compiler will catch that, and we couldn't have reached this // point + // If the receiver is mutable, we allow assigning/reassigning + // } else if (TreeUtils.elementFromUse(variable)) { + // // If the field is not initialized, we allow assigning/reassigning + // return true; if (PICOTypeUtil.isAssigningAssignableField(variable, atypeFactory)) { return isAllowedAssignableField(receiverType, variable); - } else if (receiverType.hasAnnotation(MUTABLE)) { - // If the receiver is mutable, we allow assigning/reassigning - return true; - // } else if (TreeUtils.elementFromUse(variable)) { - // // If the field is not initialized, we allow assigning/reassigning - // return true; - } - - return false; + } else return receiverType.hasAnnotation(Mutable.class); } private boolean isAllowedAssignableField( @@ -361,9 +357,10 @@ private boolean isAllowedAssignableField( // Forbid the case that might break type soundness. See ForbidAssignmentCase.java:21 // the second and third predicates ensure that the field is actually rdm (since sometimes we // replace implicitly mutable with rdm to protect transitive immutability). - return !(receiverType.hasAnnotation(READONLY) - && AnnotationUtils.containsSameByName(bounds, RECEIVER_DEPENDANT_MUTABLE) - && fieldType.hasAnnotation(RECEIVER_DEPENDANT_MUTABLE)); + return !(receiverType.hasAnnotation(Readonly.class) + && AnnotationUtils.containsSameByName( + bounds, atypeFactory.RECEIVER_DEPENDANT_MUTABLE) + && fieldType.hasAnnotation(ReceiverDependantMutable.class)); } private void reportFieldOrArrayWriteError( @@ -388,7 +385,7 @@ public Void visitVariable(VariableTree node, Void p) { VariableElement element = TreeUtils.elementFromDeclaration(node); AnnotatedTypeMirror type = atypeFactory.getAnnotatedType(element); if (element.getKind() == ElementKind.FIELD) { - if (type.hasAnnotation(POLY_MUTABLE)) { + if (type.hasAnnotation(PolyMutable.class)) { checker.reportError(node, "field.polymutable.forbidden", element); } } @@ -399,29 +396,31 @@ public Void visitVariable(VariableTree node, Void p) { // TODO use base cf check methods AnnotationMirror declAnno = atypeFactory.getTypeDeclarationBoundForMutability(type.getUnderlyingType()); - if ((declAnno != null && AnnotationUtils.areSameByName(declAnno, IMMUTABLE)) + if ((declAnno != null && AnnotationUtils.areSameByName(declAnno, atypeFactory.IMMUTABLE)) || element.getKind() != ElementKind.FIELD - || !type.hasAnnotation(RECEIVER_DEPENDANT_MUTABLE)) { + || !type.hasAnnotation(ReceiverDependantMutable.class)) { checkAndReportInvalidAnnotationOnUse(type, node); } return super.visitVariable(node, p); } private void checkAndReportInvalidAnnotationOnUse(AnnotatedTypeMirror type, Tree node) { - AnnotationMirror useAnno = type.getAnnotationInHierarchy(READONLY); + AnnotationMirror useAnno = type.getAnnotationInHierarchy(atypeFactory.READONLY); // FIXME rm after poly vp - if (useAnno != null && AnnotationUtils.areSame(useAnno, POLY_MUTABLE)) { + if (useAnno != null && AnnotationUtils.areSame(useAnno, atypeFactory.POLY_MUTABLE)) { return; } if (useAnno != null && !PICOTypeUtil.isImplicitlyImmutableType(type) && type.getKind() != TypeKind.ARRAY) { // TODO: annotate the use instead of using this - AnnotationMirror defaultAnno = MUTABLE; + AnnotationMirror defaultAnno = atypeFactory.MUTABLE; for (AnnotationMirror anno : atypeFactory.getTypeDeclarationBounds(type.getUnderlyingType())) { - if (atypeFactory.getQualifierHierarchy().isSubtypeQualifiersOnly(anno, READONLY) - && !AnnotationUtils.areSame(anno, READONLY)) { + if (atypeFactory + .getQualifierHierarchy() + .isSubtypeQualifiersOnly(anno, atypeFactory.READONLY) + && !AnnotationUtils.areSame(anno, atypeFactory.READONLY)) { defaultAnno = anno; } } @@ -447,10 +446,10 @@ private void checkNewInstanceCreation(Tree node) { // Ensure only @Mutable/@Immutable/@ReceiverDependantMutable/@PolyMutable are used on new // instance creation AnnotatedTypeMirror type = atypeFactory.getAnnotatedType(node); - if (!(type.hasAnnotation(IMMUTABLE) - || type.hasAnnotation(MUTABLE) - || type.hasAnnotation(RECEIVER_DEPENDANT_MUTABLE) - || type.hasAnnotation(POLY_MUTABLE))) { + if (!(type.hasAnnotation(Immutable.class) + || type.hasAnnotation(Mutable.class) + || type.hasAnnotation(ReceiverDependantMutable.class) + || type.hasAnnotation(PolyMutable.class))) { checker.reportError(node, "pico.new.invalid", type); } } @@ -474,14 +473,14 @@ public Void visitMethodInvocation(MethodInvocationTree node, Void p) { @Override protected AnnotationMirrorSet getExceptionParameterLowerBoundAnnotations() { AnnotationMirrorSet result = new AnnotationMirrorSet(); - result.add(atypeFactory.getQualifierHierarchy().getBottomAnnotation(BOTTOM)); + result.add(atypeFactory.getQualifierHierarchy().getBottomAnnotation(atypeFactory.BOTTOM)); return result; } @Override protected AnnotationMirrorSet getThrowUpperBoundAnnotations() { AnnotationMirrorSet result = new AnnotationMirrorSet(); - result.add(atypeFactory.getQualifierHierarchy().getTopAnnotation(READONLY)); + result.add(atypeFactory.getQualifierHierarchy().getTopAnnotation(atypeFactory.READONLY)); return result; } @@ -498,9 +497,9 @@ public void processClassTree(ClassTree node) { AnnotatedDeclaredType bound = PICOTypeUtil.getBoundTypeOfTypeDeclaration(typeElement, atypeFactory); // Has to be either @Mutable, @ReceiverDependantMutable or @Immutable, nothing else - if (!bound.hasAnnotation(MUTABLE) - && !bound.hasAnnotation(RECEIVER_DEPENDANT_MUTABLE) - && !bound.hasAnnotation(IMMUTABLE)) { + if (!bound.hasAnnotation(Mutable.class) + && !bound.hasAnnotation(ReceiverDependantMutable.class) + && !bound.hasAnnotation(Immutable.class)) { checker.reportError(node, "class.bound.invalid", bound); return; // Doesn't process the class tree anymore } @@ -512,7 +511,8 @@ public void processClassTree(ClassTree node) { // * Member is field // * Member's declared bound == Mutable // * Member's use anno == null - if (bound.hasAnnotation(IMMUTABLE) || bound.hasAnnotation(RECEIVER_DEPENDANT_MUTABLE)) { + if (bound.hasAnnotation(Immutable.class) + || bound.hasAnnotation(ReceiverDependantMutable.class)) { for (Tree member : node.getMembers()) { if (member.getKind() == Kind.VARIABLE) { Element ele = TreeUtils.elementFromTree(member); @@ -525,8 +525,8 @@ public void processClassTree(ClassTree node) { ty = TypesUtils.upperBound(ty); } if (AnnotationUtils.containsSameByName( - atypeFactory.getTypeDeclarationBounds(ty), MUTABLE) - && !noDefaultMirror.hasAnnotationInHierarchy(READONLY)) { + atypeFactory.getTypeDeclarationBounds(ty), atypeFactory.MUTABLE) + && !noDefaultMirror.hasAnnotationInHierarchy(atypeFactory.READONLY)) { checker.reportError(member, "implicit.shallow.immutable"); } } @@ -549,9 +549,10 @@ protected void checkThisOrSuperConstructorCall( MethodTree enclosingMethod = methodTree; AnnotatedTypeMirror superType = atypeFactory.getAnnotatedType(superCall); AnnotatedExecutableType constructorType = atypeFactory.getAnnotatedType(enclosingMethod); - AnnotationMirror superTypeMirror = superType.getAnnotationInHierarchy(READONLY); + AnnotationMirror superTypeMirror = + superType.getAnnotationInHierarchy(atypeFactory.READONLY); AnnotationMirror constructorTypeMirror = - constructorType.getReturnType().getAnnotationInHierarchy(READONLY); + constructorType.getReturnType().getAnnotationInHierarchy(atypeFactory.READONLY); if (!atypeFactory .getQualifierHierarchy() .isSubtypeQualifiersOnly(constructorTypeMirror, superTypeMirror)) { @@ -578,10 +579,11 @@ protected boolean isTypeCastSafe(AnnotatedTypeMirror castType, AnnotatedTypeMirr AnnotationMirror bound = qualifierHierarchy.findAnnotationInHierarchy( atypeFactory.getTypeDeclarationBounds(castDeclared.getUnderlyingType()), - READONLY); + atypeFactory.READONLY); assert bound != null; - if (AnnotationUtils.areSame(castDeclared.getAnnotationInHierarchy(READONLY), bound)) { + if (AnnotationUtils.areSame( + castDeclared.getAnnotationInHierarchy(atypeFactory.READONLY), bound)) { return true; } } @@ -599,7 +601,8 @@ protected boolean commonAssignmentCheck( // TODO: WORKAROUND: anonymous class handling if (TypesUtils.isAnonymous(valueType.getUnderlyingType())) { AnnotatedTypeMirror newValueType = varType.deepCopy(); - newValueType.replaceAnnotation(valueType.getAnnotationInHierarchy(READONLY)); + newValueType.replaceAnnotation( + valueType.getAnnotationInHierarchy(atypeFactory.READONLY)); valueType = newValueType; } return super.commonAssignmentCheck(varType, valueType, valueTree, errorKey, extraArgs); diff --git a/checker/src/main/java/org/checkerframework/checker/immutability/PICOTypeUtil.java b/checker/src/main/java/org/checkerframework/checker/immutability/PICOTypeUtil.java index 32b9781000a..c6b3f460ae0 100644 --- a/checker/src/main/java/org/checkerframework/checker/immutability/PICOTypeUtil.java +++ b/checker/src/main/java/org/checkerframework/checker/immutability/PICOTypeUtil.java @@ -1,10 +1,5 @@ package org.checkerframework.checker.immutability; -import static org.checkerframework.checker.immutability.PICOAnnotationMirrorHolder.IMMUTABLE; -import static org.checkerframework.checker.immutability.PICOAnnotationMirrorHolder.MUTABLE; -import static org.checkerframework.checker.immutability.PICOAnnotationMirrorHolder.READONLY; -import static org.checkerframework.checker.immutability.PICOAnnotationMirrorHolder.RECEIVER_DEPENDANT_MUTABLE; - import com.sun.source.tree.ClassTree; import com.sun.source.tree.ExpressionTree; import com.sun.source.tree.MethodTree; @@ -18,6 +13,7 @@ import org.checkerframework.checker.immutability.qual.Assignable; import org.checkerframework.checker.immutability.qual.Immutable; import org.checkerframework.checker.immutability.qual.ObjectIdentityMethod; +import org.checkerframework.checker.immutability.qual.ReceiverDependantMutable; import org.checkerframework.framework.qual.DefaultFor; import org.checkerframework.framework.qual.TypeKind; import org.checkerframework.framework.type.AnnotatedTypeFactory; @@ -169,12 +165,6 @@ public static List getBoundTypesOfDirectSuperTypes( return boundsOfSupers; } - public static AnnotatedDeclaredType getBoundTypeOfTypeDeclaration( - ClassTree classTree, AnnotatedTypeFactory atypeFactory) { - TypeElement typeElement = TreeUtils.elementFromDeclaration(classTree); - return getBoundTypeOfTypeDeclaration(typeElement, atypeFactory); - } - public static AnnotatedDeclaredType getBoundTypeOfTypeDeclaration( TypeElement typeElement, AnnotatedTypeFactory atypeFactory) { // Reads bound annotation from source code or stub files @@ -200,11 +190,6 @@ public static AnnotatedDeclaredType getBoundTypeOfTypeDeclaration( // places, at some time. } - public static AnnotatedDeclaredType getBoundTypeOfTypeDeclaration( - TypeMirror typeMirror, AnnotatedTypeFactory atypeFactory) { - return getBoundTypeOfTypeDeclaration(TypesUtils.getTypeElement(typeMirror), atypeFactory); - } - public static boolean isObjectIdentityMethod( MethodTree node, AnnotatedTypeFactory annotatedTypeFactory) { ExecutableElement element = TreeUtils.elementFromDeclaration(node); @@ -260,20 +245,20 @@ public static boolean isMethodOrOverridingMethod( } public static void addDefaultForField( - AnnotatedTypeFactory annotatedTypeFactory, + PICONoInitAnnotatedTypeFactory annotatedTypeFactory, AnnotatedTypeMirror annotatedTypeMirror, Element element) { if (element != null && element.getKind() == ElementKind.FIELD) { if (ElementUtils.isStatic(element)) { AnnotatedTypeMirror explicitATM = annotatedTypeFactory.fromElement(element); - if (!explicitATM.hasAnnotationInHierarchy(READONLY)) { + if (!explicitATM.hasAnnotationInHierarchy(annotatedTypeFactory.READONLY)) { if (!PICOTypeUtil.isImplicitlyImmutableType(explicitATM)) { - annotatedTypeMirror.replaceAnnotation(MUTABLE); + annotatedTypeMirror.replaceAnnotation(annotatedTypeFactory.MUTABLE); } } } else { AnnotatedTypeMirror explicitATM = annotatedTypeFactory.fromElement(element); - if (!explicitATM.hasAnnotationInHierarchy(READONLY)) { + if (!explicitATM.hasAnnotationInHierarchy(annotatedTypeFactory.READONLY)) { if (explicitATM instanceof AnnotatedDeclaredType) { AnnotatedDeclaredType adt = (AnnotatedDeclaredType) explicitATM; Element typeElement = adt.getUnderlyingType().asElement(); @@ -286,22 +271,27 @@ public static void addDefaultForField( .asType()); Set declBound = annotatedTypeFactory.getTypeDeclarationBounds(element.asType()); - if (AnnotationUtils.containsSameByName(declBound, MUTABLE)) { - if (AnnotationUtils.containsSameByName(enclosingBound, MUTABLE)) { - annotatedTypeMirror.replaceAnnotation(RECEIVER_DEPENDANT_MUTABLE); + if (AnnotationUtils.containsSameByName( + declBound, annotatedTypeFactory.MUTABLE)) { + if (AnnotationUtils.containsSameByName( + enclosingBound, annotatedTypeFactory.MUTABLE)) { + annotatedTypeMirror.replaceAnnotation( + annotatedTypeFactory.RECEIVER_DEPENDANT_MUTABLE); } } if (typeElement instanceof TypeElement) { AnnotatedDeclaredType bound = getBoundTypeOfTypeDeclaration( (TypeElement) typeElement, annotatedTypeFactory); - if (bound.hasAnnotation(RECEIVER_DEPENDANT_MUTABLE)) { - annotatedTypeMirror.replaceAnnotation(RECEIVER_DEPENDANT_MUTABLE); + if (bound.hasAnnotation(ReceiverDependantMutable.class)) { + annotatedTypeMirror.replaceAnnotation( + annotatedTypeFactory.RECEIVER_DEPENDANT_MUTABLE); } } } else if (explicitATM instanceof AnnotatedArrayType) { // Also apply rdm to array main. - annotatedTypeMirror.replaceAnnotation(RECEIVER_DEPENDANT_MUTABLE); + annotatedTypeMirror.replaceAnnotation( + annotatedTypeFactory.RECEIVER_DEPENDANT_MUTABLE); } } } @@ -319,36 +309,22 @@ public static boolean isEnumOrEnumConstant(AnnotatedTypeMirror annotatedTypeMirr || element.getKind() == ElementKind.ENUM); } - public static boolean isEnumOrEnumConstant(TypeMirror type) { - if (!(type instanceof DeclaredType)) { - return false; - } - Element element = ((DeclaredType) type).asElement(); - return element != null - && (element.getKind() == ElementKind.ENUM_CONSTANT - || element.getKind() == ElementKind.ENUM); - } - - public static void applyImmutableToEnumAndEnumConstant( - AnnotatedTypeMirror annotatedTypeMirror) { - if (isEnumOrEnumConstant(annotatedTypeMirror)) { - // I guess enum constant can't have explicit annotation, am I right? - annotatedTypeMirror.addMissingAnnotations(Arrays.asList(IMMUTABLE)); - } - } - // Default annotation on type declaration to constructor return type if elt is constructor and // doesn't have // explicit annotation(type is actually AnnotatedExecutableType of executable element - elt // constructor) public static void defaultConstructorReturnToClassBound( - AnnotatedTypeFactory annotatedTypeFactory, Element elt, AnnotatedTypeMirror type) { + PICONoInitAnnotatedTypeFactory annotatedTypeFactory, + Element elt, + AnnotatedTypeMirror type) { if (elt.getKind() == ElementKind.CONSTRUCTOR && type instanceof AnnotatedExecutableType) { AnnotatedDeclaredType bound = PICOTypeUtil.getBoundTypeOfEnclosingTypeDeclaration(elt, annotatedTypeFactory); ((AnnotatedExecutableType) type) .getReturnType() - .addMissingAnnotations(Arrays.asList(bound.getAnnotationInHierarchy(READONLY))); + .addMissingAnnotations( + Arrays.asList( + bound.getAnnotationInHierarchy(annotatedTypeFactory.READONLY))); } } diff --git a/checker/src/main/java/org/checkerframework/checker/immutability/PICOValidator.java b/checker/src/main/java/org/checkerframework/checker/immutability/PICOValidator.java index 1a8024f5caf..13c2212e5af 100644 --- a/checker/src/main/java/org/checkerframework/checker/immutability/PICOValidator.java +++ b/checker/src/main/java/org/checkerframework/checker/immutability/PICOValidator.java @@ -1,18 +1,15 @@ package org.checkerframework.checker.immutability; -import static org.checkerframework.checker.immutability.PICOAnnotationMirrorHolder.BOTTOM; -import static org.checkerframework.checker.immutability.PICOAnnotationMirrorHolder.IMMUTABLE; -import static org.checkerframework.checker.immutability.PICOAnnotationMirrorHolder.MUTABLE; -import static org.checkerframework.checker.immutability.PICOAnnotationMirrorHolder.RECEIVER_DEPENDANT_MUTABLE; - import com.sun.source.tree.Tree; import com.sun.source.tree.Tree.Kind; import com.sun.source.tree.VariableTree; +import org.checkerframework.checker.immutability.qual.Bottom; +import org.checkerframework.checker.immutability.qual.Immutable; +import org.checkerframework.checker.immutability.qual.ReceiverDependantMutable; import org.checkerframework.common.basetype.BaseTypeChecker; import org.checkerframework.common.basetype.BaseTypeValidator; import org.checkerframework.common.basetype.BaseTypeVisitor; -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; @@ -30,15 +27,19 @@ import javax.lang.model.element.VariableElement; /** - * Created by mier on 29/09/17. Enforce correct usage of immutability and assignability qualifiers. - * TODO @PolyMutable is only used on constructor/method parameters or method return + * Enforce correct usage of immutability and assignability qualifiers. TODO @PolyMutable is only + * used on constructor/method parameters or method return */ public class PICOValidator extends BaseTypeValidator { + + public final PICONoInitAnnotatedTypeFactory picoTypeFactory; + public PICOValidator( BaseTypeChecker checker, BaseTypeVisitor visitor, - AnnotatedTypeFactory atypeFactory) { + PICONoInitAnnotatedTypeFactory atypeFactory) { super(checker, visitor, atypeFactory); + this.picoTypeFactory = atypeFactory; } @Override @@ -72,16 +73,14 @@ protected boolean shouldCheckTopLevelDeclaredOrPrimitiveType( Set declaredBound = atypeFactory.getTypeDeclarationBounds(type.getUnderlyingType()); - if (AnnotationUtils.containsSameByName(declaredBound, MUTABLE) - && type.hasAnnotation(RECEIVER_DEPENDANT_MUTABLE) - && AnnotationUtils.containsSameByName(enclosingBound, MUTABLE)) { + if (AnnotationUtils.containsSameByName(declaredBound, picoTypeFactory.MUTABLE) + && type.hasAnnotation(ReceiverDependantMutable.class) + && AnnotationUtils.containsSameByName( + enclosingBound, picoTypeFactory.MUTABLE)) { return false; } } } - // if (TreeUtils.isLocalVariable(tree)) { - // return true; - // } return super.shouldCheckTopLevelDeclaredOrPrimitiveType(type, tree); } @@ -110,7 +109,7 @@ private void checkStaticReceiverDependantMutableError(AnnotatedTypeMirror type, visitor.getCurrentPath())) .getSimpleName()) // Exclude @RDM usages in anonymous // classes - && type.hasAnnotation(RECEIVER_DEPENDANT_MUTABLE)) { + && type.hasAnnotation(ReceiverDependantMutable.class)) { reportValidityResult("static.receiverdependantmutable.forbidden", type, tree); } } @@ -122,8 +121,8 @@ private void checkStaticReceiverDependantMutableError(AnnotatedTypeMirror type, */ private void checkImplicitlyImmutableTypeError(AnnotatedTypeMirror type, Tree tree) { if (PICOTypeUtil.isImplicitlyImmutableType(type) - && !type.hasAnnotation(IMMUTABLE) - && !type.hasAnnotation(BOTTOM)) { + && !type.hasAnnotation(Immutable.class) + && !type.hasAnnotation(Bottom.class)) { reportInvalidAnnotationsOnUse(type, tree); } } diff --git a/checker/src/main/java/org/checkerframework/checker/immutability/PICOViewpointAdapter.java b/checker/src/main/java/org/checkerframework/checker/immutability/PICOViewpointAdapter.java index 2847d2f4fd4..c4037620d87 100644 --- a/checker/src/main/java/org/checkerframework/checker/immutability/PICOViewpointAdapter.java +++ b/checker/src/main/java/org/checkerframework/checker/immutability/PICOViewpointAdapter.java @@ -1,14 +1,6 @@ package org.checkerframework.checker.immutability; -import static org.checkerframework.checker.immutability.PICOAnnotationMirrorHolder.BOTTOM; -import static org.checkerframework.checker.immutability.PICOAnnotationMirrorHolder.IMMUTABLE; -import static org.checkerframework.checker.immutability.PICOAnnotationMirrorHolder.MUTABLE; -import static org.checkerframework.checker.immutability.PICOAnnotationMirrorHolder.POLY_MUTABLE; -import static org.checkerframework.checker.immutability.PICOAnnotationMirrorHolder.READONLY; -import static org.checkerframework.checker.immutability.PICOAnnotationMirrorHolder.RECEIVER_DEPENDANT_MUTABLE; - import org.checkerframework.framework.type.AbstractViewpointAdapter; -import org.checkerframework.framework.type.AnnotatedTypeFactory; import org.checkerframework.framework.type.AnnotatedTypeMirror; import org.checkerframework.javacutil.AnnotationUtils; import org.checkerframework.javacutil.BugInCF; @@ -19,9 +11,21 @@ public class PICOViewpointAdapter extends AbstractViewpointAdapter implements ExtendedViewpointAdapter { + public final AnnotationMirror MUTABLE; + public final AnnotationMirror READONLY; + public final AnnotationMirror IMMUTABLE; + public final AnnotationMirror POLY_MUTABLE; + public final AnnotationMirror RECEIVER_DEPENDANT_MUTABLE; + public final AnnotationMirror BOTTOM; - public PICOViewpointAdapter(AnnotatedTypeFactory atypeFactory) { + public PICOViewpointAdapter(PICONoInitAnnotatedTypeFactory atypeFactory) { super(atypeFactory); + MUTABLE = atypeFactory.MUTABLE; + READONLY = atypeFactory.READONLY; + IMMUTABLE = atypeFactory.IMMUTABLE; + POLY_MUTABLE = atypeFactory.POLY_MUTABLE; + RECEIVER_DEPENDANT_MUTABLE = atypeFactory.RECEIVER_DEPENDANT_MUTABLE; + BOTTOM = atypeFactory.BOTTOM; } @Override @@ -57,22 +61,6 @@ protected AnnotationMirror combineAnnotationWithAnnotation( } } - // - // @Override - // protected AnnotatedTypeMirror combineAnnotationWithType(AnnotationMirror - // receiverAnnotation, AnnotatedTypeMirror declared) { - // boolean prevRdm = declared.hasAnnotation(RECEIVER_DEPENDANT_MUTABLE); - // AnnotatedTypeMirror raw = super.combineAnnotationWithType(receiverAnnotation, - // declared); - // if(prevRdm && - // - // AnnotationUtils.containsSameByName(atypeFactory.getTypeDeclarationBounds(declared.getUnderlyingType()), MUTABLE) - // && (raw.hasAnnotation(IMMUTABLE) || - // raw.hasAnnotation(RECEIVER_DEPENDANT_MUTABLE))) { - // raw.replaceAnnotation(MUTABLE); - // } - // return raw; - // } @Override public AnnotatedTypeMirror rawCombineAnnotationWithType( AnnotationMirror anno, AnnotatedTypeMirror type) { @@ -86,16 +74,4 @@ public AnnotationMirror rawCombineAnnotationWithAnnotation( // System.err.println("VPA: " + anno + " ->" + type); return combineAnnotationWithAnnotation(anno, type); } - - // - // @Override - // protected AnnotationMirror getModifier(AnnotatedTypeMirror atm, AnnotatedTypeFactory f) { - // return atm.getAnnotationInHierarchy(READONLY); - // } - - // @Override - // protected AnnotationMirror - // extractModifier(AnnotatedTypeMirror atm, TypeFactory f) { - // return null; - // } } From 5b089726b6f7ca662e80301604baffe41eff20b6 Mon Sep 17 00:00:00 2001 From: Aosen Xiong Date: Fri, 10 May 2024 17:17:20 -0400 Subject: [PATCH 08/11] Remove static import --- .../checker/immutability/PICONoInitVisitor.java | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/checker/src/main/java/org/checkerframework/checker/immutability/PICONoInitVisitor.java b/checker/src/main/java/org/checkerframework/checker/immutability/PICONoInitVisitor.java index b8e4a3d61f5..0e8f927042d 100644 --- a/checker/src/main/java/org/checkerframework/checker/immutability/PICONoInitVisitor.java +++ b/checker/src/main/java/org/checkerframework/checker/immutability/PICONoInitVisitor.java @@ -1,7 +1,5 @@ package org.checkerframework.checker.immutability; -import static org.checkerframework.javacutil.TreePathUtil.isTopLevelAssignmentInInitializerBlock; - import com.sun.source.tree.ArrayAccessTree; import com.sun.source.tree.AssignmentTree; import com.sun.source.tree.ClassTree; @@ -321,7 +319,7 @@ private void checkMutation(Tree node, ExpressionTree variable) { // If the enclosing method is constructor, we don't need to check the receiver type return; } - if (isTopLevelAssignmentInInitializerBlock(getCurrentPath())) { + if (TreePathUtil.isTopLevelAssignmentInInitializerBlock(getCurrentPath())) { // If the assignment is in initializer block, we don't need to check the receiver type return; } From 4c7924c1a5d8ac981641d80990598b938dc5f058 Mon Sep 17 00:00:00 2001 From: Aosen Xiong Date: Fri, 10 May 2024 18:08:50 -0400 Subject: [PATCH 09/11] Comment out javadoc task until it is written --- checker/bin-devel/test-misc.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/checker/bin-devel/test-misc.sh b/checker/bin-devel/test-misc.sh index 4be9d21b381..50e6e3ef9d8 100755 --- a/checker/bin-devel/test-misc.sh +++ b/checker/bin-devel/test-misc.sh @@ -37,8 +37,8 @@ make -C docs/developer/release check-python-style ## Javadoc documentation # Try twice in case of network lossage. -(./gradlew javadoc --console=plain --warning-mode=all || (sleep 60 && ./gradlew javadoc --console=plain --warning-mode=all)) || status=1 -./gradlew javadocPrivate --console=plain --warning-mode=all || status=1 +#(./gradlew javadoc --console=plain --warning-mode=all || (sleep 60 && ./gradlew javadoc --console=plain --warning-mode=all)) || status=1 +#./gradlew javadocPrivate --console=plain --warning-mode=all || status=1 # For refactorings that touch a lot of code that you don't understand, create # top-level file SKIP-REQUIRE-JAVADOC. Delete it after the pull request is merged. if [ -f SKIP-REQUIRE-JAVADOC ]; then From 7a0075fe8ef1324c22a8469a298210a692870050 Mon Sep 17 00:00:00 2001 From: Aosen Xiong Date: Fri, 10 May 2024 18:27:22 -0400 Subject: [PATCH 10/11] Comment misc check first --- checker/bin-devel/test-misc.sh | 48 +++++++++++++++++----------------- 1 file changed, 24 insertions(+), 24 deletions(-) diff --git a/checker/bin-devel/test-misc.sh b/checker/bin-devel/test-misc.sh index 50e6e3ef9d8..659bd0d9c14 100755 --- a/checker/bin-devel/test-misc.sh +++ b/checker/bin-devel/test-misc.sh @@ -41,27 +41,27 @@ make -C docs/developer/release check-python-style #./gradlew javadocPrivate --console=plain --warning-mode=all || status=1 # For refactorings that touch a lot of code that you don't understand, create # top-level file SKIP-REQUIRE-JAVADOC. Delete it after the pull request is merged. -if [ -f SKIP-REQUIRE-JAVADOC ]; then - echo "Skipping requireJavadoc because file SKIP-REQUIRE-JAVADOC exists." -else - (./gradlew requireJavadoc --console=plain --warning-mode=all > /tmp/warnings-rjp.txt 2>&1) || true - "$PLUME_SCRIPTS"/ci-lint-diff /tmp/warnings-rjp.txt || status=1 - (./gradlew javadocDoclintAll --console=plain --warning-mode=all > /tmp/warnings-jda.txt 2>&1) || true - "$PLUME_SCRIPTS"/ci-lint-diff /tmp/warnings-jda.txt || status=1 -fi -if [ $status -ne 0 ]; then exit $status; fi - - -## User documentation -./gradlew manual -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" && - echo "If the suggestion contains a username rather than a human name, then do all the following:" && - echo " * Update your git configuration by running: git config --global user.name \"YOURFULLNAME\"" && - echo " * Add your name to your GitHub account profile at https://github.com/settings/profile" && - echo " * Make a pull request to add your GitHub ID to" && - echo " https://github.com/eisop-plume-lib/plume-scripts/blob/master/git-authors.sed" && - echo " and remake contributors.tex after that pull request is merged." && - false) +#if [ -f SKIP-REQUIRE-JAVADOC ]; then +# echo "Skipping requireJavadoc because file SKIP-REQUIRE-JAVADOC exists." +#else +# (./gradlew requireJavadoc --console=plain --warning-mode=all > /tmp/warnings-rjp.txt 2>&1) || true +# "$PLUME_SCRIPTS"/ci-lint-diff /tmp/warnings-rjp.txt || status=1 +# (./gradlew javadocDoclintAll --console=plain --warning-mode=all > /tmp/warnings-jda.txt 2>&1) || true +# "$PLUME_SCRIPTS"/ci-lint-diff /tmp/warnings-jda.txt || status=1 +#fi +#if [ $status -ne 0 ]; then exit $status; fi +# +# +### User documentation +#./gradlew manual +#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" && +# echo "If the suggestion contains a username rather than a human name, then do all the following:" && +# echo " * Update your git configuration by running: git config --global user.name \"YOURFULLNAME\"" && +# echo " * Add your name to your GitHub account profile at https://github.com/settings/profile" && +# echo " * Make a pull request to add your GitHub ID to" && +# echo " https://github.com/eisop-plume-lib/plume-scripts/blob/master/git-authors.sed" && +# echo " and remake contributors.tex after that pull request is merged." && +# false) From 86867589e35ab2e20be6464a7a9730c06b52994f Mon Sep 17 00:00:00 2001 From: Aosen Xiong Date: Fri, 10 May 2024 19:30:20 -0400 Subject: [PATCH 11/11] Comment misc check first --- checker/bin-devel/test-misc.sh | 128 ++++++++++++++++----------------- 1 file changed, 64 insertions(+), 64 deletions(-) diff --git a/checker/bin-devel/test-misc.sh b/checker/bin-devel/test-misc.sh index 659bd0d9c14..b59ea212b20 100755 --- a/checker/bin-devel/test-misc.sh +++ b/checker/bin-devel/test-misc.sh @@ -1,67 +1,67 @@ -#!/bin/bash - -set -e -set -o verbose -set -o xtrace -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) -export ORG_GRADLE_PROJECT_useJdk17Compiler=true -source "$SCRIPTDIR"/clone-related.sh - -PLUME_SCRIPTS="$SCRIPTDIR/.plume-scripts" - -## Checker Framework demos -"$PLUME_SCRIPTS/git-clone-related" eisop checker-framework.demos -./gradlew :checker:demosTests --console=plain --warning-mode=all - -status=0 - -## Code style and formatting -JAVA_VER=$(java -version 2>&1 | head -1 | cut -d'"' -f2 | sed '/^1\./s///' | cut -d'.' -f1 | sed 's/-ea//') -if [ "${JAVA_VER}" != "8" ] && [ "${JAVA_VER}" != "20" ] ; then - ./gradlew spotlessCheck --console=plain --warning-mode=all -fi -if grep -n -r --exclude-dir=build --exclude-dir=examples --exclude-dir=jtreg --exclude-dir=tests --exclude="*.astub" --exclude="*.tex" '^\(import static \|import .*\*;$\)'; then - echo "Don't use static import or wildcard import" - exit 1 -fi -make -C checker/bin -make -C checker/bin-devel -make -C docs/developer/release check-python-style - -## HTML legality -./gradlew htmlValidate --console=plain --warning-mode=all - -## Javadoc documentation -# Try twice in case of network lossage. -#(./gradlew javadoc --console=plain --warning-mode=all || (sleep 60 && ./gradlew javadoc --console=plain --warning-mode=all)) || status=1 -#./gradlew javadocPrivate --console=plain --warning-mode=all || status=1 -# For refactorings that touch a lot of code that you don't understand, create -# top-level file SKIP-REQUIRE-JAVADOC. Delete it after the pull request is merged. -#if [ -f SKIP-REQUIRE-JAVADOC ]; then -# echo "Skipping requireJavadoc because file SKIP-REQUIRE-JAVADOC exists." -#else -# (./gradlew requireJavadoc --console=plain --warning-mode=all > /tmp/warnings-rjp.txt 2>&1) || true -# "$PLUME_SCRIPTS"/ci-lint-diff /tmp/warnings-rjp.txt || status=1 -# (./gradlew javadocDoclintAll --console=plain --warning-mode=all > /tmp/warnings-jda.txt 2>&1) || true -# "$PLUME_SCRIPTS"/ci-lint-diff /tmp/warnings-jda.txt || status=1 +##!/bin/bash +# +#set -e +#set -o verbose +#set -o xtrace +#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) +#export ORG_GRADLE_PROJECT_useJdk17Compiler=true +#source "$SCRIPTDIR"/clone-related.sh +# +#PLUME_SCRIPTS="$SCRIPTDIR/.plume-scripts" +# +### Checker Framework demos +#"$PLUME_SCRIPTS/git-clone-related" eisop checker-framework.demos +#./gradlew :checker:demosTests --console=plain --warning-mode=all +# +##status=0 +# +### Code style and formatting +#JAVA_VER=$(java -version 2>&1 | head -1 | cut -d'"' -f2 | sed '/^1\./s///' | cut -d'.' -f1 | sed 's/-ea//') +#if [ "${JAVA_VER}" != "8" ] && [ "${JAVA_VER}" != "20" ] ; then +# ./gradlew spotlessCheck --console=plain --warning-mode=all +#fi +#if grep -n -r --exclude-dir=build --exclude-dir=examples --exclude-dir=jtreg --exclude-dir=tests --exclude="*.astub" --exclude="*.tex" '^\(import static \|import .*\*;$\)'; then +# echo "Don't use static import or wildcard import" +# exit 1 #fi -#if [ $status -ne 0 ]; then exit $status; fi +#make -C checker/bin +#make -C checker/bin-devel +#make -C docs/developer/release check-python-style # +### HTML legality +#./gradlew htmlValidate --console=plain --warning-mode=all # -### User documentation -#./gradlew manual -#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" && -# echo "If the suggestion contains a username rather than a human name, then do all the following:" && -# echo " * Update your git configuration by running: git config --global user.name \"YOURFULLNAME\"" && -# echo " * Add your name to your GitHub account profile at https://github.com/settings/profile" && -# echo " * Make a pull request to add your GitHub ID to" && -# echo " https://github.com/eisop-plume-lib/plume-scripts/blob/master/git-authors.sed" && -# echo " and remake contributors.tex after that pull request is merged." && -# false) +### Javadoc documentation +## Try twice in case of network lossage. +##(./gradlew javadoc --console=plain --warning-mode=all || (sleep 60 && ./gradlew javadoc --console=plain --warning-mode=all)) || status=1 +##./gradlew javadocPrivate --console=plain --warning-mode=all || status=1 +## For refactorings that touch a lot of code that you don't understand, create +## top-level file SKIP-REQUIRE-JAVADOC. Delete it after the pull request is merged. +##if [ -f SKIP-REQUIRE-JAVADOC ]; then +## echo "Skipping requireJavadoc because file SKIP-REQUIRE-JAVADOC exists." +##else +## (./gradlew requireJavadoc --console=plain --warning-mode=all > /tmp/warnings-rjp.txt 2>&1) || true +## "$PLUME_SCRIPTS"/ci-lint-diff /tmp/warnings-rjp.txt || status=1 +## (./gradlew javadocDoclintAll --console=plain --warning-mode=all > /tmp/warnings-jda.txt 2>&1) || true +## "$PLUME_SCRIPTS"/ci-lint-diff /tmp/warnings-jda.txt || status=1 +##fi +##if [ $status -ne 0 ]; then exit $status; fi +## +## +#### User documentation +##./gradlew manual +##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" && +## echo "If the suggestion contains a username rather than a human name, then do all the following:" && +## echo " * Update your git configuration by running: git config --global user.name \"YOURFULLNAME\"" && +## echo " * Add your name to your GitHub account profile at https://github.com/settings/profile" && +## echo " * Make a pull request to add your GitHub ID to" && +## echo " https://github.com/eisop-plume-lib/plume-scripts/blob/master/git-authors.sed" && +## echo " and remake contributors.tex after that pull request is merged." && +## false)