| import java.util.Random; |
| import java.util.concurrent.locks.ReentrantLock; |
| import org.checkerframework.checker.lock.qual.*; |
| import org.checkerframework.dataflow.qual.SideEffectFree; |
| |
| public class TestTreeKinds { |
| class MyClass { |
| Object field = new Object(); |
| |
| @LockingFree |
| Object method(@GuardSatisfied MyClass this) { |
| return new Object(); |
| } |
| |
| void method2(@GuardSatisfied MyClass this) {} |
| } |
| |
| @GuardedBy("lock") MyClass m; |
| |
| { |
| // In constructor/initializer, it's OK not to hold the lock on 'this', but other locks must |
| // be respected. |
| // :: error: (lock.not.held) |
| m.field = new Object(); |
| } |
| |
| final ReentrantLock lock = new ReentrantLock(); |
| final ReentrantLock lock2 = new ReentrantLock(); |
| |
| @GuardedBy("lock") MyClass foo = new MyClass(); |
| |
| MyClass unguardedFoo = new MyClass(); |
| |
| @EnsuresLockHeld("lock") |
| void lockTheLock() { |
| lock.lock(); |
| } |
| |
| @EnsuresLockHeld("lock2") |
| void lockTheLock2() { |
| lock2.lock(); |
| } |
| |
| @EnsuresLockHeldIf(expression = "lock", result = true) |
| boolean tryToLockTheLock() { |
| return lock.tryLock(); |
| } |
| |
| // This @MayReleaseLocks annotation causes dataflow analysis to assume 'lock' is released after |
| // unlockTheLock() is called. |
| @MayReleaseLocks |
| void unlockTheLock() {} |
| |
| @SideEffectFree |
| void sideEffectFreeMethod() {} |
| |
| @LockingFree |
| void lockingFreeMethod() {} |
| |
| @MayReleaseLocks |
| void nonSideEffectFreeMethod() {} |
| |
| @Holding("lock") |
| void requiresLockHeldMethod() {} |
| |
| MyClass fooArray @GuardedBy("lock") [] = new MyClass[3]; |
| |
| @GuardedBy("lock") MyClass fooArray2[] = new MyClass[3]; |
| |
| @GuardedBy("lock") MyClass fooArray3[][] = new MyClass[3][3]; |
| |
| MyClass fooArray4 @GuardedBy("lock") [][] = new MyClass[3][3]; |
| |
| MyClass fooArray5[] @GuardedBy("lock") [] = new MyClass[3][3]; |
| |
| class myClass { |
| int i = 0; |
| } |
| |
| @GuardedBy("lock") myClass myClassInstance = new myClass(); |
| |
| @GuardedBy("lock") Exception exception = new Exception(); |
| |
| class MyParametrizedType<T> { |
| T foo; |
| int l; |
| } |
| |
| @GuardedBy("lock") MyParametrizedType<MyClass> myParametrizedType = new MyParametrizedType<>(); |
| |
| MyClass getFooWithWrongReturnType() { |
| // :: error: (return) |
| return foo; // return of guarded object |
| } |
| |
| @GuardedBy("lock") MyClass getFoo() { |
| return foo; |
| } |
| |
| MyClass @GuardedBy("lock") [] getFooArray() { |
| return fooArray; |
| } |
| |
| @GuardedBy("lock") MyClass[] getFooArray2() { |
| return fooArray2; |
| } |
| |
| @GuardedBy("lock") MyClass[][] getFooArray3() { |
| return fooArray3; |
| } |
| |
| MyClass @GuardedBy("lock") [][] getFooArray4() { |
| return fooArray4; |
| } |
| |
| MyClass[] @GuardedBy("lock") [] getFooArray5() { |
| return fooArray5; |
| } |
| |
| enum myEnumType { |
| ABC, |
| DEF |
| } |
| |
| @GuardedBy("lock") myEnumType myEnum; |
| |
| void testEnumType() { |
| // TODO: assignment is technically correct, but we could |
| // make it friendlier for the user if constant enum values on the RHS |
| // automatically cast to the @GuardedBy annotation of the LHS. |
| // :: error: (assignment) |
| myEnum = myEnumType.ABC; |
| } |
| |
| final Object intrinsicLock = new Object(); |
| |
| void testThreadHoldsLock(@GuardedBy("intrinsicLock") MyClass m) { |
| if (Thread.holdsLock(intrinsicLock)) { |
| m.field.toString(); |
| } else { |
| // :: error: (lock.not.held) |
| m.field.toString(); |
| } |
| } |
| |
| void testTreeTypes() { |
| int i, l; |
| |
| MyClass o = new MyClass(); |
| MyClass f = new MyClass(); |
| |
| // The following test cases were inspired from annotator.find.ASTPathCriterion.isSatisfiedBy |
| // in the Annotation File Utilities |
| |
| // TODO: File a bug for the dataflow issue mentioned in the line below. |
| // TODO: uncomment: Hits a bug in dataflow: |
| // do { |
| // break; |
| // } while (foo.field != null); // access to guarded object in condition of do/while loop |
| // :: error: (lock.not.held) |
| for (foo = new MyClass(); foo.field != null; foo = new MyClass()) { |
| break; |
| } // access to guarded object in condition of for loop |
| // assignment to guarded object (OK) --- foo is still refined to @GuardedBy("lock") after |
| // this point, though. |
| foo = new MyClass(); |
| // A simple method call to a guarded object is not considered a dereference (only field |
| // accesses are considered dereferences). |
| unguardedFoo.method2(); |
| // Same as above, but the guard must be satisfied if the receiver is @GuardSatisfied. |
| // :: error: (lock.not.held) |
| foo.method2(); |
| // attempt to use guarded object in a switch statement |
| // :: error: (lock.not.held) |
| switch (foo.field.hashCode()) { |
| } |
| // attempt to use guarded object inside a try with resources |
| // try(foo = new MyClass()) { foo.field.toString(); } |
| |
| // Retrieving an element from a guarded array is a dereference |
| // :: error: (lock.not.held) |
| MyClass m = fooArray[0]; |
| |
| // method call on dereference of unguarded element of *guarded* array |
| // :: error: (lock.not.held) |
| fooArray[0].field.toString(); |
| // :: error: (lock.not.held) |
| l = fooArray.length; // dereference of guarded array itself |
| |
| // method call on dereference of guarded array element |
| // :: error: (lock.not.held) |
| fooArray2[0].field.toString(); |
| // method call on dereference of unguarded array - TODO: currently preconditions are not |
| // retrieved correctly from array types. This is not unique to the Lock Checker. |
| // fooArray2.field.toString(); |
| |
| // method call on dereference of guarded array element of multidimensional array |
| // :: error: (lock.not.held) |
| fooArray3[0][0].field.toString(); |
| // method call on dereference of unguarded single-dimensional array element of unguarded |
| // multidimensional array - TODO: currently preconditions are not retrieved correctly from |
| // array types. This is not unique to the Lock Checker. |
| // fooArray3[0].field.toString(); |
| // method call on dereference of unguarded multidimensional array - TODO: currently |
| // preconditions are not retrieved correctly from array types. This is not unique to the |
| // Lock Checker. |
| // fooArray3.field.toString(); |
| |
| // method call on dereference of unguarded array element of *guarded* multidimensional array |
| // :: error: (lock.not.held) |
| fooArray4[0][0].field.toString(); |
| // dereference of unguarded single-dimensional array element of *guarded* multidimensional |
| // array |
| // :: error: (lock.not.held) |
| l = fooArray4[0].length; |
| // dereference of guarded multidimensional array |
| // :: error: (lock.not.held) |
| l = fooArray4.length; |
| |
| // method call on dereference of unguarded array element of *guarded subarray* of |
| // multidimensional array |
| // :: error: (lock.not.held) |
| fooArray5[0][0].field.toString(); |
| // dereference of guarded single-dimensional array element of multidimensional array |
| // :: error: (lock.not.held) |
| l = fooArray5[0].length; |
| // dereference of unguarded multidimensional array |
| l = fooArray5.length; |
| |
| // :: error: (lock.not.held) |
| l = getFooArray().length; // dereference of guarded array returned by a method |
| |
| // method call on dereference of guarded array element returned by a method |
| // :: error: (lock.not.held) |
| getFooArray2()[0].field.toString(); |
| // dereference of unguarded array returned by a method |
| l = getFooArray2().length; |
| |
| // method call on dereference of guarded array element of multidimensional array returned by |
| // a method |
| // :: error: (lock.not.held) |
| getFooArray3()[0][0].field.toString(); |
| // dereference of unguarded single-dimensional array element of multidimensional array |
| // returned by a method |
| l = getFooArray3()[0].length; |
| // dereference of unguarded multidimensional array returned by a method |
| l = getFooArray3().length; |
| |
| // method call on dereference of unguarded array element of *guarded* multidimensional array |
| // returned by a method |
| // :: error: (lock.not.held) |
| getFooArray4()[0][0].field.toString(); |
| // dereference of unguarded single-dimensional array element of *guarded* multidimensional |
| // array returned by a method |
| // :: error: (lock.not.held) |
| l = getFooArray4()[0].length; |
| // dereference of guarded multidimensional array returned by a method |
| // :: error: (lock.not.held) |
| l = getFooArray4().length; |
| |
| // method call on dereference of unguarded array element of *guarded subarray* of |
| // multidimensional array returned by a method |
| // :: error: (lock.not.held) |
| getFooArray5()[0][0].field.toString(); |
| // dereference of guarded single-dimensional array element of multidimensional array |
| // returned by a method |
| // :: error: (lock.not.held) |
| l = getFooArray5()[0].length; |
| // dereference of unguarded multidimensional array returned by a method |
| l = getFooArray5().length; |
| |
| // Test different @GuardedBy(...) present on the element and array locations. |
| @GuardedBy("lock") MyClass @GuardedBy("lock2") [] array = new MyClass[3]; |
| // :: error: (lock.not.held) |
| array[0].field = new Object(); |
| if (lock.isHeldByCurrentThread()) { |
| // :: error: (lock.not.held) |
| array[0].field = new Object(); |
| if (lock2.isHeldByCurrentThread()) { |
| array[0].field = new Object(); |
| } |
| } |
| |
| // method call on guarded object within parenthesized expression |
| // :: error: (lock.not.held) |
| String s = (foo.field.toString()); |
| // :: error: (lock.not.held) |
| foo.field.toString(); // method call on guarded object |
| // :: error: (lock.not.held) |
| getFoo().field.toString(); // method call on guarded object returned by a method |
| // :: error: (lock.not.held) |
| this.foo.field.toString(); // method call on guarded object using 'this' literal |
| // dereference of guarded object in labeled statement |
| label: |
| // :: error: (lock.not.held) |
| foo.field.toString(); |
| // access to guarded object in instanceof expression (OK) |
| if (foo instanceof MyClass) {} |
| // access to guarded object in while condition of while loop (OK) |
| while (foo != null) { |
| break; |
| } |
| // binary operator on guarded object in else if condition (OK) |
| if (false) { |
| } else if (foo == o) { |
| } |
| // access to guarded object in a lambda expression |
| Runnable rn = |
| () -> { |
| // :: error: (lock.not.held) |
| foo.field.toString(); |
| }; |
| // :: error: (lock.not.held) |
| i = myClassInstance.i; // access to member field of guarded object |
| // MemberReferenceTrees? how do they work |
| fooArray = new MyClass[3]; // second allocation of guarded array (OK) |
| // dereference of guarded object in conditional expression tree |
| // :: error: (lock.not.held) |
| s = i == 5 ? foo.field.toString() : f.field.toString(); |
| // dereference of guarded object in conditional expression tree |
| // :: error: (lock.not.held) |
| s = i == 5 ? f.field.toString() : foo.field.toString(); |
| // Testing of 'return' is done in getFooWithWrongReturnType() |
| // throwing a guarded object - when throwing an exception, it must be @GuardedBy({}). Even |
| // @GuardedByUnknown is not allowed. |
| try { |
| // :: error: (throw) |
| throw exception; |
| } catch (Exception e) { |
| } |
| // casting of a guarded object to an unguarded object |
| // :: error: (assignment) |
| @GuardedBy({}) Object e1 = (Object) exception; |
| // OK, since the local variable's type gets refined to @GuardedBy("lock") |
| Object e2 = (Object) exception; |
| // :: error: (lock.not.held) |
| l = myParametrizedType.l; // dereference of guarded object having a parameterized type |
| |
| // We need to support locking on local variables and protecting local variables because |
| // these locals may contain references to fields. Somehow we need to pass along the |
| // information of which field it was. |
| |
| if (foo == o) { // binary operator on guarded object (OK) |
| o.field.toString(); |
| } |
| |
| if (foo == null) { |
| // With -AconcurrentSemantics turned off, a cannot.dereference error would be expected, |
| // since there is an attempt to dereference an expression whose type has been refined to |
| // @GuardedByBottom (due to the comparison to null). However, with -AconcurrentSemantics |
| // turned on, foo may no longer be null by now, the refinement to @GuardedByBottom is |
| // lost and the refined type of foo is now the declared type ( @GuardedBy("lock") ), |
| // resulting in the lock.not.held error. |
| // :: error: (lock.not.held) |
| foo.field.toString(); |
| } |
| |
| // TODO: Reenable: |
| // @PolyGuardedBy should not be written here, but it is not explicitly forbidden by the |
| // framework. |
| // @PolyGuardedBy MyClass m2 = new MyClass(); |
| // (cannot.dereference) |
| // m2.field.toString(); |
| } |
| |
| @MayReleaseLocks |
| public void testLocals() { |
| final ReentrantLock localLock = new ReentrantLock(); |
| |
| @GuardedBy("localLock") MyClass guardedByLocalLock = new MyClass(); |
| |
| // :: error: (lock.not.held) |
| guardedByLocalLock.field.toString(); |
| |
| @GuardedBy("lock") MyClass local = new MyClass(); |
| |
| // :: error: (lock.not.held) |
| local.field.toString(); |
| |
| lockTheLock(); |
| |
| local.field.toString(); // No warning output |
| |
| unlockTheLock(); |
| } |
| |
| @MayReleaseLocks |
| public void testMethodAnnotations() { |
| Random r = new Random(); |
| |
| if (r.nextBoolean()) { |
| lockTheLock(); |
| requiresLockHeldMethod(); |
| } else { |
| // :: error: (contracts.precondition) |
| requiresLockHeldMethod(); |
| } |
| |
| if (r.nextBoolean()) { |
| lockTheLock(); |
| foo.field.toString(); |
| |
| unlockTheLock(); |
| |
| // :: error: (lock.not.held) |
| foo.field.toString(); |
| } else { |
| // :: error: (lock.not.held) |
| foo.field.toString(); |
| } |
| |
| if (tryToLockTheLock()) { |
| foo.field.toString(); |
| } else { |
| // :: error: (lock.not.held) |
| foo.field.toString(); |
| } |
| |
| if (r.nextBoolean()) { |
| lockTheLock(); |
| sideEffectFreeMethod(); |
| foo.field.toString(); |
| } else { |
| lockTheLock(); |
| nonSideEffectFreeMethod(); |
| // :: error: (lock.not.held) |
| foo.field.toString(); |
| } |
| |
| if (r.nextBoolean()) { |
| lockTheLock(); |
| lockingFreeMethod(); |
| foo.field.toString(); |
| } else { |
| lockTheLock(); |
| nonSideEffectFreeMethod(); |
| // :: error: (lock.not.held) |
| foo.field.toString(); |
| } |
| } |
| |
| void methodThatTakesAnInteger(Integer i) {} |
| |
| void testBoxedPrimitiveType() { |
| Integer i = null; |
| if (i == null) {} |
| |
| methodThatTakesAnInteger(i); |
| } |
| |
| void testReceiverGuardedByItself(@GuardedBy("<self>") TestTreeKinds this) { |
| // :: error: (lock.not.held) |
| method(); |
| synchronized (this) { |
| method(); |
| } |
| } |
| |
| void method(@GuardSatisfied TestTreeKinds this) {} |
| |
| void testOtherClassReceiverGuardedByItself(final @GuardedBy("<self>") OtherClass o) { |
| // :: error: (lock.not.held) |
| o.foo(); |
| synchronized (o) { |
| o.foo(); |
| } |
| } |
| |
| class OtherClass { |
| void foo(@GuardSatisfied OtherClass this) {} |
| } |
| |
| void testExplicitLockSynchronized() { |
| final ReentrantLock lock = new ReentrantLock(); |
| // :: error: (explicit.lock.synchronized) |
| synchronized (lock) { |
| } |
| } |
| |
| void testPrimitiveTypeGuardedby() { |
| // :: error: (immutable.type.guardedby) |
| @GuardedBy("lock") int a = 0; |
| // :: error: (immutable.type.guardedby) |
| @GuardedBy int b = 0; |
| // :: error: (immutable.type.guardedby) :: error: (guardsatisfied.location.disallowed) |
| @GuardSatisfied int c = 0; |
| // :: error: (immutable.type.guardedby) :: error: (guardsatisfied.location.disallowed) |
| @GuardSatisfied(1) int d = 0; |
| int e = 0; |
| // :: error: (immutable.type.guardedby) |
| @GuardedByUnknown int f = 0; |
| // :: error: (immutable.type.guardedby) :: error: (assignment) |
| @GuardedByBottom int g = 0; |
| } |
| |
| void testBinaryOperatorBooleanResultIsAlwaysGuardedByNothing() { |
| @GuardedBy("lock") Object o1 = new Object(); |
| Object o2 = new Object(); |
| // boolean variables are implicitly @GuardedBy({}). |
| boolean b1 = o1 == o2; |
| boolean b2 = o2 == o1; |
| boolean b3 = o1 != o2; |
| boolean b4 = o2 != o1; |
| boolean b5 = o1 instanceof Object; |
| boolean b6 = o2 instanceof Object; |
| boolean b7 = o1 instanceof @GuardedBy("lock") Object; |
| boolean b8 = o2 instanceof @GuardedBy("lock") Object; |
| } |
| } |