package chapter; import java.util.concurrent.locks.ReentrantLock; import org.checkerframework.checker.lock.qual.GuardedBy; import org.checkerframework.checker.lock.qual.GuardedByUnknown; import org.checkerframework.checker.lock.qual.MayReleaseLocks; import org.checkerframework.dataflow.qual.Deterministic; import org.checkerframework.dataflow.qual.Pure; public class LockExpressionIsFinal { class MyClass { Object field = new Object(); } class C1 { final C1 field = new C1(); // Infinite loop. This code is not meant to be executed, only type checked. C1 field2; @Deterministic C1 getFieldDeterministic() { return field; } @Pure C1 getFieldPure(Object param1, Object param2) { return field; } @Pure C1 getFieldPure2() { return field; } C1 getField() { return field; } } final C1 c1 = new C1(); // Analogous to testExplicitLockExpressionIsFinal and testGuardedByExpressionIsFinal, but for monitor locks acquired in synchronized blocks. void testSynchronizedExpressionIsFinal(boolean b) { synchronized (c1) { } Object o1 = new Object(); // o1 is effectively final - it is never reassigned Object o2 = new Object(); // o2 is reassigned later - it is not effectively final synchronized (o1) { } //:: error: (lock.expression.not.final) synchronized (o2) { } o2 = new Object(); // Reassignment that makes o2 not have been effectively final earlier. // Tests that package names are considered final. synchronized (java.lang.String.class) { } // Test a tree that is not supported by LockVisitor.ensureExpressionIsEffectivelyFinal //:: error: (lock.expression.possibly.not.final) synchronized (c1.getFieldPure(b ? c1 : o1, c1)) { } synchronized ( c1.field.field.field.getFieldPure( c1.field, c1.getFieldDeterministic().getFieldPure(c1, c1.field)) .field) { } // The following negative test cases are the same as the one above but with one modification in each. synchronized ( //:: error: (lock.expression.not.final) c1.field.field2.field.getFieldPure( c1.field, c1.getFieldDeterministic().getFieldPure(c1, c1.field)) .field) { } synchronized ( c1.field.field.field.getFieldPure( //:: error: (lock.expression.not.final) c1.field, c1.getField().getFieldPure(c1, c1.field)) .field) { } } class C2 extends ReentrantLock { final C2 field = new C2(); // Infinite loop. This code is not meant to be executed, only type checked. C2 field2; @Deterministic C2 getFieldDeterministic() { return field; } @Pure C2 getFieldPure(Object param1, Object param2) { return field; } C2 getField() { return field; } } final C2 c2 = new C2(); // Analogous to testSynchronizedExpressionIsFinal and testGuardedByExpressionIsFinal, but for explicit locks. @MayReleaseLocks void testExplicitLockExpressionIsFinal(boolean b) { c2.lock(); ReentrantLock rl1 = new ReentrantLock(); // rl1 is effectively final - it is never reassigned ReentrantLock rl2 = new ReentrantLock(); // rl2 is reassigned later - it is not effectively final rl1.lock(); rl1.unlock(); //:: error: (lock.expression.not.final) rl2.lock(); //:: error: (lock.expression.not.final) rl2.unlock(); rl2 = new ReentrantLock(); // Reassignment that makes rl2 not have been effectively final earlier. // Test a tree that is not supported by LockVisitor.ensureExpressionIsEffectivelyFinal //:: error: (lock.expression.possibly.not.final) c2.getFieldPure(b ? c2 : rl1, c2).lock(); //:: error: (lock.expression.possibly.not.final) c2.getFieldPure(b ? c2 : rl1, c2).unlock(); c2.field .field .field .getFieldPure(c2.field, c2.getFieldDeterministic().getFieldPure(c2, c2.field)) .field .lock(); c2.field .field .field .getFieldPure(c2.field, c2.getFieldDeterministic().getFieldPure(c2, c2.field)) .field .unlock(); // The following negative test cases are the same as the one above but with one modification in each. c2.field //:: error: (lock.expression.not.final) .field2 .field .getFieldPure(c2.field, c2.getFieldDeterministic().getFieldPure(c2, c2.field)) .field .lock(); c2.field //:: error: (lock.expression.not.final) .field2 .field .getFieldPure(c2.field, c2.getFieldDeterministic().getFieldPure(c2, c2.field)) .field .unlock(); c2.field .field .field //:: error: (lock.expression.not.final) .getFieldPure(c2.field, c2.getField().getFieldPure(c2, c2.field)) .field .lock(); c2.field .field .field //:: error: (lock.expression.not.final) .getFieldPure(c2.field, c2.getField().getFieldPure(c2, c2.field)) .field .unlock(); } // Analogous to testSynchronizedExpressionIsFinal and testExplicitLockExpressionIsFinal, but for expressions in @GuardedBy annotations. void testGuardedByExpressionIsFinal() { @GuardedBy("c1") Object guarded1; final Object o1 = new Object(); Object o2 = new Object(); // reassign so it's not effectively final o2 = new Object(); @GuardedBy("o1") Object guarded2 = new Object(); //:: error: (lock.expression.not.final) @GuardedBy("o2") Object guarded3 = new Object(); // Test expressions that are not supported by LockVisitor.ensureExpressionIsEffectivelyFinal @GuardedBy("java.lang.String.class") Object guarded4; //:: error: (expression.unparsable.type.invalid) @GuardedBy("c1.getFieldPure(b ? c1 : o1, c1)") Object guarded5; // TODO: Fix the flow expression parser so it can handle // @GuardedBy("c1.field.field.field.getFieldPure(c1.field, c1.getFieldDeterministic().getFieldPure(c1, c1.field)).field") Object guarded6; // Currently it fails because the memberselect incorrectly splits the "getFieldPure(...).field" field access into: // "getFieldPure(c1" // and // "field, c1.getFieldDeterministic().getFieldPure(c1, c1.field)).field" // However, as soon as one fixes the flow expression parser to parse a longer expression, one must consider // whether the CFAbstractStore can (or should) store data for the resulting flow expression. @GuardedBy("c1.field.field.field.getFieldPure2().getFieldDeterministic().field") Object guarded6; // The following negative test cases are the same as the one above but with one modification in each. //:: error: (lock.expression.not.final) @GuardedBy("c1.field.field2.field.getFieldPure2().getFieldDeterministic().field") Object guarded7; //:: error: (lock.expression.not.final) @GuardedBy("c1.field.field.field.getField().getFieldDeterministic().field") Object guarded8; // Additional test cases to test that method parameters (in this case the parameters to getFieldPure) are parsed. @GuardedBy("c1.field.field.field.getFieldPure(c1, c1).getFieldDeterministic().field") Object guarded9; @GuardedBy("c1.field.field.field.getFieldPure(c1, o1).getFieldDeterministic().field") Object guarded10; //:: error: (lock.expression.not.final) @GuardedBy("c1.field.field.field.getFieldPure(c1, o2).getFieldDeterministic().field") Object guarded11; // Test that @GuardedBy annotations on various tree kinds inside a method are visited Object guarded12 = (@GuardedBy("o1") Object) guarded2; //:: error: (lock.expression.not.final) Object guarded13 = (@GuardedBy("o2") Object) guarded3; Object guarded14[] = new @GuardedBy("o1") MyClass[3]; //:: error: (lock.expression.not.final) Object guarded15[] = new @GuardedBy("o2") MyClass[3]; // Tests that the location of the @GB annotation inside a VariableTree does not matter (i.e. it does not need to be the leftmost subtree). Object guarded16 @GuardedBy("o1") []; //:: error: (lock.expression.not.final) Object guarded17 @GuardedBy("o2") []; @GuardedBy("o1") Object guarded18[]; //:: error: (lock.expression.not.final) @GuardedBy("o2") Object guarded19[]; MyParameterizedClass1<@GuardedBy("o1") Object> m1; //:: error: (lock.expression.not.final) MyParameterizedClass1<@GuardedBy("o2") Object> m2; boolean b = c1 instanceof @GuardedBy("o1") Object; // instanceof expression have not effect on the type. // //:: error: (lock.expression.not.final) b = c1 instanceof @GuardedBy("o2") Object; // Additional tests just outside of this method below: } // Test that @GuardedBy annotations on various tree kinds outside a method are visited // Test that @GuardedBy annotations on method return types are visited. No need to test method receivers and parameters // as they are covered by tests above that visit VariableTree. final Object finalField = new Object(); Object nonFinalField = new Object(); @GuardedBy("finalField") Object testGuardedByExprIsFinal1() { return null; } //:: error: (lock.expression.not.final) @GuardedBy("nonFinalField") Object testGuardedByExprIsFinal2() { return null; } <T extends @GuardedBy("finalField") Object> T myMethodThatReturnsT_1(T t) { return t; } //:: error: (lock.expression.not.final) <T extends @GuardedBy("nonFinalField") Object> T myMethodThatReturnsT_2(T t) { return t; } class MyParameterizedClass1<T extends @GuardedByUnknown Object> {}; MyParameterizedClass1<? super @GuardedBy("finalField") Object> m1; //:: error: (lock.expression.not.final) MyParameterizedClass1<? super @GuardedBy("nonFinalField") Object> m2; MyParameterizedClass1<? extends @GuardedBy("finalField") Object> m3; //:: error: (lock.expression.not.final) MyParameterizedClass1<? extends @GuardedBy("nonFinalField") Object> m4; class MyClassContainingALock { final ReentrantLock finalLock = new ReentrantLock(); ReentrantLock nonFinalLock = new ReentrantLock(); Object field; } void testItselfFinalLock() { final @GuardedBy("<self>.finalLock") MyClassContainingALock m = new MyClassContainingALock(); //:: error: (lock.not.held) m.field = new Object(); // Ignore this error: it is expected that an error will be issued for dereferencing 'm' in order to take the 'm.finalLock' lock. // Typically, the Lock Checker does not support an object being guarded by one of its fields, but this is sometimes done in user code // with a ReentrantLock field guarding its containing object. This unfortunately makes it a bit difficult for users since they have // to add a @SuppressWarnings for this call while still making sure that warnings for other dereferences are not suppressed. //:: error: (lock.not.held) m.finalLock.lock(); m.field = new Object(); } void testItselfNonFinalLock() { final @GuardedBy("<self>.nonFinalLock") MyClassContainingALock m = new MyClassContainingALock(); //::error: (lock.not.held) :: error: (lock.expression.not.final) m.field = new Object(); //::error: (lock.not.held) :: error: (lock.expression.not.final) m.nonFinalLock.lock(); //:: error: (lock.expression.not.final) m.field = new Object(); } }