package chapter; // This test contains the sample code from the Lock Checker manual chapter // modified to fit testing instead of illustrative purposes, // and contains other miscellaneous Lock Checker testing. import java.util.AbstractCollection; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Iterator; import java.util.concurrent.locks.ReentrantLock; import org.checkerframework.checker.lock.qual.GuardSatisfied; import org.checkerframework.checker.lock.qual.GuardedBy; import org.checkerframework.checker.lock.qual.GuardedByBottom; import org.checkerframework.checker.lock.qual.GuardedByUnknown; import org.checkerframework.checker.lock.qual.Holding; import org.checkerframework.checker.lock.qual.LockingFree; import org.checkerframework.checker.lock.qual.MayReleaseLocks; import org.checkerframework.checker.lock.qual.ReleasesNoLocks; import org.checkerframework.checker.nullness.qual.NonNull; class ChapterExamples { // This code crashed when there was a bug before issue 524 was fixed. // An attempt to take the LUB between 'val' in the store with type 'long' // and 'val' in another store with type 'none' resulted in a crash. private void foo(boolean b, int a) { if (b) { if (a == 0) { boolean val = false; } else if (a == 1) { int val = 0; } else if (a == 2) { long val = 0; } else if (a == 3) { } } else { if (true) {} } } private abstract class Values<V> extends AbstractCollection<V> { public <T> T[] toArray(T[] a) { Collection<V> c = new ArrayList<V>(size()); for (Iterator<V> i = iterator(); i.hasNext(); ) { c.add(i.next()); } return c.toArray(a); } } // @GuardedByBottom, which represents the 'null' literal, is the default lower bound, // so null can be returned in the following two methods: <T> T method1(T t, boolean b) { return b ? null : t; } <T> T method2(T t, boolean b) { return null; } void bar(@NonNull Object nn1, boolean b) { @NonNull Object nn2 = method1(nn1, b); @NonNull Object nn3 = method2(nn1, b); } void bar2(@GuardedByBottom Object bottomParam, boolean b) { @GuardedByUnknown Object refinedToBottom1 = method1(bottomParam, b); @GuardedByUnknown Object refinedToBottom2 = method2(bottomParam, b); @GuardedByBottom Object bottom1 = method1(bottomParam, b); @GuardedByBottom Object bottom2 = method2(bottomParam, b); } private static boolean eq(@GuardSatisfied Object o1, @GuardSatisfied Object o2) { return (o1 == null ? o2 == null : o1.equals(o2)); } public <K extends @GuardedBy({}) Object, V extends @GuardedBy({}) Object> void put( K key, V value) { @SuppressWarnings("unchecked") K k = (K) maskNull(key); } class GuardedByUnknownTest<T extends @GuardedByUnknown MyClass> { T m; void test() { //:: error: (method.invocation.invalid) m.method(); @GuardedByUnknown MyClass local = new @GuardedByUnknown MyClass(); //:: error: (lock.not.held) local.field = new Object(); //:: error: (method.invocation.invalid) local.method(); //:: error: (lock.not.held) m.field = new Object(); } } class MyClass { Object field = new Object(); @LockingFree Object method(@GuardSatisfied MyClass this) { return new Object(); } @LockingFree public @GuardSatisfied(1) MyClass append( @GuardSatisfied(1) MyClass this, @GuardSatisfied(2) MyClass m) { return this; } final Object myLock = new Object(); void testCallToMethod(@GuardedBy("myLock") MyClass this) { //:: error: (lock.not.held) this.method(); // method()'s receiver is annotated as @GuardSatisfied } } @MayReleaseLocks @ReleasesNoLocks // TODO: enable (multiple.sideeffect.annotation) void testMultipleSideEffectAnnotations() {} void guardedByItselfOnReceiver(@GuardedBy("<self>") ChapterExamples this) { synchronized ( this) { // Tests translation of '<self>' to 'this' by the LockVisitor for this scenario. // myField = new MyClass(); myField.toString(); this.myField = new MyClass(); this.myField.toString(); } //:: error: (lock.not.held) myField = new MyClass(); //:: error: (lock.not.held) myField.toString(); //:: error: (lock.not.held) this.myField = new MyClass(); //:: error: (lock.not.held) this.myField.toString(); } void guardedByThisOnReceiver(@GuardedBy("this") ChapterExamples this) { //:: error: (lock.not.held) myField = new MyClass(); //:: error: (lock.not.held) myField.toString(); //:: error: (lock.not.held) this.myField = new MyClass(); //:: error: (lock.not.held) this.myField.toString(); synchronized (this) { myField = new MyClass(); myField.toString(); this.myField = new MyClass(); this.myField.toString(); } } void testDereferenceOfReceiverAndParameter( @GuardedBy("lock") ChapterExamples this, @GuardedBy("lock") MyClass m) { //:: error: (lock.not.held) myField = new MyClass(); //:: error: (lock.not.held) myField.toString(); //:: error: (lock.not.held) this.myField = new MyClass(); //:: error: (lock.not.held) this.myField.toString(); //:: error: (lock.not.held) m.field = new Object(); //:: error: (lock.not.held) m.field.toString(); // The following error is due to the fact that you cannot access "this.lock" without first having acquired "lock". // The right fix in a user scenario would be to not guard "this" with "this.lock". The current object could instead // be guarded by "<self>" or by some other lock expression that is not one of its fields. We are keeping this test // case here to make sure this scenario issues a warning. //:: error: (lock.not.held) synchronized (lock) { myField = new MyClass(); myField.toString(); this.myField = new MyClass(); this.myField.toString(); m.field = new Object(); m.field.toString(); } } @GuardedBy("lock") MyClass myObj = new MyClass(); @LockingFree @GuardedBy("lock") MyClass myMethodReturningMyObj() { return myObj; } ChapterExamples() { lock = new Object(); } void myMethod8() { //:: error: (lock.not.held) boolean b4 = compare(p1, myMethod()); // An error is issued indicating that p2 might be dereferenced without // "lock" being held. The method call need not be modified, since // @GuardedBy({}) <: @GuardedByUnknown and @GuardedBy("lock") <: @GuardedByUnknown, // but the lock must be acquired prior to the method call. //:: error: (lock.not.held) boolean b2 = compare(p1, p2); //:: error: (lock.not.held) boolean b3 = compare(p1, this.p2); //:: error: (lock.not.held) boolean b5 = compare(p1, this.myMethod()); synchronized (lock) { boolean b6 = compare(p1, p2); // OK boolean b7 = compare(p1, this.p2); // OK boolean b8 = compare(p1, myMethod()); // OK boolean b9 = compare(p1, this.myMethod()); // OK } } // Keep in mind, the expression itself may or may not be a // method call. Simple examples of expression.identifier : // myObject.field // myMethod().field // myObject.method() // myMethod().method() void myMethod7() { //:: error: (lock.not.held) Object f = myObj.field; //:: error: (lock.not.held) Object f2 = myMethodReturningMyObj().field; //:: error: (lock.not.held) myObj.method(); // method()'s receiver is annotated as @GuardSatisfied //:: error: (lock.not.held) myMethodReturningMyObj().method(); // method()'s receiver is annotated as @GuardSatisfied synchronized (lock) { f = myObj.field; f2 = myMethodReturningMyObj().field; myObj.method(); myMethodReturningMyObj().method(); } //:: error: (lock.not.held) myMethodReturningMyObj().field = new Object(); //:: error: (lock.not.held) x.field = new Object(); synchronized (lock) { myMethod().field = new Object(); } synchronized (lock) { x.field = new Object(); // toString is not LockingFree. How annoying. } this.x = new MyClass(); } final Object lock; // Initialized in the constructor @GuardedBy("lock") MyClass x = new MyClass(); @GuardedBy("lock") MyClass y = x; // OK, because dereferences of y will require "lock" to be held. //:: error: (assignment.type.incompatible) @GuardedBy({}) MyClass z = x; // ILLEGAL because dereferences of z do not require "lock" to be held. @LockingFree @GuardedBy("lock") MyClass myMethod() { return x; // OK because the return type is @GuardedBy("lock") } void exampleMethod() { //:: error: (lock.not.held) x.field = new Object(); // ILLEGAL because the lock is not known to be held //:: error: (lock.not.held) y.field = new Object(); // ILLEGAL because the lock is not known to be held //:: error: (lock.not.held) myMethod().field = new Object(); // ILLEGAL because the lock is not known to be held synchronized (lock) { x.field = new Object(); // OK: the lock is known to be held y.field = new Object(); // OK: the lock is known to be held myMethod().field = new Object(); // OK: the lock is known to be held } } final MyClass a = new MyClass(); final MyClass b = new MyClass(); @GuardedBy("a") MyClass x5 = new MyClass(); @GuardedBy({"a", "b"}) MyClass y5 = new MyClass(); void myMethod2() { //:: error: (assignment.type.incompatible) y5 = x5; // ILLEGAL } //:: error: (immutable.type.guardedby) @GuardedBy("a") String s = "string"; @GuardedBy({}) MyClass o1; @GuardedBy("lock") MyClass o2; @GuardedBy("lock") MyClass o3; void someMethod() { o3 = o2; // OK, since o2 and o3 are guarded by exactly the same lock set. //:: error: (assignment.type.incompatible) o1 = o2; // Assignment type incompatible errors are issued for both assignments, since //:: error: (assignment.type.incompatible) o2 = o1; // {"lock"} and {} are not identical sets. } @SuppressWarnings("lock:cast.unsafe") void someMethod2() { // A cast can be used if the user knows it is safe to do so. // However, the @SuppressWarnings must be added. o1 = (@GuardedBy({}) MyClass) o2; } static final Object myLock = new Object(); @GuardedBy("ChapterExamples.myLock") MyClass myMethod3() { return new MyClass(); } // reassignments without holding the lock are OK. @GuardedBy("ChapterExamples.myLock") MyClass x2 = myMethod3(); @GuardedBy("ChapterExamples.myLock") MyClass y2 = x2; void myMethod4() { //:: error: (lock.not.held) x2.field = new Object(); // ILLEGAL because the lock is not held synchronized (ChapterExamples.myLock) { y2.field = new Object(); // OK: the lock is held } } void myMethod5(@GuardedBy("ChapterExamples.myLock") MyClass a) { //:: error: (lock.not.held) a.field = new Object(); // ILLEGAL: the lock is not held synchronized (ChapterExamples.myLock) { a.field = new Object(); // OK: the lock is held } } @LockingFree boolean compare(@GuardSatisfied MyClass a, @GuardSatisfied MyClass b) { return true; } @GuardedBy({}) MyClass p1; @GuardedBy("lock") MyClass p2; void myMethod6() { // It is the responsibility of callers to 'compare' to acquire the lock. synchronized (lock) { boolean b1 = compare(p1, p2); // OK. No error issued. } //:: error: (lock.not.held) p2.field = new Object(); // An error is issued indicating that p2 might be dereferenced without "lock" being held. The method call need not be modified, since @GuardedBy({}) <: @GuardedByUnknown and @GuardedBy("lock") <: @GuardedByUnknown, but the lock must be acquired prior to the method call. //:: error: (lock.not.held) boolean b2 = compare(p1, p2); } void helper1(@GuardedBy("ChapterExamples.myLock") MyClass a) { //:: error: (lock.not.held) a.field = new Object(); // ILLEGAL: the lock is not held synchronized (ChapterExamples.myLock) { a.field = new Object(); // OK: the lock is held } } @Holding("ChapterExamples.myLock") @LockingFree void helper2(@GuardedBy("ChapterExamples.myLock") MyClass b) { b.field = new Object(); // OK: the lock is held } @LockingFree void helper3(@GuardSatisfied MyClass c) { c.field = new Object(); // OK: the guard is satisfied } @LockingFree void helper4(@GuardedBy("ChapterExamples.myLock") MyClass d) { //:: error: (lock.not.held) d.field = new Object(); // ILLEGAL: the lock is not held } @ReleasesNoLocks void helper5() {} // No annotation means @ReleasesNoLocks void helper6() {} void myMethod2(@GuardedBy("ChapterExamples.myLock") MyClass e) { helper1(e); // OK to pass to another routine without holding the lock. //:: error: (lock.not.held) e.field = new Object(); // ILLEGAL: the lock is not held //:: error: (contracts.precondition.not.satisfied) helper2(e); //:: error: (lock.not.held) helper3(e); synchronized (ChapterExamples.myLock) { helper2(e); helper3(e); // OK, since parameter is @GuardSatisfied helper4(e); // OK, but helper4's body still has an error. helper5(); helper6(); helper2(e); // Can still be called after helper5() and helper6() } } private @GuardedBy({}) MyClass myField; // TODO: For now, boxed types are treated as primitive types. This may change in the future. void unboxing() { int a = 1; //:: error: (immutable.type.guardedby) @GuardedBy("lock") Integer c; synchronized (lock) { //:: error: (assignment.type.incompatible) c = a; } //:: error: (immutable.type.guardedby) @GuardedBy("lock") Integer b = 1; int d; synchronized (lock) { //:: error: (assignment.type.incompatible) d = b; // Expected, since b cannot be @GuardedBy("lock") since it is a boxed primitive. //:: error: (method.invocation.invalid) d = b.intValue(); // The de-sugared version does not issue an error. } c = c + b; // Syntactic sugar for c = new Integer(c.intValue() + b.intValue()). // Expected, since b and c cannot be @GuardedBy("lock") since they are boxed primitives. //:: error: (method.invocation.invalid) c = new Integer(c.intValue() + b.intValue()); // The de-sugared version synchronized (lock) { c = c + b; // Syntactic sugar for c = new Integer(c.intValue() + b.intValue()). // Expected, since b and c cannot be @GuardedBy("lock") since they are boxed primitives. //:: error: (method.invocation.invalid) c = new Integer(c.intValue() + b.intValue()); // The de-sugared version } //:: error: (assignment.type.incompatible) a = b; b = c; // OK } /* TODO Re-enable when guarding primitives is supported by the Lock Checker. void boxingUnboxing() { @GuardedBy("lock") int a = 1; @GuardedBy({}) Integer c; synchronized(lock) { c = a; } @GuardedBy("lock") Integer b = 1; @GuardedBy({}) int d; synchronized(lock) { // TODO re-enable this error (assignment.type.incompatible) d = b; // TODO: This should not result in assignment.type.incompatible because 'b' is actually syntactic sugar for b.intValue(). d = b.intValue(); // The de-sugared version does not issue an error. } // TODO re-enable this error (lock.not.held) c = c + b; // Syntactic sugar for c = new Integer(c.intValue() + b.intValue()), hence 'lock' must be held. // TODO re-enable this error (lock.not.held) c = new Integer(c.intValue() + b.intValue()); // The de-sugared version synchronized(lock) { c = c + b; // Syntactic sugar for c = new Integer(c.intValue() + b.intValue()), hence 'lock' must be held. c = new Integer(c.intValue() + b.intValue()); // The de-sugared version } // TODO re-enable this error (lock.not.held) a = b; // TODO: This assignment between two reference types should not require a lock to be held. }*/ final ReentrantLock lock1 = new ReentrantLock(); final ReentrantLock lock2 = new ReentrantLock(); @GuardedBy("lock1") MyClass filename; @GuardedBy("lock2") MyClass extension; void method0() { //:: error: (lock.not.held) :: error: (lock.not.held) filename = filename.append(extension); } void method1() { lock1.lock(); //:: error: (lock.not.held) filename = filename.append(extension); } void method2() { lock2.lock(); //:: error: (lock.not.held) filename = filename.append(extension); } void method3() { lock1.lock(); lock2.lock(); filename = filename.append(extension); filename = filename.append(null); //:: error: (assignment.type.incompatible) filename = extension.append(extension); //:: error: (assignment.type.incompatible) filename = extension.append(filename); } void matchingGSparams(@GuardSatisfied(1) MyClass m1, @GuardSatisfied(1) MyClass m2) {} void method4() { lock1.lock(); lock2.lock(); matchingGSparams(filename, null); matchingGSparams(null, filename); } public static boolean deepEquals(Object o1, Object o2) { if (o1 instanceof Object[] && o2 instanceof Object[]) { return Arrays.deepEquals((Object[]) o1, (Object[]) o2); } return false; } public static final class Comparer<T extends Comparable<T>> { public boolean compare(T[] a1, T[] a2) { T elt1 = a1[0]; T elt2 = a2[0]; return elt1.equals(elt2); } } public static <T extends @GuardedBy({}) Object> boolean indexOf(T[] a, Object elt) { if (elt.equals(a[0])) { return false; } return true; // found : (@org.checkerframework.checker.lock.qual.GuardedBy({}) :: T)[ extends @GuardedByUnknown @LockPossiblyHeld Object super @GuardedBy({}) @LockHeld Void] // required: @GuardedBy @LockPossiblyHeld Object } private static final Object NULL_KEY = new Object(); // A guardsatisfied.location.disallowed error is issued for the cast. @SuppressWarnings({"cast.unsafe", "guardsatisfied.location.disallowed"}) private static @GuardSatisfied(1) Object maskNull(@GuardSatisfied(1) Object key) { return (key == null ? (@GuardSatisfied(1) Object) NULL_KEY : key); } public void assignmentOfGSWithNoIndex(@GuardSatisfied Object a, @GuardSatisfied Object b) { //:: error: (guardsatisfied.assignment.disallowed) a = b; } class Session { @Holding("this") public void kill(@GuardSatisfied Session this) {} } class SessionManager { private @GuardedBy("<self>") Session session = new Session(); private void session_done() { final @GuardedBy("<self>") Session tmp = session; session = null; synchronized (tmp) { tmp.kill(); } } } }