You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
577 lines
12 KiB
577 lines
12 KiB
/*
|
|
* Copyright (c) Facebook, Inc. and its affiliates.
|
|
*
|
|
* This source code is licensed under the MIT license found in the
|
|
* LICENSE file in the root directory of this source tree.
|
|
*/
|
|
|
|
package codetoanalyze.java.infer;
|
|
|
|
import android.annotation.SuppressLint;
|
|
import com.google.common.annotations.VisibleForTesting;
|
|
import java.io.Closeable;
|
|
import java.util.concurrent.locks.Lock;
|
|
import java.util.concurrent.locks.ReadWriteLock;
|
|
import java.util.concurrent.locks.ReentrantLock;
|
|
import java.util.concurrent.locks.ReentrantReadWriteLock;
|
|
import javax.annotation.concurrent.GuardedBy;
|
|
|
|
public class GuardedByExample {
|
|
|
|
static class AutoCloseableReadWriteUpdateLock implements Closeable {
|
|
@Override
|
|
public void close() {}
|
|
}
|
|
|
|
private Object mLock = new Object();
|
|
|
|
private Object mOtherLock = new Object();
|
|
|
|
private AutoCloseableReadWriteUpdateLock mReadWriteLock = new AutoCloseableReadWriteUpdateLock();
|
|
|
|
@GuardedBy("mLock")
|
|
private Object f = new Object();
|
|
|
|
@GuardedBy("this")
|
|
Object g = new Object();
|
|
|
|
Object mCopyOfG;
|
|
|
|
@GuardedBy("SomeLockThatDoesntExist")
|
|
Object h = new Object();
|
|
|
|
@GuardedBy("mReadWriteLock")
|
|
Object i = new Object();
|
|
|
|
private static Object sLock = new Object();
|
|
|
|
@GuardedBy("sLock")
|
|
static Object sFld;
|
|
|
|
@GuardedBy("GuardedByExample.class")
|
|
static Object sGuardedByClass;
|
|
|
|
static {
|
|
// don't warn on class initializer
|
|
sFld = new Object();
|
|
}
|
|
|
|
public GuardedByExample() {
|
|
// don't warn on reads or writes of Guarded fields in constructor
|
|
f.toString();
|
|
g = new Object();
|
|
}
|
|
|
|
void readFBad() {
|
|
this.f.toString();
|
|
}
|
|
|
|
@SuppressLint("InvalidAccessToGuardedField")
|
|
void readFBadButSuppressed() {
|
|
this.f.toString();
|
|
}
|
|
|
|
@SuppressLint("SomeOtherWarning")
|
|
void readFBadButSuppressedOther() {
|
|
this.f.toString();
|
|
}
|
|
|
|
void writeFBad() {
|
|
this.f = new Object();
|
|
}
|
|
|
|
void readFBadWrongLock() {
|
|
synchronized (mOtherLock) {
|
|
this.f.toString(); // f is supposed to be protected by mLock
|
|
}
|
|
}
|
|
|
|
void writeFBadWrongLock() {
|
|
synchronized (mOtherLock) {
|
|
this.f = new Object(); // f is supposed to be protected by mLock
|
|
}
|
|
}
|
|
|
|
void readFAfterBlockBad() {
|
|
synchronized (mLock) {
|
|
}
|
|
this.f.toString();
|
|
}
|
|
|
|
void writeFAfterBlockBad() {
|
|
synchronized (mLock) {
|
|
}
|
|
this.f = new Object();
|
|
}
|
|
|
|
@GuardedBy("mOtherLock")
|
|
void readFBadWrongAnnotation() {
|
|
this.f.toString();
|
|
}
|
|
|
|
@GuardedBy("mLock")
|
|
void readFOkMethodAnnotated() {
|
|
this.f.toString();
|
|
}
|
|
|
|
synchronized void synchronizedMethodReadOk() {
|
|
this.g.toString();
|
|
}
|
|
|
|
synchronized void synchronizedMethodWriteOk() {
|
|
this.g = new Object();
|
|
}
|
|
|
|
void readFOkSynchronized() {
|
|
synchronized (mLock) {
|
|
this.f.toString();
|
|
}
|
|
}
|
|
|
|
void writeFOkSynchronized() {
|
|
synchronized (mLock) {
|
|
this.f = new Object();
|
|
}
|
|
}
|
|
|
|
synchronized void synchronizedMethodReadBad() {
|
|
this.f.toString(); // f is supposed to be protected by mLock, not this
|
|
}
|
|
|
|
synchronized void synchronizedMethodWriteBad() {
|
|
this.f = new Object(); // f is supposed to be protected by mLock, not this
|
|
}
|
|
|
|
void reassignCopyOk() {
|
|
synchronized (this) {
|
|
mCopyOfG = g; // these are ok: access of g guarded by this
|
|
}
|
|
mCopyOfG = new Object(); // ok; this doesn't change the value of g
|
|
}
|
|
|
|
void readHBad() {
|
|
synchronized (mLock) { // h is not protected by mLock
|
|
this.h.toString();
|
|
}
|
|
}
|
|
|
|
synchronized void readHBadSynchronizedMethodShouldntHelp() {
|
|
this.h.toString(); // h is not protected by this
|
|
}
|
|
|
|
private void privateUnguardedAccess() {
|
|
// not protected, but safe if all call sites guard the access to f
|
|
this.g.toString();
|
|
}
|
|
|
|
public void guardedCallSite1() {
|
|
synchronized (this) {
|
|
privateUnguardedAccess(); // should not warn; lock is held
|
|
}
|
|
}
|
|
|
|
public synchronized void guardedCallSite2() {
|
|
privateUnguardedAccess(); // should not warn; lock is held
|
|
}
|
|
|
|
private void wrapper() {
|
|
privateUnguardedAccess(); // should not warn, just propagate the proof obl
|
|
}
|
|
|
|
public void guardedCallSite3() {
|
|
synchronized (this) {
|
|
wrapper(); // should not warn
|
|
}
|
|
}
|
|
|
|
void readWriteLockOk() {
|
|
try (AutoCloseableReadWriteUpdateLock lock = mReadWriteLock) {
|
|
this.i.toString();
|
|
}
|
|
}
|
|
|
|
static synchronized void staticSynchronizedOk() {
|
|
sGuardedByClass.toString();
|
|
}
|
|
|
|
static void synchronizeOnClassOk1() {
|
|
synchronized (GuardedByExample.class) {
|
|
sGuardedByClass.toString(); // should not warn here
|
|
sGuardedByClass = new Object(); // or here
|
|
}
|
|
}
|
|
|
|
void synchronizedOnThisBad() {
|
|
sGuardedByClass.toString();
|
|
}
|
|
|
|
Object dontReportOnCompilerGenerated() {
|
|
return new Object() {
|
|
public void accessInAnonClassOk() {
|
|
synchronized (mLock) {
|
|
f.toString();
|
|
}
|
|
}
|
|
};
|
|
}
|
|
|
|
Object readFromInnerClassOkOuter() {
|
|
return new Object() {
|
|
public String readFromInnerClassOk() {
|
|
synchronized (GuardedByExample.this) {
|
|
return g.toString();
|
|
}
|
|
}
|
|
};
|
|
}
|
|
|
|
Object readFromInnerClassBad1Outer() {
|
|
return new Object() {
|
|
public String readFromInnerClassBad1() {
|
|
synchronized (this) {
|
|
return g.toString(); // g is guarded by the outer class this, not this$0
|
|
}
|
|
}
|
|
};
|
|
}
|
|
|
|
Object readFromInnerClassBad2Outer() {
|
|
return new Object() {
|
|
public synchronized String readFromInnerClassBad2() {
|
|
return g.toString(); // g is guarded by the outer class this, not this$0
|
|
}
|
|
};
|
|
}
|
|
|
|
@VisibleForTesting
|
|
public void visibleForTestingOk1() {
|
|
f.toString(); // should push proof obl to caller
|
|
}
|
|
|
|
@VisibleForTesting
|
|
void visibleForTestingOk2() {
|
|
f.toString(); // should push proof obl to caller
|
|
}
|
|
|
|
synchronized Object returnPtG() {
|
|
return g;
|
|
}
|
|
|
|
// note: this test should raise an error under "by value" GuardedBy semantics, but not under
|
|
// "by reference" GuardedBy semantics
|
|
void readGFromCopyOk() {
|
|
synchronized (this) {
|
|
mCopyOfG = g; // these are ok: access of g guarded by this
|
|
g.toString();
|
|
}
|
|
mCopyOfG.toString(); // should be an error; unprotected access to pt(g)
|
|
}
|
|
|
|
// another "by reference" vs "by value" test. buggy in "by value", but safe in "by reference"
|
|
void usePtG() {
|
|
Object ptG = returnPtG();
|
|
ptG.toString();
|
|
}
|
|
|
|
Object byRefTrickyBad() {
|
|
Object local = null;
|
|
synchronized (this) {
|
|
local = g; // we have a local pointer... to pt(G)
|
|
}
|
|
g.toString(); // ...but unsafe access is through g!
|
|
return local;
|
|
}
|
|
|
|
void byRefTrickyOk() {
|
|
Object local = null;
|
|
synchronized (this) {
|
|
local = g; // we have a local pointer... to pt(G)
|
|
}
|
|
local.toString(); // ...but unsafe access is through g!
|
|
}
|
|
|
|
@GuardedBy("ui_thread")
|
|
Object uiThread1;
|
|
|
|
@GuardedBy("ui-thread")
|
|
Object uiThread2;
|
|
|
|
@GuardedBy("uithread")
|
|
Object uiThread3;
|
|
|
|
@GuardedBy("something that's clearly not an expression")
|
|
Object nonExpression;
|
|
|
|
// tests for not reporting false alarms on unrecognized GuardedBy strings
|
|
void accessUnrecognizedGuardedByFieldsOk() {
|
|
uiThread1 = new Object();
|
|
uiThread1.toString();
|
|
uiThread2 = new Object();
|
|
uiThread2.toString();
|
|
uiThread3 = new Object();
|
|
uiThread3.toString();
|
|
nonExpression = new Object();
|
|
nonExpression.toString();
|
|
}
|
|
|
|
// outer class this tests
|
|
@GuardedBy("GuardedByExample.this")
|
|
Object guardedByOuterThis;
|
|
|
|
synchronized void okOuterAccess() {
|
|
guardedByOuterThis = null;
|
|
}
|
|
|
|
// inner class this tests
|
|
private class Inner {
|
|
@GuardedBy("this")
|
|
Object guardedByInnerThis1;
|
|
|
|
@GuardedBy("Inner.this")
|
|
Object guardedByInnerThis2;
|
|
|
|
@GuardedBy("GuardedByExample$Inner.this")
|
|
Object guardedByInnerThis3;
|
|
|
|
@GuardedBy("Inner.class")
|
|
Object guardedByInnerClass1;
|
|
|
|
@GuardedBy("GuardedByExample.Inner.class")
|
|
Object guardedByInnerClass2;
|
|
|
|
@GuardedBy("GuardedByExample$Inner.class")
|
|
Object guardedByInnerClass3;
|
|
|
|
synchronized void okAccess1() {
|
|
guardedByInnerThis1 = null;
|
|
}
|
|
|
|
synchronized void okAccess2() {
|
|
guardedByInnerThis2 = null;
|
|
}
|
|
|
|
synchronized void okAccess3() {
|
|
guardedByInnerThis3 = null;
|
|
}
|
|
|
|
void okInnerClassGuard1() {
|
|
synchronized (Inner.class) {
|
|
guardedByInnerClass1 = new Object();
|
|
guardedByInnerClass2 = new Object();
|
|
guardedByInnerClass3 = new Object();
|
|
}
|
|
}
|
|
|
|
void okInnerClassGuard2() {
|
|
synchronized (GuardedByExample.Inner.class) {
|
|
guardedByInnerClass1 = new Object();
|
|
guardedByInnerClass2 = new Object();
|
|
guardedByInnerClass3 = new Object();
|
|
}
|
|
}
|
|
}
|
|
|
|
// TODO: report on these cases
|
|
/*
|
|
public void unguardedCallSiteBad1() {
|
|
privateUnguardedAccess(); // should warn; lock is not held
|
|
}
|
|
|
|
protected void unguardedCallSiteBad2() {
|
|
privateUnguardedAccess(); // should warn; lock is not held
|
|
}
|
|
|
|
void unguardedCallSiteBad3() {
|
|
privateUnguardedAccess(); // should warn; lock is not held
|
|
}
|
|
*/
|
|
|
|
int n;
|
|
|
|
public void withloop2() {
|
|
synchronized (mLock) {
|
|
for (int i = 0; i <= n; i++) {
|
|
f = 42;
|
|
}
|
|
}
|
|
}
|
|
|
|
public void withoutloop2() {
|
|
synchronized (mLock) {
|
|
f = 42;
|
|
}
|
|
}
|
|
|
|
@GuardedBy("self_reference")
|
|
Object self_reference;
|
|
|
|
void guardedBySelfReferenceOK() {
|
|
synchronized (self_reference) {
|
|
this.self_reference.toString();
|
|
}
|
|
}
|
|
|
|
// TODO: report on this case, or at least a version which writes
|
|
/*
|
|
void guardedBySelfReferenceBad() {
|
|
this.self_reference.toString();
|
|
}
|
|
*/
|
|
|
|
@GuardedBy("itself")
|
|
Object itself_fld;
|
|
|
|
void itselfOK() {
|
|
synchronized (itself_fld) {
|
|
this.itself_fld.toString();
|
|
}
|
|
}
|
|
|
|
// TODO: report on this case, or at least a version which writes
|
|
/*
|
|
void itselfBad() {
|
|
this.itself_fld.toString();
|
|
}
|
|
*/
|
|
|
|
ReadWriteLock mRWL;
|
|
|
|
@GuardedBy("mRWL")
|
|
Integer guardedbymRWL;
|
|
|
|
Integer someOtherInt;
|
|
|
|
void readLockOK() {
|
|
mRWL.readLock().lock();
|
|
someOtherInt = guardedbymRWL;
|
|
mRWL.readLock().unlock();
|
|
}
|
|
|
|
void writeLockOK() {
|
|
mRWL.writeLock().lock();
|
|
guardedbymRWL = 55;
|
|
mRWL.writeLock().unlock();
|
|
}
|
|
|
|
ReentrantReadWriteLock mRRWL;
|
|
|
|
@GuardedBy("mRRWL")
|
|
Integer guardedbymRRWL;
|
|
|
|
void reentrantReadLockOK() {
|
|
mRRWL.readLock().lock();
|
|
someOtherInt = guardedbymRRWL;
|
|
mRRWL.readLock().unlock();
|
|
}
|
|
|
|
void reentrantWriteLockOK() {
|
|
mRRWL.writeLock().lock();
|
|
guardedbymRRWL = 55;
|
|
mRRWL.writeLock().unlock();
|
|
}
|
|
|
|
// TODO: warn on misuse of read/write locks.
|
|
|
|
@GuardedBy("this")
|
|
Integer xForSub;
|
|
|
|
static class Sub extends GuardedByExample {
|
|
|
|
void goodSub1() {
|
|
synchronized (this) {
|
|
xForSub = 22;
|
|
}
|
|
}
|
|
|
|
synchronized void goodSub2() {
|
|
xForSub = 22;
|
|
}
|
|
|
|
void badSub() {
|
|
xForSub = 22;
|
|
}
|
|
}
|
|
|
|
Lock normallock;
|
|
|
|
@GuardedBy("normallock")
|
|
Integer guardedbynl;
|
|
|
|
ReentrantLock reentrantlock;
|
|
|
|
@GuardedBy("reentrantlock")
|
|
Integer guardedbyrel;
|
|
|
|
void goodGuardedByNormalLock() {
|
|
normallock.lock();
|
|
guardedbynl = 22;
|
|
normallock.unlock();
|
|
}
|
|
|
|
void goodTryLockGuardedByNormalLock() {
|
|
if (normallock.tryLock()) {
|
|
guardedbynl = 22;
|
|
normallock.unlock();
|
|
}
|
|
}
|
|
|
|
void goodTryLockGuardedByReentrantLock() {
|
|
if (reentrantlock.tryLock()) {
|
|
guardedbyrel = 44;
|
|
reentrantlock.unlock();
|
|
}
|
|
}
|
|
|
|
void badGuardedByNormalLock() {
|
|
guardedbynl = 22;
|
|
}
|
|
|
|
void badGuardedByReentrantLock() {
|
|
guardedbyrel = 44;
|
|
}
|
|
|
|
static class OtherClassWithLock {
|
|
ReentrantLock lock;
|
|
|
|
@GuardedBy("lock")
|
|
Object guardedByLock;
|
|
|
|
Object otherClassObject;
|
|
|
|
void guardedInSameClassOk() {
|
|
lock.lock();
|
|
guardedByLock = new Object();
|
|
lock.unlock();
|
|
}
|
|
}
|
|
|
|
@GuardedBy("OtherClassWithLock.lock")
|
|
Object guardedByLock1;
|
|
|
|
@GuardedBy("codetoanalyze.java.infer.GuardedByExample$OtherClassWithLock.lock")
|
|
Object guardedByLock2;
|
|
|
|
@GuardedBy("OtherClassWithLock.otherClassObject")
|
|
Object guardedByLock3;
|
|
|
|
OtherClassWithLock otherClass;
|
|
|
|
void guardedByTypeSyntaxOk1() {
|
|
otherClass.lock.lock();
|
|
guardedByLock1 = true;
|
|
guardedByLock2 = true;
|
|
otherClass.lock.unlock();
|
|
}
|
|
|
|
void guardedByTypeSyntaxOk2() {
|
|
synchronized (otherClass.otherClassObject) {
|
|
guardedByLock3 = true;
|
|
}
|
|
}
|
|
|
|
void guardedByTypeSyntaxBad() {
|
|
guardedByLock1 = true;
|
|
guardedByLock2 = true;
|
|
}
|
|
}
|