/*
* Copyright 2014 Google Inc. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.errorprone.bugpatterns.threadsafety;
import com.google.errorprone.CompilationTestHelper;
import org.junit.Before;
import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
/** {@link GuardedByChecker}Test */
@RunWith(JUnit4.class)
public class GuardedByCheckerTest {
private CompilationTestHelper compilationHelper;
@Before
public void setUp() {
compilationHelper = CompilationTestHelper.newInstance(GuardedByChecker.class, getClass());
}
@Test
public void testLocked() throws Exception {
compilationHelper
.addSourceLines(
"threadsafety/Test.java",
"package threadsafety;",
"import javax.annotation.concurrent.GuardedBy;",
"import java.util.concurrent.locks.Lock;",
"class Test {",
" final Lock lock = null;",
" @GuardedBy(\"lock\")",
" int x;",
" void m() {",
" lock.lock();",
" // BUG: Diagnostic contains:",
" // access should be guarded by 'this.lock'",
" x++;",
" try {",
" x++;",
" } catch (Exception e) {",
" x--;",
" } finally {",
" lock.unlock();",
" }",
" // BUG: Diagnostic contains:",
" // access should be guarded by 'this.lock'",
" x++;",
" }",
"}")
.doTest();
}
/** "static synchronized method() { ... }" == "synchronized (MyClass.class) { ... }" */
@Test
public void testStaticLocked() throws Exception {
compilationHelper
.addSourceLines(
"threadsafety/Test.java",
"package threadsafety;",
"import javax.annotation.concurrent.GuardedBy;",
"import java.util.concurrent.locks.Lock;",
"class Test {",
" @GuardedBy(\"Test.class\")",
" static int x;",
" static synchronized void m() {",
" x++;",
" }",
"}")
.doTest();
}
@Test
public void testMonitor() throws Exception {
compilationHelper
.addSourceLines(
"threadsafety/Test.java",
"package threadsafety;",
"import javax.annotation.concurrent.GuardedBy;",
"import com.google.common.util.concurrent.Monitor;",
"class Test {",
" final Monitor monitor = null;",
" @GuardedBy(\"monitor\")",
" int x;",
" void m() {",
" monitor.enter();",
" // BUG: Diagnostic contains:",
" // access should be guarded by 'this.monitor'",
" x++;",
" try {",
" x++;",
" } finally {",
" monitor.leave();",
" }",
" // BUG: Diagnostic contains:",
" // access should be guarded by 'this.monitor'",
" x++;",
" }",
"}")
.doTest();
}
@Test
public void testWrongLock() throws Exception {
compilationHelper
.addSourceLines(
"threadsafety/Test.java",
"package threadsafety;",
"import javax.annotation.concurrent.GuardedBy;",
"import java.util.concurrent.locks.Lock;",
"class Test {",
" final Lock lock1 = null;",
" final Lock lock2 = null;",
" @GuardedBy(\"lock1\")",
" int x;",
" void m() {",
" lock2.lock();",
" try {",
" // BUG: Diagnostic contains:",
" // access should be guarded by 'this.lock1'",
" x++;",
" } finally {",
" lock2.unlock();",
" }",
" }",
"}")
.doTest();
}
@Test
public void testGuardedStaticFieldAccess_1() throws Exception {
compilationHelper
.addSourceLines(
"threadsafety/Test.java",
"package threadsafety;",
"import javax.annotation.concurrent.GuardedBy;",
"class Test {",
" public static final Object lock = new Object();",
" @GuardedBy(\"lock\")",
" public static int x;",
" void m() {",
" synchronized (Test.lock) {",
" Test.x++;",
" }",
" }",
"}")
.doTest();
}
@Test
public void testGuardedStaticFieldAccess_2() throws Exception {
compilationHelper
.addSourceLines(
"threadsafety/Test.java",
"package threadsafety;",
"import javax.annotation.concurrent.GuardedBy;",
"class Test {",
" public static final Object lock = new Object();",
" @GuardedBy(\"lock\")",
" public static int x;",
" void m() {",
" synchronized (lock) {",
" Test.x++;",
" }",
" }",
"}")
.doTest();
}
@Test
public void testGuardedStaticFieldAccess_3() throws Exception {
compilationHelper
.addSourceLines(
"threadsafety/Test.java",
"package threadsafety;",
"import javax.annotation.concurrent.GuardedBy;",
"class Test {",
" public static final Object lock = new Object();",
" @GuardedBy(\"lock\")",
" public static int x;",
" void m() {",
" synchronized (Test.lock) {",
" x++;",
" }",
" }",
"}")
.doTest();
}
@Test
public void testGuardedStaticFieldAccess_EnclosingClass() throws Exception {
compilationHelper
.addSourceLines(
"threadsafety/Test.java",
"package threadsafety;",
"import javax.annotation.concurrent.GuardedBy;",
"class Test {",
" @GuardedBy(\"Test.class\")",
" public static int x;",
" synchronized static void n() {",
" Test.x++;",
" }",
"}")
.doTest();
}
@Test
public void testBadStaticFieldAccess() throws Exception {
compilationHelper
.addSourceLines(
"threadsafety/Test.java",
"package threadsafety;",
"import javax.annotation.concurrent.GuardedBy;",
"class Test {",
" public static final Object lock = new Object();",
" @GuardedBy(\"lock\")",
" public static int x;",
" void m() {",
" // BUG: Diagnostic contains:",
" // access should be guarded by 'Test.lock'",
" Test.x++;",
" }",
"}")
.doTest();
}
@Test
public void testBadGuard() throws Exception {
compilationHelper
.addSourceLines(
"threadsafety/Test.java",
"package threadsafety;",
"import javax.annotation.concurrent.GuardedBy;",
"class Test {",
" // BUG: Diagnostic contains: Invalid @GuardedBy expression",
" @GuardedBy(\"foo\") int y;",
"}")
.doTest();
}
@Test
public void testUnheldInstanceGuard() throws Exception {
compilationHelper
.addSourceLines(
"threadsafety/Test.java",
"package threadsafety;",
"import javax.annotation.concurrent.GuardedBy;",
"class Test {",
" final Object mu = new Object();",
" @GuardedBy(\"mu\") int y;",
"}",
"class Main {",
" void m(Test t) {",
" // BUG: Diagnostic contains:",
" // should be guarded by 't.mu'",
" t.y++;",
" }",
"}")
.doTest();
}
@Test
public void testUnheldItselfGuard() throws Exception {
compilationHelper
.addSourceLines(
"threadsafety/Test.java",
"package threadsafety;",
"import javax.annotation.concurrent.GuardedBy;",
"class Itself {",
" @GuardedBy(\"itself\")",
" int x;",
" void incrementX() {",
" // BUG: Diagnostic contains:",
" // should be guarded by 'this.x'",
" x++;",
" }",
"}")
.doTest();
}
@Test
public void i541() throws Exception {
compilationHelper
.addSourceLines(
"threadsafety/Test.java",
"package threadsafety;",
"import java.util.List;",
"import javax.annotation.concurrent.GuardedBy;",
"class Itself {",
" @GuardedBy(\"itself\")",
" List<String> xs;",
" void f() {",
" // BUG: Diagnostic contains:",
" // should be guarded by 'this.xs'",
" this.xs.add(\"\");",
" synchronized (this.xs) { this.xs.add(\"\"); }",
" synchronized (this.xs) { xs.add(\"\"); }",
" synchronized (xs) { this.xs.add(\"\"); }",
" synchronized (xs) { xs.add(\"\"); }",
" }",
"}")
.doTest();
}
@Test
public void testCtor() throws Exception {
compilationHelper
.addSourceLines(
"threadsafety/Test.java",
"package threadsafety;",
"import javax.annotation.concurrent.GuardedBy;",
"class Test {",
" @GuardedBy(\"this\") int x;",
" public Test() {",
" this.x = 42;",
" }",
"}")
.doTest();
}
@Test
public void testBadGuardMethodAccess() throws Exception {
compilationHelper
.addSourceLines(
"threadsafety/Test.java",
"package threadsafety;",
"import javax.annotation.concurrent.GuardedBy;",
"class Test {",
" @GuardedBy(\"this\") void x() {}",
" void m() {",
" // BUG: Diagnostic contains: this",
" x();",
" }",
"}")
.doTest();
}
@Test
public void testTransitiveGuardMethodAccess() throws Exception {
compilationHelper
.addSourceLines(
"threadsafety/Test.java",
"package threadsafety;",
"import javax.annotation.concurrent.GuardedBy;",
"class Test {",
" @GuardedBy(\"this\") void x() {}",
" @GuardedBy(\"this\") void m() {",
" x();",
" }",
"}")
.doTest();
}
@Ignore // TODO(cushon): support read/write lock copies
@Test
public void testReadWriteLockCopy() throws Exception {
compilationHelper
.addSourceLines(
"threadsafety.Test",
"package threadsafety.Test;",
"import javax.annotation.concurrent.GuardedBy;",
"import java.util.concurrent.locks.ReentrantReadWriteLock;",
"import java.util.concurrent.locks.Lock;",
"class Test {",
" final ReentrantReadWriteLock lock = new ReentrantReadWriteLock();",
" final Lock readLock = lock.readLock();",
" final Lock writeLock = lock.writeLock();",
" @GuardedBy(\"lock\") boolean b = false;",
" void m() {",
" readLock.lock();",
" try {",
" b = true;",
" } finally {",
" readLock.unlock();",
" }",
" }",
" void n() {",
" writeLock.lock();",
" try {",
" b = true;",
" } finally {",
" writeLock.unlock();",
" }",
" }",
"}")
.doTest();
}
@Test
public void testReadWriteLock() throws Exception {
compilationHelper
.addSourceLines(
"threadsafety/Test.java",
"package threadsafety;",
"import javax.annotation.concurrent.GuardedBy;",
"import java.util.concurrent.locks.ReentrantReadWriteLock;",
"class Test {",
" final ReentrantReadWriteLock lock = new ReentrantReadWriteLock();",
" @GuardedBy(\"lock\") boolean b = false;",
" void m() {",
" lock.readLock().lock();",
" try {",
" b = true;",
" } finally {",
" lock.readLock().unlock();",
" }",
" }",
" void n() {",
" lock.writeLock().lock();",
" try {",
" b = true;",
" } finally {",
" lock.writeLock().unlock();",
" }",
" }",
"}")
.doTest();
}
// Test that ReadWriteLocks are currently ignored.
@Test
public void testReadWriteLockIsIgnored() throws Exception {
compilationHelper
.addSourceLines(
"threadsafety/Test.java",
"package threadsafety.Test;",
"import javax.annotation.concurrent.GuardedBy;",
"import java.util.concurrent.locks.ReentrantReadWriteLock;",
"class Test {",
" final ReentrantReadWriteLock lock = new ReentrantReadWriteLock();",
" @GuardedBy(\"lock\") boolean b = false;",
" void m() {",
" try {",
" b = true;",
" } finally {",
" }",
" }",
"}")
.doTest();
}
@Test
public void testInnerClass_enclosingClassLock() throws Exception {
compilationHelper
.addSourceLines(
"threadsafety/Test.java",
"package threadsafety;",
"import javax.annotation.concurrent.GuardedBy;",
"public class Test {",
" final Object mu = new Object();",
" @GuardedBy(\"mu\") boolean b = false;",
" private final class Baz {",
" public void m() {",
" synchronized (mu) {",
" n();",
" }",
" }",
" @GuardedBy(\"Test.this.mu\")",
" private void n() {",
" b = true;",
" }",
" }",
"}")
.doTest();
}
// notice lexically enclosing owner, use NamedThis!
@Test
public void testInnerClass_thisLock() throws Exception {
compilationHelper
.addSourceLines(
"threadsafety/Test.java",
"package threadsafety;",
"import javax.annotation.concurrent.GuardedBy;",
"public class Test {",
" @GuardedBy(\"this\") boolean b = false;",
" private final class Baz {",
" private synchronized void n() {",
" // BUG: Diagnostic contains:",
" // should be guarded by 'Test.this'",
" b = true;",
" }",
" }",
"}")
.doTest();
}
@Test
public void testAnonymousClass() throws Exception {
compilationHelper
.addSourceLines(
"threadsafety/Test.java",
"package threadsafety;",
"import javax.annotation.concurrent.GuardedBy;",
"public class Test {",
" @GuardedBy(\"this\") boolean b = false;",
" private synchronized void n() {",
" b = true;",
" new Object() {",
" void m() {",
" // BUG: Diagnostic contains:",
" // should be guarded by 'Test.this'",
" b = true;",
" }",
" };",
" }",
"}")
.doTest();
}
@Test
public void testInheritedLock() throws Exception {
compilationHelper
.addSourceLines(
"threadsafety/Test.java",
"package threadsafety;",
"import javax.annotation.concurrent.GuardedBy;",
"class A {",
" final Object lock = new Object();",
"}",
"class B extends A {",
" @GuardedBy(\"lock\") boolean b = false;",
" void m() {",
" synchronized (lock) {",
" b = true;",
" };",
" }",
"}")
.doTest();
}
@Test
public void testEnclosingSuperAccess() throws Exception {
compilationHelper
.addSourceLines(
"threadsafety/Test.java",
"package threadsafety;",
"import javax.annotation.concurrent.GuardedBy;",
"class A {",
" final Object lock = new Object();",
" @GuardedBy(\"lock\") boolean flag = false;",
"}",
"class B extends A {",
" void m() {",
" new Object() {",
" @GuardedBy(\"lock\")",
" void n() {",
" flag = true;",
" }",
" };",
" }",
"}")
.doTest();
}
@Test
public void testSuperAccess_this() throws Exception {
compilationHelper
.addSourceLines(
"threadsafety/Test.java",
"package threadsafety;",
"import javax.annotation.concurrent.GuardedBy;",
"class A {",
" final Object lock = new Object();",
" @GuardedBy(\"this\") boolean flag = false;",
"}",
"class B extends A {",
" synchronized void m() {",
" flag = true;",
" }",
"}")
.doTest();
}
@Test
public void testSuperAccess_lock() throws Exception {
compilationHelper
.addSourceLines(
"threadsafety/Test.java",
"package threadsafety;",
"import javax.annotation.concurrent.GuardedBy;",
"class A {",
" final Object lock = new Object();",
" @GuardedBy(\"lock\") boolean flag = false;",
"}",
"class B extends A {",
" void m() {",
" synchronized (lock) {",
" flag = true;",
" }",
" synchronized (this.lock) {",
" flag = true;",
" }",
" }",
"}")
.doTest();
}
@Test
public void testSuperAccess_staticLock() throws Exception {
compilationHelper
.addSourceLines(
"threadsafety/Test.java",
"package threadsafety;",
"import javax.annotation.concurrent.GuardedBy;",
"class A {",
" static final Object lock = new Object();",
" @GuardedBy(\"lock\") static boolean flag = false;",
"}",
"class B extends A {",
" void m() {",
" synchronized (A.lock) {",
" flag = true;",
" }",
" synchronized (B.lock) {",
" flag = true;",
" }",
" }",
"}")
.doTest();
}
@Test
public void testOtherClass_bad_staticLock() throws Exception {
compilationHelper
.addSourceLines(
"threadsafety/Test.java",
"package threadsafety;",
"import javax.annotation.concurrent.GuardedBy;",
"class A {",
" static final Object lock = new Object();",
" @GuardedBy(\"lock\") static boolean flag = false;",
"}",
"class B {",
" static final Object lock = new Object();",
" @GuardedBy(\"lock\") static boolean flag = false;",
" void m() {",
" synchronized (B.lock) {",
" // BUG: Diagnostic contains:",
" // should be guarded by 'A.lock'",
" A.flag = true;",
" }",
" synchronized (A.lock) {",
" // BUG: Diagnostic contains:",
" // should be guarded by 'B.lock'",
" B.flag = true;",
" }",
" }",
"}")
.doTest();
}
@Test
public void testOtherClass_bad_staticLock_alsoSub() throws Exception {
compilationHelper
.addSourceLines(
"threadsafety/Test.java",
"package threadsafety;",
"import javax.annotation.concurrent.GuardedBy;",
"class A {",
" static final Object lock = new Object();",
" @GuardedBy(\"lock\") static boolean flag = false;",
"}",
"class B extends A {",
" static final Object lock = new Object();",
" @GuardedBy(\"lock\") static boolean flag = false;",
" void m() {",
" synchronized (B.lock) {",
" // BUG: Diagnostic contains:",
" // should be guarded by 'A.lock'",
" A.flag = true;",
" }",
" synchronized (A.lock) {",
" // BUG: Diagnostic contains:",
" // should be guarded by 'B.lock'",
" B.flag = true;",
" }",
" }",
"}")
.doTest();
}
@Test
public void testOtherClass_staticLock() throws Exception {
compilationHelper
.addSourceLines(
"threadsafety/Test.java",
"package threadsafety;",
"import javax.annotation.concurrent.GuardedBy;",
"class A {",
" static final Object lock = new Object();",
" @GuardedBy(\"lock\") static boolean flag = false;",
"}",
"class B {",
" void m() {",
" synchronized (A.lock) {",
" A.flag = true;",
" }",
" }",
"}")
.doTest();
}
@Test
public void instanceAccess_instanceGuard() throws Exception {
compilationHelper
.addSourceLines(
"threadsafety/Test.java",
"package threadsafety;",
"import javax.annotation.concurrent.GuardedBy;",
"class InstanceAccess_InstanceGuard {",
" class A {",
" final Object lock = new Object();",
" @GuardedBy(\"lock\")",
" int x;",
" }",
"",
"class B extends A {",
" void m() {",
" synchronized (this.lock) {",
" this.x++;",
" }",
" // BUG: Diagnostic contains:",
" // should be guarded by 'this.lock'",
" this.x++;",
" }",
"}",
"}")
.doTest();
}
@Test
public void instanceAccess_lexicalGuard() throws Exception {
compilationHelper
.addSourceLines(
"threadsafety/Test.java",
"package threadsafety;",
"import javax.annotation.concurrent.GuardedBy;",
"class InstanceAccess_LexicalGuard {",
" class Outer {",
" final Object lock = new Object();",
" class Inner {",
" @GuardedBy(\"lock\")",
" int x;",
" void m() {",
" synchronized (Outer.this.lock) {",
" this.x++;",
" }",
" // BUG: Diagnostic contains:",
" // should be guarded by 'Outer.this.lock'",
" this.x++;",
" }",
" }",
" }",
"}")
.doTest();
}
@Test
public void lexicalAccess_instanceGuard() throws Exception {
compilationHelper
.addSourceLines(
"threadsafety/Test.java",
"package threadsafety;",
"import javax.annotation.concurrent.GuardedBy;",
"class LexicalAccess_InstanceGuard {",
" class Outer {",
" final Object lock = new Object();",
" @GuardedBy(\"lock\")",
" int x;",
" class Inner {",
" void m() {",
" synchronized (Outer.this.lock) {",
" Outer.this.x++;",
" }",
" // BUG: Diagnostic contains:",
" // should be guarded by 'Outer.this.lock'",
" Outer.this.x++;",
" }",
" }",
" }",
"}")
.doTest();
}
@Test
public void lexicalAccess_lexicalGuard() throws Exception {
compilationHelper
.addSourceLines(
"threadsafety/Test.java",
"package threadsafety;",
"import javax.annotation.concurrent.GuardedBy;",
"class LexicalAccess_LexicalGuard {",
" class Outer {",
" final Object lock = new Object();",
" class Inner {",
" @GuardedBy(\"lock\")",
" int x;",
" class InnerMost {",
" void m() {",
" synchronized (Outer.this.lock) {",
" Inner.this.x++;",
" }",
" // BUG: Diagnostic contains:",
" // should be guarded by 'Outer.this.lock'",
" Inner.this.x++;",
" }",
" }",
" }",
" }",
"}")
.doTest();
}
@Test
public void instanceAccess_thisGuard() throws Exception {
compilationHelper
.addSourceLines(
"threadsafety/Test.java",
"package threadsafety;",
"import javax.annotation.concurrent.GuardedBy;",
"class InstanceAccess_ThisGuard {",
" class A {",
" @GuardedBy(\"this\")",
" int x;",
" }",
" class B extends A {",
" void m() {",
" synchronized (this) {",
" this.x++;",
" }",
" // BUG: Diagnostic contains:",
" // should be guarded by 'this'",
" this.x++;",
" }",
" }",
"}")
.doTest();
}
@Test
public void instanceAccess_namedThisGuard() throws Exception {
compilationHelper
.addSourceLines(
"threadsafety/Test.java",
"package threadsafety;",
"import javax.annotation.concurrent.GuardedBy;",
"class InstanceAccess_NamedThisGuard {",
" class Outer {",
" class Inner {",
" @GuardedBy(\"Outer.this\")",
" int x;",
" void m() {",
" synchronized (Outer.this) {",
" x++;",
" }",
" // BUG: Diagnostic contains:",
" // should be guarded by 'Outer.this'",
" x++;",
" }",
" }",
" }",
"}")
.doTest();
}
@Test
public void lexicalAccess_thisGuard() throws Exception {
compilationHelper
.addSourceLines(
"threadsafety/Test.java",
"package threadsafety;",
"import javax.annotation.concurrent.GuardedBy;",
"class LexicalAccess_ThisGuard {",
" class Outer {",
" @GuardedBy(\"this\")",
" int x;",
" class Inner {",
" void m() {",
" synchronized (Outer.this) {",
" Outer.this.x++;",
" }",
" // BUG: Diagnostic contains:",
" // should be guarded by 'Outer.this'",
" Outer.this.x++;",
" }",
" }",
" }",
"}")
.doTest();
}
@Test
public void lexicalAccess_namedThisGuard() throws Exception {
compilationHelper
.addSourceLines(
"threadsafety/Test.java",
"package threadsafety;",
"import javax.annotation.concurrent.GuardedBy;",
"class LexicalAccess_NamedThisGuard {",
" class Outer {",
" class Inner {",
" @GuardedBy(\"Outer.this\")",
" int x;",
" class InnerMost {",
" void m() {",
" synchronized (Outer.this) {",
" Inner.this.x++;",
" }",
" // BUG: Diagnostic contains:",
" // should be guarded by 'Outer.this'",
" Inner.this.x++;",
" }",
" }",
" }",
" }",
"}")
.doTest();
}
// Test that the analysis doesn't crash on lock expressions it doesn't recognize.
// Note: there's currently no way to use @GuardedBy to specify that the guard is a specific array
// element.
@Test
public void complexLockExpression() throws Exception {
compilationHelper
.addSourceLines(
"threadsafety/Test.java",
"package threadsafety;",
"class ComplexLockExpression {",
" final Object[] xs = {};",
" final int[] ys = {};",
" void m(int i) {",
" synchronized (xs[i]) {",
" ys[i]++;",
" }",
" }",
"}")
.doTest();
}
@Test
public void wrongInnerClassInstance() throws Exception {
compilationHelper
.addSourceLines(
"threadsafety/Test.java",
"package threadsafety;",
"import javax.annotation.concurrent.GuardedBy;",
"class WrongInnerClassInstance {",
" final Object lock = new Object();",
" class Inner {",
" @GuardedBy(\"lock\") int x = 0;",
" void m(Inner i) {",
" synchronized (WrongInnerClassInstance.this.lock) {",
" // BUG: Diagnostic contains:",
" // guarded by 'lock' in enclosing instance"
+ " 'threadsafety.WrongInnerClassInstance' of 'i'",
" i.x++;",
" }",
" }",
" }",
"}")
.doTest();
}
// (This currently passes because the analysis ignores try-with-resources, not because it
// understands why this example is safe.)
@Ignore // TODO(cushon): support try-with-resources
@Test
public void tryWithResources() throws Exception {
compilationHelper
.addSourceLines(
"threadsafety/Test.java",
"package threadsafety;",
"import javax.annotation.concurrent.GuardedBy;",
"import java.util.concurrent.locks.Lock;",
"class Test {",
" Lock lock;",
" @GuardedBy(\"lock\")",
" int x;",
" static class LockCloser implements AutoCloseable {",
" Lock lock;",
" LockCloser(Lock lock) {",
" this.lock = lock;",
" this.lock.lock();",
" }",
" @Override",
" public void close() throws Exception {",
" lock.unlock();",
" }",
" }",
" void m() throws Exception {",
" try (LockCloser _ = new LockCloser(lock)) {",
" x++;",
" }",
" }",
"}")
.doTest();
}
// Test that the contents of try-with-resources block are ignored (for now), but the catch and
// finally blocks are checked.
// TODO(cushon): support try-with-resources block.
@Test
public void tryWithResourcesAreNotFullyUnsupported() throws Exception {
compilationHelper
.addSourceLines(
"threadsafety/Test.java",
"package threadsafety;",
"import javax.annotation.concurrent.GuardedBy;",
"import java.util.concurrent.locks.Lock;",
"class Test {",
" Lock lock;",
" @GuardedBy(\"lock\")",
" int x;",
" void m(AutoCloseable c) throws Exception {",
" try (AutoCloseable unused = c) {",
" x++; // should be an error!",
" } catch (Exception e) {",
" // BUG: Diagnostic contains:",
" // should be guarded by 'this.lock'",
" x++;",
" throw e;",
" } finally {",
" // BUG: Diagnostic contains:",
" // should be guarded by 'this.lock'",
" x++;",
" }",
" }",
"",
" void n(AutoCloseable c) throws Exception {",
" lock.lock();",
" try (AutoCloseable unused = c) {",
" } catch (Exception e) {",
" x++;",
" } finally {",
" lock.unlock();",
" }",
" }",
"}")
.doTest();
}
@Test
public void testLexicalScopingExampleOne() throws Exception {
compilationHelper
.addSourceLines(
"threadsafety/Test.java",
"package threadsafety;",
"import javax.annotation.concurrent.GuardedBy;",
"class Transaction {",
" @GuardedBy(\"this\")",
" int x;",
" interface Handler {",
" void apply();",
" }",
" public void handle() {",
" runHandler(new Handler() {",
" public void apply() {",
" // BUG: Diagnostic contains:",
" // should be guarded by 'Transaction.this'",
" x++;",
" }",
" });",
" }",
" private synchronized void runHandler(Handler handler) {",
" handler.apply();",
" }",
"}")
.doTest();
}
// TODO(cushon): allowing @GuardedBy on overridden methods is unsound.
@Test
public void testLexicalScopingExampleTwo() throws Exception {
compilationHelper
.addSourceLines(
"threadsafety/Test.java",
"package threadsafety;",
"import javax.annotation.concurrent.GuardedBy;",
"class Transaction {",
" @GuardedBy(\"this\")",
" int x;",
" interface Handler {",
" void apply();",
" }",
" public void handle() {",
" runHandler(new Handler() {",
" @GuardedBy(\"Transaction.this\")",
" public void apply() {",
" x++;",
" }",
" });",
" }",
" private synchronized void runHandler(Handler handler) {",
" // This isn't safe...",
" handler.apply();",
" }",
"}")
.doTest();
}
@Test
public void testAliasing() throws Exception {
compilationHelper
.addSourceLines(
"threadsafety/Test.java",
"package threadsafety;",
"import javax.annotation.concurrent.GuardedBy;",
"import java.util.List;",
"import java.util.ArrayList;",
"class Names {",
" @GuardedBy(\"this\")",
" List<String> names = new ArrayList<>();",
" public void addName(String name) {",
" List<String> copyOfNames;",
" synchronized (this) {",
" copyOfNames = names; // OK: access of 'names' guarded by 'this'",
" }",
" copyOfNames.add(name); // should be an error: this access is not thread-safe!",
" }",
"}")
.doTest();
}
@Test
public void testMonitorGuard() throws Exception {
compilationHelper
.addSourceLines(
"threadsafety/Test.java",
"package threadsafety;",
"import javax.annotation.concurrent.GuardedBy;",
"import com.google.common.util.concurrent.Monitor;",
"import java.util.List;",
"import java.util.ArrayList;",
"class Test {",
" final Monitor monitor = new Monitor();",
" @GuardedBy(\"monitor\") int x;",
" final Monitor.Guard guard = new Monitor.Guard(monitor) {",
" @Override public boolean isSatisfied() {",
" x++;",
" return true;",
" }",
" };",
"}")
.doTest();
}
@Test
public void testSemaphore() throws Exception {
compilationHelper
.addSourceLines(
"threadsafety/Test.java",
"package threadsafety;",
"import javax.annotation.concurrent.GuardedBy;",
"import java.util.concurrent.Semaphore;",
"class Test {",
" final Semaphore semaphore = null;",
" @GuardedBy(\"semaphore\")",
" int x;",
" void m() throws InterruptedException {",
" semaphore.acquire();",
" // BUG: Diagnostic contains:",
" // access should be guarded by 'this.semaphore'",
" x++;",
" try {",
" x++;",
" } finally {",
" semaphore.release();",
" }",
" // BUG: Diagnostic contains:",
" // access should be guarded by 'this.semaphore'",
" x++;",
" }",
"}")
.doTest();
}
@Test
public void synchronizedOnLockMethod_negative() throws Exception {
compilationHelper
.addSourceLines(
"threadsafety/Test.java",
"package threadsafety;",
"import javax.annotation.concurrent.GuardedBy;",
// do not remove, regression test for a bug when RWL is on the classpath
"import java.util.concurrent.locks.ReadWriteLock;",
"class Test {",
" Object lock() { return null; }",
" @GuardedBy(\"lock()\")",
" int x;",
" void m() {",
" synchronized (lock()) {",
" x++;",
" }",
" }",
"}")
.doTest();
}
@Test
public void suppressLocalVariable() throws Exception {
compilationHelper
.addSourceLines(
"threadsafety/Test.java",
"package threadsafety;",
"import javax.annotation.concurrent.GuardedBy;",
"import java.util.concurrent.locks.Lock;",
"class Test {",
" final Lock lock = null;",
" @GuardedBy(\"lock\")",
" int x;",
" void m() {",
" @SuppressWarnings(\"GuardedBy\")",
" int z = x++;",
" }",
"}")
.doTest();
}
// regression test for issue 387
@Test
public void enclosingBlockScope() throws Exception {
compilationHelper
.addSourceLines(
"threadsafety/Test.java",
"package threadsafety;",
"import javax.annotation.concurrent.GuardedBy;",
"public class Test {",
" public final Object mu = new Object();",
" @GuardedBy(\"mu\") int x = 1;",
" {",
" new Object() {",
" void f() {",
" synchronized (mu) {",
" x++;",
" }",
" }",
" };",
" }",
"}")
.doTest();
}
@Ignore("b/26834754") // fix resolution of qualified type names
@Test
public void qualfiedType() throws Exception {
compilationHelper
.addSourceLines(
"lib/Lib.java",
"package lib;",
"public class Lib {",
" public static class Inner {",
" public static final Object mu = new Object();",
" }",
"}")
.addSourceLines(
"threadsafety/Test.java",
"package threadsafety;",
"import javax.annotation.concurrent.GuardedBy;",
"public class Test {",
" public final Object mu = new Object();",
" @GuardedBy(\"lib.Lib.Inner.mu\") int x = 1;",
" void f() {",
" synchronized (lib.Lib.Inner.mu) {",
" x++;",
" }",
" }",
"}")
.doTest();
}
@Ignore("b/26834754") // fix resolution of qualified type names
@Test
public void innerClassTypeQualifier() throws Exception {
compilationHelper
.addSourceLines(
"lib/Lib.java",
"package lib;",
"public class Lib {",
" public static class Inner {",
" public static final Object mu = new Object();",
" }",
"}")
.addSourceLines(
"threadsafety/Test.java",
"package threadsafety;",
"import javax.annotation.concurrent.GuardedBy;",
"import lib.Lib;",
"public class Test {",
" public final Object mu = new Object();",
" @GuardedBy(\"Lib.Inner.mu\") int x = 1;",
" void f() {",
" synchronized (Lib.Inner.mu) {",
" x++;",
" }",
" }",
"}")
.doTest();
}
@Test
public void instanceInitializersAreUnchecked() throws Exception {
compilationHelper
.addSourceLines(
"threadsafety/Test.java",
"package threadsafety;",
"import javax.annotation.concurrent.GuardedBy;",
"public class Test {",
" public final Object mu1 = new Object();",
" public final Object mu2 = new Object();",
" @GuardedBy(\"mu1\") int x = 1;",
" {",
" synchronized (mu2) {",
" x++;",
" }",
" synchronized (mu1) {",
" x++;",
" }",
" }",
"}")
.doTest();
}
@Test
public void classInitializersAreUnchecked() throws Exception {
compilationHelper
.addSourceLines(
"threadsafety/Test.java",
"package threadsafety;",
"import javax.annotation.concurrent.GuardedBy;",
"public class Test {",
" public static final Object mu1 = new Object();",
" public static final Object mu2 = new Object();",
" @GuardedBy(\"mu1\") static int x = 1;",
" static {",
" synchronized (mu2) {",
" x++;",
" }",
" synchronized (mu1) {",
" x++;",
" }",
" }",
"}")
.doTest();
}
@Test
public void staticFieldInitializersAreUnchecked() throws Exception {
compilationHelper
.addSourceLines(
"threadsafety/Test.java",
"package threadsafety;",
"import javax.annotation.concurrent.GuardedBy;",
"public class Test {",
" public static final Object mu = new Object();",
" @GuardedBy(\"mu\") static int x0 = 1;",
" static int x1 = x0++;",
"}")
.doTest();
}
@Test
public void instanceFieldInitializersAreUnchecked() throws Exception {
compilationHelper
.addSourceLines(
"threadsafety/Test.java",
"package threadsafety;",
"import javax.annotation.concurrent.GuardedBy;",
"public class Test {",
" public final Object mu = new Object();",
" @GuardedBy(\"mu\") int x0 = 1;",
" int x1 = x0++;",
"}")
.doTest();
}
@Test
public void innerClassMethod() throws Exception {
compilationHelper
.addSourceLines(
"threadsafety/Test.java",
"package threadsafety;",
"import javax.annotation.concurrent.GuardedBy;",
"public class Test {",
" public final Object mu = new Object();",
" class Inner {",
" @GuardedBy(\"mu\") int x;",
" @GuardedBy(\"Test.this\") int y;",
" }",
" void f(Inner i) {",
" synchronized (mu) {",
" // BUG: Diagnostic contains:",
" // guarded by 'mu' in enclosing instance 'threadsafety.Test' of 'i'",
" i.x++;",
" }",
" }",
" synchronized void g(Inner i) {",
" // BUG: Diagnostic contains:",
" // guarded by enclosing instance 'threadsafety.Test' of 'i'",
" i.y++;",
" }",
"}")
.doTest();
}
@Test
public void innerClassMethod_classBoundary() throws Exception {
compilationHelper
.addSourceLines(
"threadsafety/Outer.java",
"package threadsafety;",
"import javax.annotation.concurrent.GuardedBy;",
"public class Outer {",
" public final Object mu = new Object();",
" class Inner {",
" @GuardedBy(\"mu\") int x;",
" @GuardedBy(\"Outer.this\") int y;",
" }",
"}")
.addSourceLines(
"threadsafety/Test.java",
"package threadsafety;",
"import javax.annotation.concurrent.GuardedBy;",
"public class Test {",
" void f() {",
" Outer a = new Outer();",
" Outer b = new Outer();",
" Outer.Inner ai = a.new Inner();",
" synchronized (b.mu) {",
" // BUG: Diagnostic contains:",
" // Access should be guarded by 'mu' in enclosing instance 'threadsafety.Outer'"
+ " of 'ai', which is not accessible in this scope; instead found: 'b.mu'",
" ai.x++;",
" }",
" synchronized (b) {",
" // BUG: Diagnostic contains:",
" // Access should be guarded by enclosing instance 'threadsafety.Outer' of 'ai',"
+ " which is not accessible in this scope; instead found: 'b'",
" ai.y++;",
" }",
" }",
"}")
.doTest();
}
@Test
public void regression_b27686620() throws Exception {
compilationHelper
.addSourceLines(
"A.java", //
"class A extends One {",
" void g() {}",
"}")
.addSourceLines(
"B.java",
"import javax.annotation.concurrent.GuardedBy;",
"class One {",
" @GuardedBy(\"One.class\") static int x = 1;",
" static void f() { synchronized (One.class) { x++; } }",
"}",
"class Two {",
" @GuardedBy(\"Two.class\") static int x = 1;",
" static void f() { synchronized (Two.class) { x++; } }",
"}")
.addSourceLines(
"C.java", //
"class B extends Two {",
" void g() {}",
"}")
.doTest();
}
@Ignore // TODO(cushon): clean up existing instances and re-enable
@Test
public void qualifiedMethod() throws Exception {
compilationHelper
.addSourceLines(
"threadsafety/Test.java",
"package threadsafety;",
"import javax.annotation.concurrent.GuardedBy;",
"public class Test {",
" @GuardedBy(\"this\") void f() {}",
" void main() {",
" // BUG: Diagnostic contains: 'this', which could not be resolved",
" new Test().f();",
" Test t = new Test();",
" // BUG: Diagnostic contains: guarded by 't'",
" t.f();",
" }",
"}")
.doTest();
}
// regression test for #426
@Test
public void noSuchMethod() throws Exception {
compilationHelper
.addSourceLines(
"Foo.java", //
"public class Foo {}")
.addSourceLines(
"Test.java",
"import javax.annotation.concurrent.GuardedBy;",
"public class Test {",
" Foo foo;",
" // BUG: Diagnostic contains: could not resolve guard",
" @GuardedBy(\"foo.get()\") Object o = null;",
"}")
.doTest();
}
// regression test for b/34251959
@Test
public void lambda() throws Exception {
compilationHelper
.addSourceLines(
"Test.java",
"import javax.annotation.concurrent.GuardedBy;",
"public class Test {",
" @GuardedBy(\"this\") int x;",
" synchronized void f() {",
" Runnable r = () -> {",
" // BUG: Diagnostic contains: should be guarded by 'this',",
" x++;",
" };",
" }",
"}")
.doTest();
}
}