"
+ // 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..2a151a9f3e0
--- /dev/null
+++ b/checker/tests/immutability/Planet.java
@@ -0,0 +1,94 @@
+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;
+
+/** 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..dde1ab53920
--- /dev/null
+++ b/checker/tests/immutability/Primitive.java
@@ -0,0 +1,31 @@
+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..99b951dbc46
--- /dev/null
+++ b/checker/tests/immutability/ReadonlyConstructor.java
@@ -0,0 +1,7 @@
+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..223125c6d43
--- /dev/null
+++ b/checker/tests/immutability/ReceiverDependantMutableConstructor.java
@@ -0,0 +1,63 @@
+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..3a633908dd1
--- /dev/null
+++ b/checker/tests/immutability/ReceiverTypeOutsideConstructor.java
@@ -0,0 +1,141 @@
+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..ee07e9e3472
--- /dev/null
+++ b/checker/tests/immutability/Static.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.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(
+ // :: error: (static.receiverdependantmutable.forbidden)
+ @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 super A> l) {}
+
+ void test2(List super B> l) {}
+
+ void test3(List super C> l) {}
+}
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 extends E> var1);
+ boolean add(E var1);
+ boolean addAll(@Readonly Collection extends E> 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 extends E> c);
+ boolean addAll(int index, @Readonly Collection extends E> 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 extends E> c);
+}
+
+@ReceiverDependantMutable
+class HashSet {
+ @ReceiverDependantMutable HashSet();
+ @ReceiverDependantMutable HashSet(@Readonly Collection extends E> 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 extends K, ? extends V> 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 extends K, ? extends V> 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 extends T> 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);
+}