/*
 * Copyright 2014 The Error Prone Authors.
 *
 * 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.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 final CompilationTestHelper compilationHelper =
      CompilationTestHelper.newInstance(GuardedByChecker.class, getClass());

  @Test
  public void locked() {
    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 staticLocked() {
    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 monitor() {
    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 wrongLock() {
    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 guardedStaticFieldAccess_1() {
    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 guardedStaticFieldAccess_2() {
    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 guardedStaticFieldAccess_3() {
    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 guardedStaticFieldAccess_enclosingClass() {
    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 badStaticFieldAccess() {
    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 badGuard() {
    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 errorProneAnnotation() {
    compilationHelper
        .addSourceLines(
            "threadsafety/Test.java",
            """
            package threadsafety;
            import com.google.errorprone.annotations.concurrent.GuardedBy;
            class Test {
              // BUG: Diagnostic contains: Invalid @GuardedBy expression
              @GuardedBy("foo") int y;
            }
            """)
        .doTest();
  }

  @Test
  public void unheldInstanceGuard() {
    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 unheldItselfGuard() {
    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() {
    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 methodQualifiedWithThis() {
    compilationHelper
        .addSourceLines(
            "threadsafety/Test.java",
            """
            package threadsafety;
            import java.util.List;
            import javax.annotation.concurrent.GuardedBy;
            class Itself {
              @GuardedBy("this")
              void f() {};
              void g() {
                // BUG: Diagnostic contains:
                // should be guarded by 'this'
                this.f();
                synchronized (this) { f(); }
                synchronized (this) { this.f(); }
              }
            }
            """)
        .doTest();
  }

  @Test
  public void ctor() {
    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 badGuardMethodAccess() {
    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 transitiveGuardMethodAccess() {
    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 readWriteLockCopy() {
    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 readWriteLock() {
    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 readWriteLockIsIgnored() {
    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 innerClass_enclosingClassLock() {
    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 innerClass_thisLock() {
    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 anonymousClass() {
    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 inheritedLock() {
    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 enclosingSuperAccess() {
    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 superAccess_this() {
    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 superAccess_lock() {
    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 superAccess_staticLock() {
    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 otherClass_bad_staticLock() {
    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 otherClass_bad_staticLock_alsoSub() {
    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 otherClass_staticLock() {
    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() {
    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() {
    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() {
    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() {
    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() {
    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() {
    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() {
    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() {
    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() {
    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() {
    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() {
    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
  public void tryWithResources_resourceVariables() {
    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) {
                  // BUG: Diagnostic contains:
                  x++;
                } 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 lexicalScopingExampleOne() {
    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 lexicalScopingExampleTwo() {
    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 aliasing() {
    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 monitorGuard() {
    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 semaphore() {
    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() {
    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() {
    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() {
    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 qualifiedType() {
    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() {
    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() {
    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() {
    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() {
    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() {
    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() {
    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();
  }

  @Ignore("b/128641856")
  @Test
  public void innerClassInMethod() {
    compilationHelper
        .addSourceLines(
            "threadsafety/Test.java",
            """
            package threadsafety;
            import javax.annotation.concurrent.GuardedBy;
            public class Test {
              public final Object mu = new Object();
              @GuardedBy("mu") int i = 0;
              void f() {
                class Inner {
                  @GuardedBy("mu") void m() {i++;}
                }
                Inner i = new Inner();
                synchronized (mu) {
                  i.m();
                }
              }
            }
            """)
        .doTest();
  }

  @Test
  public void innerClassMethod_classBoundary() {
    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() {
    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();
  }

  @Test
  public void qualifiedMethod() {
    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();
  }

  @Test
  public void qualifiedMethodWrongThis_causesFinding_whenMatchOnErrorsFlagNotSet() {
    CompilationTestHelper.newInstance(GuardedByChecker.class, getClass())
        .addSourceLines(
            "MemoryAllocatedInfoJava.java",
            """
import javax.annotation.concurrent.GuardedBy;
public class MemoryAllocatedInfoJava {
  private static final class AllocationStats {
    @GuardedBy("MemoryAllocatedInfoJava.this")
    void addAllocation(long size) {}
  }
  public void addStackTrace(long size) {
    synchronized (this) {
      AllocationStats stat = new AllocationStats();
      // BUG: Diagnostic contains: Access should be guarded by enclosing instance 'MemoryAllocatedInfoJava' of 'stat', which is not accessible in this scope; instead found: 'this'
      stat.addAllocation(size);
    }
  }
}
""")
        .doTest();
  }

  // regression test for #426
  @Test
  public void noSuchMethod() {
    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() {
    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();
  }

  @Test
  public void multipleLocks() {
    compilationHelper
        .addSourceLines(
            "GuardedBy.java",
            """
            @interface GuardedBy {
              String[] value() default {};
            }
            """)
        .addSourceLines(
            "Test.java",
            """
            public class Test {
              private final Object mu = new Object();
              @GuardedBy({"this", "mu"}) int x;
              void f() {
                synchronized (this) {
                  synchronized (mu) {
                    x++;
                  }
                }
                synchronized (this) {
                  // BUG: Diagnostic contains: should be guarded by 'this.mu'
                  x++;
                }
                synchronized (mu) {
                  // BUG: Diagnostic contains: should be guarded by 'this'
                  x++;
                }
              }
            }
            """)
        .doTest();
  }

  // Ensure sure outer instance handling doesn't accidentally include enclosing classes of
  // static member classes.
  @Test
  public void staticMemberClass_enclosingInstanceLock() {
    compilationHelper
        .addSourceLines(
            "threadsafety/Test.java",
            """
            package threadsafety;
            import javax.annotation.concurrent.GuardedBy;
            public class Test {
              final Object mu = new Object();
              private static final class Baz {
                // BUG: Diagnostic contains: could not resolve guard
                @GuardedBy("mu") int x;
              }
              public void m(Baz b) {
                synchronized (mu) {
                  // BUG: Diagnostic contains: 'mu', which could not be resolved
                  b.x++;
                }
              }
            }
            """)
        .doTest();
  }

  // Ensure sure outer instance handling doesn't accidentally include enclosing classes of
  // static member classes.
  @Test
  public void staticMemberClass_staticOuterClassLock() {
    compilationHelper
        .addSourceLines(
            "threadsafety/Test.java",
            """
            package threadsafety;
            import javax.annotation.concurrent.GuardedBy;
            public class Test {
              static final Object mu = new Object();
              private static final class Baz {
                @GuardedBy("mu") int x;
              }
              public void m(Baz b) {
                synchronized (mu) {
                  b.x++;
                }
              }
            }
            """)
        .doTest();
  }

  @Test
  public void newClassBase() {
    compilationHelper
        .addSourceLines(
            "Foo.java",
            """
            import javax.annotation.concurrent.GuardedBy;
            public class Foo {
              private final Object mu = new Object();
              @GuardedBy("mu") int x;
            }
            """)
        .addSourceLines(
            "Bar.java",
            """
            public class Bar {
              void bar (Foo f) {
                // BUG: Diagnostic contains: should be guarded by 'f.mu'
                f.x = 10;
              }
              void bar () {
                // BUG: Diagnostic contains: should be guarded by 'mu'
                new Foo().x = 11;
              }
            }
            """)
        .doTest();
  }

  @Test
  public void suppressionOnMethod() {
    compilationHelper
        .addSourceLines(
            "Test.java",
            """
            package threadsafety;
            import javax.annotation.concurrent.GuardedBy;
            class Test {
              final Object lock = null;
              void foo() {
                class Foo extends Object {
                  @GuardedBy("lock") int x;
                  @SuppressWarnings("GuardedBy")
                  void m() {
                    synchronized (lock) {
                      int z = x++;
                    }
                  }
                }
              }
            }
            """)
        .doTest();
  }

  @Test
  public void missingGuard() {
    compilationHelper
        .addSourceLines(
            "threadsafety/Lib.java",
            """
            package threadsafety;
            import javax.annotation.concurrent.GuardedBy;
            @SuppressWarnings("GuardedBy")
            class Lib {
              @GuardedBy("lock")
              public void doSomething() {}
            }
            """)
        .addSourceLines(
            "threadsafety/Test.java",
            """
            package threadsafety;
            class Test {
              void m(Lib lib) {
                // BUG: Diagnostic contains: 'lock', which could not be resolved
                lib.doSomething();
              }
            }
            """)
        .doTest();
  }

  @Test
  public void parameterGuard() {
    compilationHelper
        .addSourceLines(
            "threadsafety/Test.java",
            """
import javax.annotation.concurrent.GuardedBy;
class Work {
  final Object lock = new Object();
  Object getLock() {
    return lock;
  }
  @GuardedBy("getLock()")
  void workStarted() {
  }
}
class Worker {
  @GuardedBy("work.getLock()")
  void f(Work work) {
    work.workStarted(); // ok
  }
  // BUG: Diagnostic contains: could not resolve guard
  @GuardedBy("work2.getLock()") void g() {}
  @GuardedBy("a.getLock()")
  void g(Work a, Work b) {
    a.workStarted(); // ok
    // BUG: Diagnostic contains: should be guarded by 'b.getLock()'; instead found: 'a.getLock()'
    b.workStarted();
  }
}
abstract class Test {
  abstract Work getWork();
  void t(Worker worker, Work work) {
    synchronized(work.getLock()) {
      worker.f(work);
    }
    synchronized(getWork().getLock()) {
      // BUG: Diagnostic contains: guarded by 'work.getLock()'
      worker.f(getWork());
    }
    // BUG: Diagnostic contains: guarded by 'work.getLock()'
    worker.f(work);
  }
}
""")
        .doTest();
  }

  @Test
  public void parameterGuardNegative() {
    compilationHelper
        .addSourceLines(
            "threadsafety/Test.java",
            """
            import javax.annotation.concurrent.GuardedBy;
            class Work {
              final Object lock = new Object();
              Object getLock() {
                return lock;
              }
            }
            class Worker {
              @GuardedBy("work.getLock()")
              void f(Work work) {
              }
            }
            class Test {
              void t(Worker worker, Work work) {
                synchronized(work.getLock()) {
                  worker.f(work);
                }
              }
            }
            """)
        .doTest();
  }

  @Test
  public void parameterGuardNegativeSimpleName() {
    compilationHelper
        .addSourceLines(
            "threadsafety/Test.java",
            """
            import javax.annotation.concurrent.GuardedBy;
            class Work {
              final Object lock = new Object();
              Object getLock() {
                return lock;
              }
            }
            class Worker {
              @GuardedBy("work.getLock()")
              void f(Work work) {
              }
              void g() {
                Work work = new Work();
                synchronized(work.getLock()) {
                  f(work);
                }
              }
            }
            """)
        .doTest();
  }

  @Test
  public void varargsArity() {
    compilationHelper
        .addSourceLines(
            "threadsafety/Test.java",
            """
            import javax.annotation.concurrent.GuardedBy;
            class Test {
              @GuardedBy("xs.toString()")
              void f(int x, Object... xs) {
              }
              void g() {
                Object[] xs = null;
                synchronized(xs.toString()) {
                  f(0, xs);
                }
                // BUG: Diagnostic contains:
                f(0);
              }
            }
            """)
        .doTest();
  }

  @Test
  public void immediateLambdas() {
    compilationHelper
        .addSourceLines(
            "Test.java",
            """
            import java.util.ArrayList;
            import java.util.List;
            import java.util.Optional;
            import javax.annotation.concurrent.GuardedBy;
            class Test {
              @GuardedBy("this") private final List<String> xs = new ArrayList<>();
              @GuardedBy("ys") private final List<String> ys = new ArrayList<>();
              public synchronized void add(Optional<String> x) {
                x.ifPresent(y -> xs.add(y));
                x.ifPresent(xs::add);
                // BUG: Diagnostic contains:
                x.ifPresent(y -> ys.add(y));
                // BUG: Diagnostic contains:
                x.ifPresent(ys::add);
              }
            }
            """)
        .doTest();
  }

  @Test
  public void methodReferences_shouldBeFlagged() {
    compilationHelper
        .addSourceLines(
            "Test.java",
            """
            import java.util.ArrayList;
            import java.util.List;
            import java.util.Optional;
            import java.util.function.Predicate;
            import javax.annotation.concurrent.GuardedBy;
            class Test {
              @GuardedBy("this") private final List<String> xs = new ArrayList<>();
              private final List<Predicate<String>> preds = new ArrayList<>();
              public synchronized void test() {
                // BUG: Diagnostic contains:
                preds.add(xs::contains);
              }
            }
            """)
        .doTest();
  }

  @Test
  public void methodReference_referencedMethodIsFlagged() {
    compilationHelper
        .addSourceLines(
            "Test.java",
            """
            import java.util.ArrayList;
            import java.util.List;
            import java.util.Optional;
            import java.util.function.Predicate;
            import javax.annotation.concurrent.GuardedBy;
            class Test {
              private final List<Predicate<String>> preds = new ArrayList<>();
              public synchronized void test() {
                Optional.of("foo").ifPresent(this::frobnicate);
                // BUG: Diagnostic contains: should be guarded by
                preds.add(this::frobnicate);
              }
              @GuardedBy("this")
              public boolean frobnicate(String x) {
                return true;
              }
            }
            """)
        .doTest();
  }

  @Test
  public void lambdaMethodInvokedImmediately_shouldNotBeFlagged() {
    compilationHelper
        .addSourceLines(
            "Test.java",
            """
            import java.util.List;
            import javax.annotation.concurrent.GuardedBy;
            class Test {
              @GuardedBy("this") private final Object o = new Object();
              public synchronized void test(List<?> xs) {
                xs.forEach(x -> o.toString());
              }
            }
            """)
        .doTest();
  }
}
