Capability attributes on a parameter mean one of two unrelated things: on a scoped-lockable parameter they describe the locks the passed scope object holds, while on a function pointer parameter they describe the requirements of the function called through the pointer. Since #191187 allowed the latter, both of the scoped-lockable code paths have been misreading function pointer parameters as scope objects. At a call site, the argument bound to the parameter was translated into a capability and required to be held, so passing a callback that requires a capability was reported as a missing lock named after the callback: ``` void apply(fn_t fn EXCLUSIVE_LOCKS_REQUIRED(mu)); static int my_fn() EXCLUSIVE_LOCKS_REQUIRED(mu); ... apply(my_fn); // warning: requires holding mutex 'fn' ``` In the callee, the same confusion seeded the parameter's capabilities into the function's entry lockset, so they were considered held throughout the body and the calls made through the pointer went unchecked -- the opposite of what the annotation asks for. Skip function pointer parameters in both places; their attributes are already checked at the indirect call sites (by #191187), the same way as for annotated function pointer variables and fields. Assisted-by: Claude Opus 5
8318 lines
215 KiB
C++
8318 lines
215 KiB
C++
// RUN: %clang_cc1 -fsyntax-only -verify -std=c++11 -Wthread-safety -Wthread-safety-pointer -Wthread-safety-beta -Wno-thread-safety-negative -fcxx-exceptions -DUSE_CAPABILITY=0 %s
|
|
// RUN: %clang_cc1 -fsyntax-only -verify -std=c++11 -Wthread-safety -Wthread-safety-pointer -Wthread-safety-beta -Wno-thread-safety-negative -fcxx-exceptions -DUSE_CAPABILITY=1 %s
|
|
// RUN: %clang_cc1 -fsyntax-only -verify -std=c++20 -Wthread-safety -Wthread-safety-pointer -Wthread-safety-beta -Wno-thread-safety-negative -fcxx-exceptions -DUSE_CAPABILITY=0 %s
|
|
// RUN: %clang_cc1 -fsyntax-only -verify -std=c++20 -Wthread-safety -Wthread-safety-pointer -Wthread-safety-beta -Wno-thread-safety-negative -fcxx-exceptions -DUSE_CAPABILITY=1 %s
|
|
|
|
// FIXME: should also run %clang_cc1 -fsyntax-only -verify -Wthread-safety -std=c++11 -Wc++98-compat %s
|
|
// FIXME: should also run %clang_cc1 -fsyntax-only -verify -Wthread-safety %s
|
|
|
|
#include "thread-safety-annotations.h"
|
|
|
|
class LOCKABLE Mutex {
|
|
public:
|
|
void Lock() EXCLUSIVE_LOCK_FUNCTION();
|
|
void ReaderLock() SHARED_LOCK_FUNCTION();
|
|
void Unlock() UNLOCK_FUNCTION();
|
|
void ExclusiveUnlock() EXCLUSIVE_UNLOCK_FUNCTION();
|
|
void ReaderUnlock() SHARED_UNLOCK_FUNCTION();
|
|
bool TryLock() EXCLUSIVE_TRYLOCK_FUNCTION(true);
|
|
bool ReaderTryLock() SHARED_TRYLOCK_FUNCTION(true);
|
|
void LockWhen(const int &cond) EXCLUSIVE_LOCK_FUNCTION();
|
|
|
|
void PromoteShared() SHARED_UNLOCK_FUNCTION() EXCLUSIVE_LOCK_FUNCTION();
|
|
void DemoteExclusive() EXCLUSIVE_UNLOCK_FUNCTION() SHARED_LOCK_FUNCTION();
|
|
|
|
// for negative capabilities
|
|
const Mutex& operator!() const { return *this; }
|
|
|
|
void AssertHeld() ASSERT_EXCLUSIVE_LOCK();
|
|
void AssertReaderHeld() ASSERT_SHARED_LOCK();
|
|
};
|
|
|
|
class SCOPED_LOCKABLE MutexLock {
|
|
public:
|
|
MutexLock(Mutex *mu) EXCLUSIVE_LOCK_FUNCTION(mu);
|
|
MutexLock(Mutex *mu, bool adopt) EXCLUSIVE_LOCKS_REQUIRED(mu);
|
|
~MutexLock() UNLOCK_FUNCTION();
|
|
};
|
|
|
|
class SCOPED_LOCKABLE ReaderMutexLock {
|
|
public:
|
|
ReaderMutexLock(Mutex *mu) SHARED_LOCK_FUNCTION(mu);
|
|
ReaderMutexLock(Mutex *mu, bool adopt) SHARED_LOCKS_REQUIRED(mu);
|
|
~ReaderMutexLock() UNLOCK_FUNCTION();
|
|
};
|
|
|
|
class SCOPED_LOCKABLE ReleasableMutexLock {
|
|
public:
|
|
ReleasableMutexLock(Mutex *mu) EXCLUSIVE_LOCK_FUNCTION(mu);
|
|
~ReleasableMutexLock() UNLOCK_FUNCTION();
|
|
|
|
void Release() UNLOCK_FUNCTION();
|
|
};
|
|
|
|
class SCOPED_LOCKABLE DoubleMutexLock {
|
|
public:
|
|
DoubleMutexLock(Mutex *mu1, Mutex *mu2) EXCLUSIVE_LOCK_FUNCTION(mu1, mu2);
|
|
~DoubleMutexLock() UNLOCK_FUNCTION();
|
|
};
|
|
|
|
template<typename Mu>
|
|
class SCOPED_LOCKABLE TemplateMutexLock {
|
|
public:
|
|
TemplateMutexLock(Mu *mu) EXCLUSIVE_LOCK_FUNCTION(mu);
|
|
~TemplateMutexLock() UNLOCK_FUNCTION();
|
|
};
|
|
|
|
template<typename... Mus>
|
|
class SCOPED_LOCKABLE VariadicMutexLock {
|
|
public:
|
|
VariadicMutexLock(Mus *...mus) EXCLUSIVE_LOCK_FUNCTION(mus...);
|
|
~VariadicMutexLock() UNLOCK_FUNCTION();
|
|
};
|
|
|
|
// The universal lock, written "*", allows checking to be selectively turned
|
|
// off for a particular piece of code.
|
|
void beginNoWarnOnReads() SHARED_LOCK_FUNCTION("*");
|
|
void endNoWarnOnReads() UNLOCK_FUNCTION("*");
|
|
void beginNoWarnOnWrites() EXCLUSIVE_LOCK_FUNCTION("*");
|
|
void endNoWarnOnWrites() UNLOCK_FUNCTION("*");
|
|
|
|
|
|
// For testing handling of smart pointers.
|
|
template<class T>
|
|
class SmartPtr {
|
|
public:
|
|
SmartPtr(T* p) : ptr_(p) { }
|
|
SmartPtr(const SmartPtr<T>& p) : ptr_(p.ptr_) { }
|
|
~SmartPtr();
|
|
|
|
T* get() const { return ptr_; }
|
|
T* operator->() const { return ptr_; }
|
|
T& operator*() const { return *ptr_; }
|
|
T& operator[](int i) const { return ptr_[i]; }
|
|
|
|
private:
|
|
T* ptr_;
|
|
};
|
|
|
|
template<typename T, typename U>
|
|
U& operator->*(const SmartPtr<T>& ptr, U T::*p) { return ptr->*p; }
|
|
|
|
|
|
// For testing destructor calls and cleanup.
|
|
class MyString {
|
|
public:
|
|
MyString(const char* s);
|
|
~MyString();
|
|
};
|
|
|
|
|
|
// For testing operator overloading
|
|
template <class K, class T>
|
|
class MyMap {
|
|
public:
|
|
T& operator[](const K& k);
|
|
};
|
|
|
|
|
|
// For testing handling of containers.
|
|
template <class T>
|
|
class MyContainer {
|
|
public:
|
|
MyContainer();
|
|
|
|
typedef T* iterator;
|
|
typedef const T* const_iterator;
|
|
|
|
T* begin();
|
|
T* end();
|
|
|
|
const T* cbegin();
|
|
const T* cend();
|
|
|
|
T& operator[](int i);
|
|
const T& operator[](int i) const;
|
|
|
|
private:
|
|
T* ptr_;
|
|
};
|
|
|
|
|
|
|
|
Mutex sls_mu;
|
|
|
|
Mutex sls_mu2 __attribute__((acquired_after(sls_mu)));
|
|
int sls_guard_var __attribute__((guarded_var)) = 0;
|
|
int sls_guardby_var __attribute__((guarded_by(sls_mu))) = 0;
|
|
|
|
bool getBool();
|
|
|
|
class MutexWrapper {
|
|
public:
|
|
Mutex mu;
|
|
int x __attribute__((guarded_by(mu)));
|
|
void MyLock() EXCLUSIVE_LOCK_FUNCTION(mu);
|
|
};
|
|
|
|
struct TestingMoreComplexAttributes {
|
|
Mutex lock;
|
|
struct { Mutex lock; } strct;
|
|
union {
|
|
bool a __attribute__((guarded_by(lock)));
|
|
bool b __attribute__((guarded_by(strct.lock)));
|
|
bool *ptr_a __attribute__((pt_guarded_by(lock)));
|
|
bool *ptr_b __attribute__((pt_guarded_by(strct.lock)));
|
|
Mutex lock1 __attribute__((acquired_before(lock))) __attribute__((acquired_before(strct.lock)));
|
|
Mutex lock2 __attribute__((acquired_after(lock))) __attribute__((acquired_after(strct.lock)));
|
|
};
|
|
} more_complex_atttributes;
|
|
|
|
void more_complex_attributes() {
|
|
more_complex_atttributes.a = true; // expected-warning{{writing variable 'a' requires holding mutex 'more_complex_atttributes.lock' exclusively}}
|
|
more_complex_atttributes.b = true; // expected-warning{{writing variable 'b' requires holding mutex 'more_complex_atttributes.strct.lock' exclusively}}
|
|
*more_complex_atttributes.ptr_a = true; // expected-warning{{writing the value pointed to by 'ptr_a' requires holding mutex 'more_complex_atttributes.lock' exclusively}}
|
|
*more_complex_atttributes.ptr_b = true; // expected-warning{{writing the value pointed to by 'ptr_b' requires holding mutex 'more_complex_atttributes.strct.lock' exclusively}}
|
|
|
|
more_complex_atttributes.lock.Lock();
|
|
more_complex_atttributes.lock1.Lock(); // expected-warning{{mutex 'lock1' must be acquired before 'lock'}}
|
|
more_complex_atttributes.lock1.Unlock();
|
|
more_complex_atttributes.lock.Unlock();
|
|
|
|
more_complex_atttributes.lock2.Lock();
|
|
more_complex_atttributes.lock.Lock(); // expected-warning{{mutex 'lock' must be acquired before 'lock2'}}
|
|
more_complex_atttributes.lock.Unlock();
|
|
more_complex_atttributes.lock2.Unlock();
|
|
}
|
|
|
|
MutexWrapper sls_mw;
|
|
|
|
void sls_fun_0() {
|
|
sls_mw.mu.Lock();
|
|
sls_mw.x = 5;
|
|
sls_mw.mu.Unlock();
|
|
}
|
|
|
|
void sls_fun_2() {
|
|
sls_mu.Lock();
|
|
int x = sls_guard_var;
|
|
sls_mu.Unlock();
|
|
}
|
|
|
|
void sls_fun_3() {
|
|
sls_mu.Lock();
|
|
sls_guard_var = 2;
|
|
sls_mu.Unlock();
|
|
}
|
|
|
|
void sls_fun_4() {
|
|
sls_mu2.Lock();
|
|
sls_guard_var = 2;
|
|
sls_mu2.Unlock();
|
|
}
|
|
|
|
void sls_fun_5() {
|
|
sls_mu.Lock();
|
|
int x = sls_guardby_var;
|
|
sls_mu.Unlock();
|
|
}
|
|
|
|
void sls_fun_6() {
|
|
sls_mu.Lock();
|
|
sls_guardby_var = 2;
|
|
sls_mu.Unlock();
|
|
}
|
|
|
|
void sls_fun_7() {
|
|
sls_mu.Lock();
|
|
sls_mu2.Lock();
|
|
sls_mu2.Unlock();
|
|
sls_mu.Unlock();
|
|
}
|
|
|
|
void sls_fun_8() {
|
|
sls_mu.Lock();
|
|
if (getBool())
|
|
sls_mu.Unlock();
|
|
else
|
|
sls_mu.Unlock();
|
|
}
|
|
|
|
void sls_fun_9() {
|
|
if (getBool())
|
|
sls_mu.Lock();
|
|
else
|
|
sls_mu.Lock();
|
|
sls_mu.Unlock();
|
|
}
|
|
|
|
void sls_fun_good_6() {
|
|
if (getBool()) {
|
|
sls_mu.Lock();
|
|
} else {
|
|
if (getBool()) {
|
|
getBool(); // EMPTY
|
|
} else {
|
|
getBool(); // EMPTY
|
|
}
|
|
sls_mu.Lock();
|
|
}
|
|
sls_mu.Unlock();
|
|
}
|
|
|
|
void sls_fun_good_7() {
|
|
sls_mu.Lock();
|
|
while (getBool()) {
|
|
sls_mu.Unlock();
|
|
if (getBool()) {
|
|
if (getBool()) {
|
|
sls_mu.Lock();
|
|
continue;
|
|
}
|
|
}
|
|
sls_mu.Lock();
|
|
}
|
|
sls_mu.Unlock();
|
|
}
|
|
|
|
void sls_fun_good_8() {
|
|
sls_mw.MyLock();
|
|
sls_mw.mu.Unlock();
|
|
}
|
|
|
|
void sls_fun_bad_1() {
|
|
sls_mu.Unlock(); // \
|
|
// expected-warning{{releasing mutex 'sls_mu' that was not held}}
|
|
}
|
|
|
|
void sls_fun_bad_2() {
|
|
sls_mu.Lock(); // expected-note{{mutex acquired here}}
|
|
sls_mu.Lock(); // \
|
|
// expected-warning{{acquiring mutex 'sls_mu' that is already held}}
|
|
sls_mu.Unlock();
|
|
}
|
|
|
|
void sls_fun_bad_3() {
|
|
sls_mu.Lock(); // expected-note {{mutex acquired here}}
|
|
} // expected-warning{{mutex 'sls_mu' is still held at the end of function}}
|
|
|
|
void sls_fun_bad_4() {
|
|
if (getBool())
|
|
sls_mu.Lock(); // expected-note{{mutex acquired here}}
|
|
else
|
|
sls_mu2.Lock(); // expected-note{{mutex acquired here}}
|
|
} // expected-warning{{mutex 'sls_mu' is not held on every path through here}} \
|
|
// expected-warning{{mutex 'sls_mu2' is not held on every path through here}}
|
|
|
|
void sls_fun_bad_5() {
|
|
sls_mu.Lock(); // expected-note {{mutex acquired here}}
|
|
if (getBool())
|
|
sls_mu.Unlock();
|
|
} // expected-warning{{mutex 'sls_mu' is not held on every path through here}}
|
|
|
|
void sls_fun_bad_6() {
|
|
if (getBool()) {
|
|
sls_mu.Lock(); // expected-note {{mutex acquired here}}
|
|
} else {
|
|
if (getBool()) {
|
|
getBool(); // EMPTY
|
|
} else {
|
|
getBool(); // EMPTY
|
|
}
|
|
}
|
|
sls_mu.Unlock(); // \
|
|
expected-warning{{mutex 'sls_mu' is not held on every path through here}}\
|
|
expected-warning{{releasing mutex 'sls_mu' that was not held}}
|
|
}
|
|
|
|
void sls_fun_bad_7() {
|
|
sls_mu.Lock();
|
|
while (getBool()) { // \
|
|
expected-warning{{expecting mutex 'sls_mu' to be held at start of each loop}}
|
|
sls_mu.Unlock();
|
|
if (getBool()) {
|
|
if (getBool()) {
|
|
continue;
|
|
}
|
|
}
|
|
sls_mu.Lock(); // expected-note {{mutex acquired here}}
|
|
}
|
|
sls_mu.Unlock();
|
|
}
|
|
|
|
void sls_fun_bad_8() {
|
|
sls_mu.Lock(); // expected-note{{mutex acquired here}}
|
|
|
|
do {
|
|
sls_mu.Unlock(); // expected-warning{{expecting mutex 'sls_mu' to be held at start of each loop}}
|
|
} while (getBool());
|
|
}
|
|
|
|
void sls_fun_bad_9() {
|
|
do {
|
|
sls_mu.Lock(); // \
|
|
// expected-warning{{expecting mutex 'sls_mu' to be held at start of each loop}} \
|
|
// expected-note{{mutex acquired here}}
|
|
} while (getBool());
|
|
sls_mu.Unlock();
|
|
}
|
|
|
|
void sls_fun_bad_10() {
|
|
sls_mu.Lock(); // expected-note 2{{mutex acquired here}}
|
|
while(getBool()) { // expected-warning{{expecting mutex 'sls_mu' to be held at start of each loop}}
|
|
sls_mu.Unlock();
|
|
}
|
|
} // expected-warning{{mutex 'sls_mu' is still held at the end of function}}
|
|
|
|
void sls_fun_bad_11() {
|
|
while (getBool()) { // \
|
|
expected-warning{{expecting mutex 'sls_mu' to be held at start of each loop}}
|
|
sls_mu.Lock(); // expected-note {{mutex acquired here}}
|
|
}
|
|
sls_mu.Unlock(); // \
|
|
// expected-warning{{releasing mutex 'sls_mu' that was not held}}
|
|
}
|
|
|
|
void sls_fun_bad_12() {
|
|
sls_mu.Lock(); // expected-note {{mutex acquired here}}
|
|
while (getBool()) {
|
|
sls_mu.Unlock();
|
|
if (getBool()) {
|
|
if (getBool()) {
|
|
break;
|
|
}
|
|
}
|
|
sls_mu.Lock();
|
|
}
|
|
sls_mu.Unlock(); // \
|
|
expected-warning{{mutex 'sls_mu' is not held on every path through here}} \
|
|
expected-warning{{releasing mutex 'sls_mu' that was not held}}
|
|
}
|
|
|
|
//-----------------------------------------//
|
|
// Handling lock expressions in attribute args
|
|
// -------------------------------------------//
|
|
|
|
Mutex aa_mu;
|
|
|
|
class GlobalLocker {
|
|
public:
|
|
void globalLock() EXCLUSIVE_LOCK_FUNCTION(aa_mu);
|
|
void globalUnlock() UNLOCK_FUNCTION(aa_mu);
|
|
};
|
|
|
|
GlobalLocker glock;
|
|
|
|
void aa_fun_1() {
|
|
glock.globalLock();
|
|
glock.globalUnlock();
|
|
}
|
|
|
|
void aa_fun_bad_1() {
|
|
glock.globalUnlock(); // \
|
|
// expected-warning{{releasing mutex 'aa_mu' that was not held}}
|
|
}
|
|
|
|
void aa_fun_bad_2() {
|
|
glock.globalLock(); // expected-note{{mutex acquired here}}
|
|
glock.globalLock(); // \
|
|
// expected-warning{{acquiring mutex 'aa_mu' that is already held}}
|
|
glock.globalUnlock();
|
|
}
|
|
|
|
void aa_fun_bad_3() {
|
|
glock.globalLock(); // expected-note{{mutex acquired here}}
|
|
} // expected-warning{{mutex 'aa_mu' is still held at the end of function}}
|
|
|
|
//--------------------------------------------------//
|
|
// Regression tests for unusual method names
|
|
//--------------------------------------------------//
|
|
|
|
Mutex wmu;
|
|
|
|
// Test diagnostics for other method names.
|
|
class WeirdMethods {
|
|
// FIXME: can't currently check inside constructors and destructors.
|
|
WeirdMethods() {
|
|
wmu.Lock(); // EXPECTED-NOTE {{mutex acquired here}}
|
|
} // EXPECTED-WARNING {{mutex 'wmu' is still held at the end of function}}
|
|
~WeirdMethods() {
|
|
wmu.Lock(); // EXPECTED-NOTE {{mutex acquired here}}
|
|
} // EXPECTED-WARNING {{mutex 'wmu' is still held at the end of function}}
|
|
void operator++() {
|
|
wmu.Lock(); // expected-note {{mutex acquired here}}
|
|
} // expected-warning {{mutex 'wmu' is still held at the end of function}}
|
|
operator int*() {
|
|
wmu.Lock(); // expected-note {{mutex acquired here}}
|
|
return 0;
|
|
} // expected-warning {{mutex 'wmu' is still held at the end of function}}
|
|
};
|
|
|
|
//-----------------------------------------------//
|
|
// Errors for guarded by or guarded var variables
|
|
// ----------------------------------------------//
|
|
|
|
int *pgb_gvar __attribute__((pt_guarded_var));
|
|
int *pgb_var __attribute__((pt_guarded_by(sls_mu)));
|
|
|
|
class PGBFoo {
|
|
public:
|
|
int x;
|
|
int *pgb_field __attribute__((guarded_by(sls_mu2)))
|
|
__attribute__((pt_guarded_by(sls_mu)));
|
|
void testFoo() {
|
|
pgb_field = &x; // \
|
|
// expected-warning {{writing variable 'pgb_field' requires holding mutex 'sls_mu2' exclusively}}
|
|
*pgb_field = x; // expected-warning {{reading variable 'pgb_field' requires holding mutex 'sls_mu2'}} \
|
|
// expected-warning {{writing the value pointed to by 'pgb_field' requires holding mutex 'sls_mu' exclusively}}
|
|
x = *pgb_field; // expected-warning {{reading variable 'pgb_field' requires holding mutex 'sls_mu2'}} \
|
|
// expected-warning {{reading the value pointed to by 'pgb_field' requires holding mutex 'sls_mu'}}
|
|
(*pgb_field)++; // expected-warning {{reading variable 'pgb_field' requires holding mutex 'sls_mu2'}} \
|
|
// expected-warning {{writing the value pointed to by 'pgb_field' requires holding mutex 'sls_mu' exclusively}}
|
|
}
|
|
};
|
|
|
|
class GBFoo {
|
|
public:
|
|
int gb_field __attribute__((guarded_by(sls_mu)));
|
|
|
|
void testFoo() {
|
|
gb_field = 0; // \
|
|
// expected-warning {{writing variable 'gb_field' requires holding mutex 'sls_mu' exclusively}}
|
|
}
|
|
|
|
void testNoAnal() NO_THREAD_SAFETY_ANALYSIS {
|
|
gb_field = 0;
|
|
}
|
|
};
|
|
|
|
GBFoo GlobalGBFoo __attribute__((guarded_by(sls_mu)));
|
|
|
|
void gb_fun_0() {
|
|
sls_mu.Lock();
|
|
int x = *pgb_var;
|
|
sls_mu.Unlock();
|
|
}
|
|
|
|
void gb_fun_1() {
|
|
sls_mu.Lock();
|
|
*pgb_var = 2;
|
|
sls_mu.Unlock();
|
|
}
|
|
|
|
void gb_fun_2() {
|
|
int x;
|
|
pgb_var = &x;
|
|
}
|
|
|
|
void gb_fun_3() {
|
|
int *x = pgb_var;
|
|
}
|
|
|
|
void gb_bad_0() {
|
|
sls_guard_var = 1; // \
|
|
// expected-warning{{writing variable 'sls_guard_var' requires holding any mutex exclusively}}
|
|
}
|
|
|
|
void gb_bad_1() {
|
|
int x = sls_guard_var; // \
|
|
// expected-warning{{reading variable 'sls_guard_var' requires holding any mutex}}
|
|
}
|
|
|
|
void gb_bad_2() {
|
|
sls_guardby_var = 1; // \
|
|
// expected-warning {{writing variable 'sls_guardby_var' requires holding mutex 'sls_mu' exclusively}}
|
|
}
|
|
|
|
void gb_bad_3() {
|
|
int x = sls_guardby_var; // \
|
|
// expected-warning {{reading variable 'sls_guardby_var' requires holding mutex 'sls_mu'}}
|
|
}
|
|
|
|
void gb_bad_4() {
|
|
*pgb_gvar = 1; // \
|
|
// expected-warning {{writing the value pointed to by 'pgb_gvar' requires holding any mutex exclusively}}
|
|
}
|
|
|
|
void gb_bad_5() {
|
|
int x = *pgb_gvar; // \
|
|
// expected-warning {{reading the value pointed to by 'pgb_gvar' requires holding any mutex}}
|
|
}
|
|
|
|
void gb_bad_6() {
|
|
*pgb_var = 1; // \
|
|
// expected-warning {{writing the value pointed to by 'pgb_var' requires holding mutex 'sls_mu' exclusively}}
|
|
}
|
|
|
|
void gb_bad_7() {
|
|
int x = *pgb_var; // \
|
|
// expected-warning {{reading the value pointed to by 'pgb_var' requires holding mutex 'sls_mu'}}
|
|
}
|
|
|
|
void gb_bad_8() {
|
|
GBFoo G;
|
|
G.gb_field = 0; // \
|
|
// expected-warning {{writing variable 'gb_field' requires holding mutex 'sls_mu'}}
|
|
}
|
|
|
|
void gb_bad_9() {
|
|
sls_guard_var++; // \
|
|
// expected-warning{{writing variable 'sls_guard_var' requires holding any mutex exclusively}}
|
|
sls_guard_var--; // \
|
|
// expected-warning{{writing variable 'sls_guard_var' requires holding any mutex exclusively}}
|
|
++sls_guard_var; // \
|
|
// expected-warning{{writing variable 'sls_guard_var' requires holding any mutex exclusively}}
|
|
--sls_guard_var;// \
|
|
// expected-warning{{writing variable 'sls_guard_var' requires holding any mutex exclusively}}
|
|
}
|
|
|
|
//-----------------------------------------------//
|
|
// Warnings on variables with late parsed attributes
|
|
// ----------------------------------------------//
|
|
|
|
class LateFoo {
|
|
public:
|
|
int a __attribute__((guarded_by(mu)));
|
|
int b;
|
|
|
|
void foo() EXCLUSIVE_LOCKS_REQUIRED(mu) { }
|
|
|
|
void test() {
|
|
a = 0; // \
|
|
// expected-warning{{writing variable 'a' requires holding mutex 'mu' exclusively}}
|
|
b = a; // \
|
|
// expected-warning {{reading variable 'a' requires holding mutex 'mu'}}
|
|
c = 0; // \
|
|
// expected-warning {{writing variable 'c' requires holding mutex 'mu' exclusively}}
|
|
}
|
|
|
|
int c __attribute__((guarded_by(mu)));
|
|
|
|
Mutex mu;
|
|
};
|
|
|
|
class LateBar {
|
|
public:
|
|
int a_ __attribute__((guarded_by(mu1_)));
|
|
int b_;
|
|
int *q __attribute__((pt_guarded_by(mu)));
|
|
Mutex mu1_;
|
|
Mutex mu;
|
|
LateFoo Foo;
|
|
LateFoo Foo2;
|
|
LateFoo *FooPointer;
|
|
};
|
|
|
|
LateBar b1, *b3;
|
|
|
|
void late_0() {
|
|
LateFoo FooA;
|
|
LateFoo FooB;
|
|
FooA.mu.Lock();
|
|
FooA.a = 5;
|
|
FooA.mu.Unlock();
|
|
}
|
|
|
|
void late_1() {
|
|
LateBar BarA;
|
|
BarA.FooPointer->mu.Lock();
|
|
BarA.FooPointer->a = 2;
|
|
BarA.FooPointer->mu.Unlock();
|
|
}
|
|
|
|
void late_bad_0() {
|
|
LateFoo fooA;
|
|
LateFoo fooB;
|
|
fooA.mu.Lock();
|
|
fooB.a = 5; // \
|
|
// expected-warning{{writing variable 'a' requires holding mutex 'fooB.mu' exclusively}} \
|
|
// expected-note{{found near match 'fooA.mu'}}
|
|
fooA.mu.Unlock();
|
|
}
|
|
|
|
void late_bad_1() {
|
|
Mutex mu;
|
|
mu.Lock();
|
|
b1.mu1_.Lock();
|
|
int res = b1.a_ + b3->b_;
|
|
b3->b_ = *b1.q; // \
|
|
// expected-warning{{reading the value pointed to by 'q' requires holding mutex 'b1.mu'}}
|
|
b1.mu1_.Unlock();
|
|
b1.b_ = res;
|
|
mu.Unlock();
|
|
}
|
|
|
|
void late_bad_2() {
|
|
LateBar BarA;
|
|
BarA.FooPointer->mu.Lock();
|
|
BarA.Foo.a = 2; // \
|
|
// expected-warning{{writing variable 'a' requires holding mutex 'BarA.Foo.mu' exclusively}} \
|
|
// expected-note{{found near match 'BarA.FooPointer->mu'}}
|
|
BarA.FooPointer->mu.Unlock();
|
|
}
|
|
|
|
void late_bad_3() {
|
|
LateBar BarA;
|
|
BarA.Foo.mu.Lock();
|
|
BarA.FooPointer->a = 2; // \
|
|
// expected-warning{{writing variable 'a' requires holding mutex 'BarA.FooPointer->mu' exclusively}} \
|
|
// expected-note{{found near match 'BarA.Foo.mu'}}
|
|
BarA.Foo.mu.Unlock();
|
|
}
|
|
|
|
void late_bad_4() {
|
|
LateBar BarA;
|
|
BarA.Foo.mu.Lock();
|
|
BarA.Foo2.a = 2; // \
|
|
// expected-warning{{writing variable 'a' requires holding mutex 'BarA.Foo2.mu' exclusively}} \
|
|
// expected-note{{found near match 'BarA.Foo.mu'}}
|
|
BarA.Foo.mu.Unlock();
|
|
}
|
|
|
|
//-----------------------------------------------//
|
|
// Extra warnings for shared vs. exclusive locks
|
|
// ----------------------------------------------//
|
|
|
|
void shared_fun_0() {
|
|
sls_mu.Lock();
|
|
do {
|
|
sls_mu.Unlock();
|
|
sls_mu.Lock();
|
|
} while (getBool());
|
|
sls_mu.Unlock();
|
|
}
|
|
|
|
void shared_fun_1() {
|
|
sls_mu.ReaderLock(); // \
|
|
// expected-note {{the other acquisition of mutex 'sls_mu' is here}}
|
|
do {
|
|
sls_mu.Unlock();
|
|
sls_mu.Lock(); // \
|
|
// expected-warning {{mutex 'sls_mu' is acquired exclusively and shared in the same scope}}
|
|
} while (getBool());
|
|
sls_mu.Unlock();
|
|
}
|
|
|
|
void shared_fun_3() {
|
|
if (getBool())
|
|
sls_mu.Lock();
|
|
else
|
|
sls_mu.Lock();
|
|
*pgb_var = 1;
|
|
sls_mu.Unlock();
|
|
}
|
|
|
|
void shared_fun_4() {
|
|
if (getBool())
|
|
sls_mu.ReaderLock();
|
|
else
|
|
sls_mu.ReaderLock();
|
|
int x = sls_guardby_var;
|
|
sls_mu.Unlock();
|
|
}
|
|
|
|
void shared_fun_8() {
|
|
if (getBool())
|
|
sls_mu.Lock(); // \
|
|
// expected-warning {{mutex 'sls_mu' is acquired exclusively and shared in the same scope}}
|
|
else
|
|
sls_mu.ReaderLock(); // \
|
|
// expected-note {{the other acquisition of mutex 'sls_mu' is here}}
|
|
sls_mu.Unlock();
|
|
}
|
|
|
|
void shared_fun_9() {
|
|
sls_mu.Lock();
|
|
sls_mu.ExclusiveUnlock();
|
|
|
|
sls_mu.ReaderLock();
|
|
sls_mu.ReaderUnlock();
|
|
}
|
|
|
|
void shared_fun_10() {
|
|
sls_mu.Lock();
|
|
sls_mu.DemoteExclusive();
|
|
sls_mu.ReaderUnlock();
|
|
}
|
|
|
|
void shared_fun_11() {
|
|
sls_mu.ReaderLock();
|
|
sls_mu.PromoteShared();
|
|
sls_mu.Unlock();
|
|
}
|
|
|
|
void shared_bad_0() {
|
|
sls_mu.Lock(); // \
|
|
// expected-note {{the other acquisition of mutex 'sls_mu' is here}}
|
|
do {
|
|
sls_mu.Unlock();
|
|
sls_mu.ReaderLock(); // \
|
|
// expected-warning {{mutex 'sls_mu' is acquired exclusively and shared in the same scope}}
|
|
} while (getBool());
|
|
sls_mu.Unlock();
|
|
}
|
|
|
|
void shared_bad_1() {
|
|
if (getBool())
|
|
sls_mu.Lock(); // \
|
|
// expected-warning {{mutex 'sls_mu' is acquired exclusively and shared in the same scope}}
|
|
else
|
|
sls_mu.ReaderLock(); // \
|
|
// expected-note {{the other acquisition of mutex 'sls_mu' is here}}
|
|
*pgb_var = 1;
|
|
sls_mu.Unlock();
|
|
}
|
|
|
|
void shared_bad_2() {
|
|
if (getBool())
|
|
sls_mu.ReaderLock(); // \
|
|
// expected-warning {{mutex 'sls_mu' is acquired exclusively and shared in the same scope}}
|
|
else
|
|
sls_mu.Lock(); // \
|
|
// expected-note {{the other acquisition of mutex 'sls_mu' is here}}
|
|
*pgb_var = 1;
|
|
sls_mu.Unlock();
|
|
}
|
|
|
|
void shared_bad_3() {
|
|
sls_mu.Lock(); // expected-note {{mutex acquired here}}
|
|
sls_mu.ReaderUnlock(); // \
|
|
// expected-warning {{releasing mutex 'sls_mu' using shared access, expected exclusive access}}
|
|
}
|
|
|
|
void shared_bad_4() {
|
|
sls_mu.ReaderLock(); // expected-note {{mutex acquired here}}
|
|
sls_mu.ExclusiveUnlock(); // \
|
|
// expected-warning {{releasing mutex 'sls_mu' using exclusive access, expected shared access}}
|
|
}
|
|
|
|
void shared_bad_5() {
|
|
sls_mu.Lock(); // expected-note {{mutex acquired here}}
|
|
sls_mu.PromoteShared(); // \
|
|
// expected-warning {{releasing mutex 'sls_mu' using shared access, expected exclusive access}}
|
|
sls_mu.ExclusiveUnlock();
|
|
}
|
|
|
|
void shared_bad_6() {
|
|
sls_mu.ReaderLock(); // expected-note {{mutex acquired here}}
|
|
sls_mu.DemoteExclusive(); // \
|
|
// expected-warning {{releasing mutex 'sls_mu' using exclusive access, expected shared access}}
|
|
sls_mu.ReaderUnlock();
|
|
}
|
|
|
|
// FIXME: Add support for functions (not only methods)
|
|
class LRBar {
|
|
public:
|
|
void aa_elr_fun() EXCLUSIVE_LOCKS_REQUIRED(aa_mu);
|
|
void aa_elr_fun_s() SHARED_LOCKS_REQUIRED(aa_mu);
|
|
void le_fun() __attribute__((locks_excluded(sls_mu)));
|
|
};
|
|
|
|
class LRFoo {
|
|
public:
|
|
void test() EXCLUSIVE_LOCKS_REQUIRED(sls_mu);
|
|
void testShared() SHARED_LOCKS_REQUIRED(sls_mu2);
|
|
};
|
|
|
|
void elr_fun() EXCLUSIVE_LOCKS_REQUIRED(sls_mu);
|
|
void elr_fun() {}
|
|
|
|
LRFoo MyLRFoo;
|
|
LRBar Bar;
|
|
|
|
void es_fun_0() {
|
|
aa_mu.Lock();
|
|
Bar.aa_elr_fun();
|
|
aa_mu.Unlock();
|
|
}
|
|
|
|
void es_fun_1() {
|
|
aa_mu.Lock();
|
|
Bar.aa_elr_fun_s();
|
|
aa_mu.Unlock();
|
|
}
|
|
|
|
void es_fun_2() {
|
|
aa_mu.ReaderLock();
|
|
Bar.aa_elr_fun_s();
|
|
aa_mu.Unlock();
|
|
}
|
|
|
|
void es_fun_3() {
|
|
sls_mu.Lock();
|
|
MyLRFoo.test();
|
|
sls_mu.Unlock();
|
|
}
|
|
|
|
void es_fun_4() {
|
|
sls_mu2.Lock();
|
|
MyLRFoo.testShared();
|
|
sls_mu2.Unlock();
|
|
}
|
|
|
|
void es_fun_5() {
|
|
sls_mu2.ReaderLock();
|
|
MyLRFoo.testShared();
|
|
sls_mu2.Unlock();
|
|
}
|
|
|
|
void es_fun_6() {
|
|
Bar.le_fun();
|
|
}
|
|
|
|
void es_fun_7() {
|
|
sls_mu.Lock();
|
|
elr_fun();
|
|
sls_mu.Unlock();
|
|
}
|
|
|
|
void es_fun_8() NO_THREAD_SAFETY_ANALYSIS;
|
|
|
|
void es_fun_8() {
|
|
Bar.aa_elr_fun_s();
|
|
}
|
|
|
|
void es_fun_9() SHARED_LOCKS_REQUIRED(aa_mu);
|
|
void es_fun_9() {
|
|
Bar.aa_elr_fun_s();
|
|
}
|
|
|
|
void es_fun_10() EXCLUSIVE_LOCKS_REQUIRED(aa_mu);
|
|
void es_fun_10() {
|
|
Bar.aa_elr_fun_s();
|
|
}
|
|
|
|
void es_bad_0() {
|
|
Bar.aa_elr_fun(); // \
|
|
// expected-warning {{calling function 'aa_elr_fun' requires holding mutex 'aa_mu' exclusively}}
|
|
}
|
|
|
|
void es_bad_1() {
|
|
aa_mu.ReaderLock();
|
|
Bar.aa_elr_fun(); // \
|
|
// expected-warning {{calling function 'aa_elr_fun' requires holding mutex 'aa_mu' exclusively}}
|
|
aa_mu.Unlock();
|
|
}
|
|
|
|
void es_bad_2() {
|
|
Bar.aa_elr_fun_s(); // \
|
|
// expected-warning {{calling function 'aa_elr_fun_s' requires holding mutex 'aa_mu'}}
|
|
}
|
|
|
|
void es_bad_3() {
|
|
MyLRFoo.test(); // \
|
|
// expected-warning {{calling function 'test' requires holding mutex 'sls_mu' exclusively}}
|
|
}
|
|
|
|
void es_bad_4() {
|
|
MyLRFoo.testShared(); // \
|
|
// expected-warning {{calling function 'testShared' requires holding mutex 'sls_mu2'}}
|
|
}
|
|
|
|
void es_bad_5() {
|
|
sls_mu.ReaderLock();
|
|
MyLRFoo.test(); // \
|
|
// expected-warning {{calling function 'test' requires holding mutex 'sls_mu' exclusively}}
|
|
sls_mu.Unlock();
|
|
}
|
|
|
|
void es_bad_6() {
|
|
sls_mu.Lock();
|
|
Bar.le_fun(); // \
|
|
// expected-warning {{cannot call function 'le_fun' while mutex 'sls_mu' is held}}
|
|
sls_mu.Unlock();
|
|
}
|
|
|
|
void es_bad_7() {
|
|
sls_mu.ReaderLock();
|
|
Bar.le_fun(); // \
|
|
// expected-warning {{cannot call function 'le_fun' while mutex 'sls_mu' is held}}
|
|
sls_mu.Unlock();
|
|
}
|
|
|
|
|
|
//-----------------------------------------------//
|
|
// Unparseable lock expressions
|
|
// ----------------------------------------------//
|
|
|
|
// FIXME -- derive new tests for unhandled expressions
|
|
|
|
|
|
//----------------------------------------------------------------------------//
|
|
// The following test cases are ported from the gcc thread safety implementation
|
|
// They are each wrapped inside a namespace with the test number of the gcc test
|
|
//
|
|
// FIXME: add all the gcc tests, once this analysis passes them.
|
|
//----------------------------------------------------------------------------//
|
|
|
|
//-----------------------------------------//
|
|
// Good testcases (no errors)
|
|
//-----------------------------------------//
|
|
|
|
namespace thread_annot_lock_20 {
|
|
class Bar {
|
|
public:
|
|
static int func1() EXCLUSIVE_LOCKS_REQUIRED(mu1_);
|
|
static int b_ GUARDED_BY(mu1_);
|
|
static Mutex mu1_;
|
|
static int a_ GUARDED_BY(mu1_);
|
|
};
|
|
|
|
Bar b1;
|
|
|
|
int Bar::func1()
|
|
{
|
|
int res = 5;
|
|
|
|
if (a_ == 4)
|
|
res = b_;
|
|
return res;
|
|
}
|
|
} // end namespace thread_annot_lock_20
|
|
|
|
namespace thread_annot_lock_22 {
|
|
// Test various usage of GUARDED_BY and PT_GUARDED_BY annotations, especially
|
|
// uses in class definitions.
|
|
Mutex mu;
|
|
|
|
class Bar {
|
|
public:
|
|
int a_ GUARDED_BY(mu1_);
|
|
int b_;
|
|
int *q PT_GUARDED_BY(mu);
|
|
Mutex mu1_ ACQUIRED_AFTER(mu);
|
|
};
|
|
|
|
Bar b1, *b3;
|
|
int *p GUARDED_BY(mu) PT_GUARDED_BY(mu);
|
|
int res GUARDED_BY(mu) = 5;
|
|
|
|
int func(int i)
|
|
{
|
|
int x;
|
|
mu.Lock();
|
|
b1.mu1_.Lock();
|
|
res = b1.a_ + b3->b_;
|
|
*p = i;
|
|
b1.a_ = res + b3->b_;
|
|
b3->b_ = *b1.q;
|
|
b1.mu1_.Unlock();
|
|
b1.b_ = res;
|
|
x = res;
|
|
mu.Unlock();
|
|
return x;
|
|
}
|
|
} // end namespace thread_annot_lock_22
|
|
|
|
namespace thread_annot_lock_27_modified {
|
|
// test lock annotations applied to function definitions
|
|
// Modified: applied annotations only to function declarations
|
|
Mutex mu1;
|
|
Mutex mu2 ACQUIRED_AFTER(mu1);
|
|
|
|
class Foo {
|
|
public:
|
|
int method1(int i) SHARED_LOCKS_REQUIRED(mu2) EXCLUSIVE_LOCKS_REQUIRED(mu1);
|
|
};
|
|
|
|
int Foo::method1(int i) {
|
|
return i;
|
|
}
|
|
|
|
|
|
int foo(int i) EXCLUSIVE_LOCKS_REQUIRED(mu2) SHARED_LOCKS_REQUIRED(mu1);
|
|
int foo(int i) {
|
|
return i;
|
|
}
|
|
|
|
static int bar(int i) EXCLUSIVE_LOCKS_REQUIRED(mu1);
|
|
static int bar(int i) {
|
|
return i;
|
|
}
|
|
|
|
void main() {
|
|
Foo a;
|
|
|
|
mu1.Lock();
|
|
mu2.Lock();
|
|
a.method1(1);
|
|
foo(2);
|
|
mu2.Unlock();
|
|
bar(3);
|
|
mu1.Unlock();
|
|
}
|
|
} // end namespace thread_annot_lock_27_modified
|
|
|
|
|
|
namespace thread_annot_lock_38 {
|
|
// Test the case where a template member function is annotated with lock
|
|
// attributes in a non-template class.
|
|
class Foo {
|
|
public:
|
|
void func1(int y) LOCKS_EXCLUDED(mu_);
|
|
template <typename T> void func2(T x) LOCKS_EXCLUDED(mu_);
|
|
private:
|
|
Mutex mu_;
|
|
};
|
|
|
|
Foo *foo;
|
|
|
|
void main()
|
|
{
|
|
foo->func1(5);
|
|
foo->func2(5);
|
|
}
|
|
} // end namespace thread_annot_lock_38
|
|
|
|
namespace thread_annot_lock_43 {
|
|
// Tests lock canonicalization
|
|
class Foo {
|
|
public:
|
|
Mutex *mu_;
|
|
};
|
|
|
|
class FooBar {
|
|
public:
|
|
Foo *foo_;
|
|
int GetA() EXCLUSIVE_LOCKS_REQUIRED(foo_->mu_) { return a_; }
|
|
int a_ GUARDED_BY(foo_->mu_);
|
|
};
|
|
|
|
FooBar *fb;
|
|
|
|
void main()
|
|
{
|
|
int x;
|
|
fb->foo_->mu_->Lock();
|
|
x = fb->GetA();
|
|
fb->foo_->mu_->Unlock();
|
|
}
|
|
} // end namespace thread_annot_lock_43
|
|
|
|
namespace thread_annot_lock_49 {
|
|
// Test the support for use of lock expression in the annotations
|
|
class Foo {
|
|
public:
|
|
Mutex foo_mu_;
|
|
};
|
|
|
|
class Bar {
|
|
private:
|
|
Foo *foo;
|
|
Mutex bar_mu_ ACQUIRED_AFTER(foo->foo_mu_);
|
|
|
|
public:
|
|
void Test1() {
|
|
foo->foo_mu_.Lock();
|
|
bar_mu_.Lock();
|
|
bar_mu_.Unlock();
|
|
foo->foo_mu_.Unlock();
|
|
}
|
|
};
|
|
|
|
void main() {
|
|
Bar bar;
|
|
bar.Test1();
|
|
}
|
|
} // end namespace thread_annot_lock_49
|
|
|
|
namespace thread_annot_lock_61_modified {
|
|
// Modified to fix the compiler errors
|
|
// Test the fix for a bug introduced by the support of pass-by-reference
|
|
// parameters.
|
|
struct Foo { Foo &operator<< (bool) {return *this;} };
|
|
Foo &getFoo();
|
|
struct Bar { Foo &func () {return getFoo();} };
|
|
struct Bas { void operator& (Foo &) {} };
|
|
void mumble()
|
|
{
|
|
Bas() & Bar().func() << "" << "";
|
|
Bas() & Bar().func() << "";
|
|
}
|
|
} // end namespace thread_annot_lock_61_modified
|
|
|
|
|
|
namespace thread_annot_lock_65 {
|
|
// Test the fix for a bug in the support of allowing reader locks for
|
|
// non-const, non-modifying overload functions. (We didn't handle the builtin
|
|
// properly.)
|
|
enum MyFlags {
|
|
Zero,
|
|
One,
|
|
Two,
|
|
Three,
|
|
Four,
|
|
Five,
|
|
Six,
|
|
Seven,
|
|
Eight,
|
|
Nine
|
|
};
|
|
|
|
inline MyFlags
|
|
operator|(MyFlags a, MyFlags b)
|
|
{
|
|
return MyFlags(static_cast<int>(a) | static_cast<int>(b));
|
|
}
|
|
|
|
inline MyFlags&
|
|
operator|=(MyFlags& a, MyFlags b)
|
|
{
|
|
return a = a | b;
|
|
}
|
|
} // end namespace thread_annot_lock_65
|
|
|
|
namespace thread_annot_lock_66_modified {
|
|
// Modified: Moved annotation to function defn
|
|
// Test annotations on out-of-line definitions of member functions where the
|
|
// annotations refer to locks that are also data members in the class.
|
|
Mutex mu;
|
|
|
|
class Foo {
|
|
public:
|
|
int method1(int i) SHARED_LOCKS_REQUIRED(mu1, mu, mu2);
|
|
int data GUARDED_BY(mu1);
|
|
Mutex *mu1;
|
|
Mutex *mu2;
|
|
};
|
|
|
|
int Foo::method1(int i)
|
|
{
|
|
return data + i;
|
|
}
|
|
|
|
void main()
|
|
{
|
|
Foo a;
|
|
|
|
a.mu2->Lock();
|
|
a.mu1->Lock();
|
|
mu.Lock();
|
|
a.method1(1);
|
|
mu.Unlock();
|
|
a.mu1->Unlock();
|
|
a.mu2->Unlock();
|
|
}
|
|
} // end namespace thread_annot_lock_66_modified
|
|
|
|
namespace thread_annot_lock_68_modified {
|
|
// Test a fix to a bug in the delayed name binding with nested template
|
|
// instantiation. We use a stack to make sure a name is not resolved to an
|
|
// inner context.
|
|
template <typename T>
|
|
class Bar {
|
|
Mutex mu_;
|
|
};
|
|
|
|
template <typename T>
|
|
class Foo {
|
|
public:
|
|
void func(T x) {
|
|
mu_.Lock();
|
|
count_ = x;
|
|
mu_.Unlock();
|
|
}
|
|
|
|
private:
|
|
T count_ GUARDED_BY(mu_);
|
|
Bar<T> bar_;
|
|
Mutex mu_;
|
|
};
|
|
|
|
void main()
|
|
{
|
|
Foo<int> *foo;
|
|
foo->func(5);
|
|
}
|
|
} // end namespace thread_annot_lock_68_modified
|
|
|
|
namespace thread_annot_lock_30_modified {
|
|
// Test delay parsing of lock attribute arguments with nested classes.
|
|
// Modified: trylocks replaced with exclusive_lock_fun
|
|
int a = 0;
|
|
|
|
class Bar {
|
|
struct Foo;
|
|
|
|
public:
|
|
void MyLock() EXCLUSIVE_LOCK_FUNCTION(mu);
|
|
|
|
int func() {
|
|
MyLock();
|
|
// if (foo == 0) {
|
|
// return 0;
|
|
// }
|
|
a = 5;
|
|
mu.Unlock();
|
|
return 1;
|
|
}
|
|
|
|
class FooBar {
|
|
int x;
|
|
int y;
|
|
};
|
|
|
|
private:
|
|
Mutex mu;
|
|
};
|
|
|
|
Bar *bar;
|
|
|
|
void main()
|
|
{
|
|
bar->func();
|
|
}
|
|
} // end namespace thread_annot_lock_30_modified
|
|
|
|
namespace thread_annot_lock_47 {
|
|
// Test the support for annotations on virtual functions.
|
|
// This is a good test case. (i.e. There should be no warning emitted by the
|
|
// compiler.)
|
|
class Base {
|
|
public:
|
|
virtual void func1() EXCLUSIVE_LOCKS_REQUIRED(mu_);
|
|
virtual void func2() LOCKS_EXCLUDED(mu_);
|
|
Mutex mu_;
|
|
};
|
|
|
|
class Child : public Base {
|
|
public:
|
|
virtual void func1() EXCLUSIVE_LOCKS_REQUIRED(mu_);
|
|
virtual void func2() LOCKS_EXCLUDED(mu_);
|
|
};
|
|
|
|
void main() {
|
|
Child *c;
|
|
Base *b = c;
|
|
|
|
b->mu_.Lock();
|
|
b->func1();
|
|
b->mu_.Unlock();
|
|
b->func2();
|
|
|
|
c->mu_.Lock();
|
|
c->func1();
|
|
c->mu_.Unlock();
|
|
c->func2();
|
|
}
|
|
} // end namespace thread_annot_lock_47
|
|
|
|
//-----------------------------------------//
|
|
// Tests which produce errors
|
|
//-----------------------------------------//
|
|
|
|
namespace thread_annot_lock_13 {
|
|
Mutex mu1;
|
|
Mutex mu2;
|
|
|
|
int g GUARDED_BY(mu1);
|
|
int w GUARDED_BY(mu2);
|
|
|
|
class Foo {
|
|
public:
|
|
void bar() LOCKS_EXCLUDED(mu_, mu1);
|
|
int foo() SHARED_LOCKS_REQUIRED(mu_) EXCLUSIVE_LOCKS_REQUIRED(mu2);
|
|
|
|
private:
|
|
int a_ GUARDED_BY(mu_);
|
|
public:
|
|
Mutex mu_ ACQUIRED_AFTER(mu1);
|
|
};
|
|
|
|
int Foo::foo()
|
|
{
|
|
int res;
|
|
w = 5;
|
|
res = a_ + 5;
|
|
return res;
|
|
}
|
|
|
|
void Foo::bar()
|
|
{
|
|
int x;
|
|
mu_.Lock();
|
|
x = foo(); // expected-warning {{calling function 'foo' requires holding mutex 'mu2' exclusively}}
|
|
a_ = x + 1;
|
|
mu_.Unlock();
|
|
if (x > 5) {
|
|
mu1.Lock();
|
|
g = 2;
|
|
mu1.Unlock();
|
|
}
|
|
}
|
|
|
|
void main()
|
|
{
|
|
Foo f1, *f2;
|
|
f1.mu_.Lock();
|
|
f1.bar(); // expected-warning {{cannot call function 'bar' while mutex 'f1.mu_' is held}}
|
|
mu2.Lock();
|
|
f1.foo();
|
|
mu2.Unlock();
|
|
f1.mu_.Unlock();
|
|
f2->mu_.Lock();
|
|
f2->bar(); // expected-warning {{cannot call function 'bar' while mutex 'f2->mu_' is held}}
|
|
f2->mu_.Unlock();
|
|
mu2.Lock();
|
|
w = 2;
|
|
mu2.Unlock();
|
|
}
|
|
} // end namespace thread_annot_lock_13
|
|
|
|
namespace thread_annot_lock_18_modified {
|
|
// Modified: Trylocks removed
|
|
// Test the ability to distnguish between the same lock field of
|
|
// different objects of a class.
|
|
class Bar {
|
|
public:
|
|
bool MyLock() EXCLUSIVE_LOCK_FUNCTION(mu1_);
|
|
void MyUnlock() UNLOCK_FUNCTION(mu1_);
|
|
int a_ GUARDED_BY(mu1_);
|
|
|
|
private:
|
|
Mutex mu1_;
|
|
};
|
|
|
|
Bar *b1, *b2;
|
|
|
|
void func()
|
|
{
|
|
b1->MyLock();
|
|
b1->a_ = 5;
|
|
b2->a_ = 3; // \
|
|
// expected-warning {{writing variable 'a_' requires holding mutex 'b2->mu1_' exclusively}} \
|
|
// expected-note {{found near match 'b1->mu1_'}}
|
|
b2->MyLock();
|
|
b2->MyUnlock();
|
|
b1->MyUnlock();
|
|
}
|
|
} // end namespace thread_annot_lock_18_modified
|
|
|
|
namespace thread_annot_lock_21 {
|
|
// Test various usage of GUARDED_BY and PT_GUARDED_BY annotations, especially
|
|
// uses in class definitions.
|
|
Mutex mu;
|
|
|
|
class Bar {
|
|
public:
|
|
int a_ GUARDED_BY(mu1_);
|
|
int b_;
|
|
int *q PT_GUARDED_BY(mu);
|
|
Mutex mu1_ ACQUIRED_AFTER(mu);
|
|
};
|
|
|
|
Bar b1, *b3;
|
|
int *p GUARDED_BY(mu) PT_GUARDED_BY(mu);
|
|
|
|
int res GUARDED_BY(mu) = 5;
|
|
|
|
int func(int i)
|
|
{
|
|
int x;
|
|
b3->mu1_.Lock();
|
|
res = b1.a_ + b3->b_; // expected-warning {{reading variable 'a_' requires holding mutex 'b1.mu1_'}} \
|
|
// expected-warning {{writing variable 'res' requires holding mutex 'mu' exclusively}} \
|
|
// expected-note {{found near match 'b3->mu1_'}}
|
|
*p = i; // expected-warning {{reading variable 'p' requires holding mutex 'mu'}} \
|
|
// expected-warning {{writing the value pointed to by 'p' requires holding mutex 'mu' exclusively}}
|
|
b1.a_ = res + b3->b_; // expected-warning {{reading variable 'res' requires holding mutex 'mu'}} \
|
|
// expected-warning {{writing variable 'a_' requires holding mutex 'b1.mu1_' exclusively}} \
|
|
// expected-note {{found near match 'b3->mu1_'}}
|
|
b3->b_ = *b1.q; // expected-warning {{reading the value pointed to by 'q' requires holding mutex 'mu'}}
|
|
b3->mu1_.Unlock();
|
|
b1.b_ = res; // expected-warning {{reading variable 'res' requires holding mutex 'mu'}}
|
|
x = res; // expected-warning {{reading variable 'res' requires holding mutex 'mu'}}
|
|
return x;
|
|
}
|
|
} // end namespace thread_annot_lock_21
|
|
|
|
namespace thread_annot_lock_35_modified {
|
|
// Test the analyzer's ability to distinguish the lock field of different
|
|
// objects.
|
|
class Foo {
|
|
private:
|
|
Mutex lock_;
|
|
int a_ GUARDED_BY(lock_);
|
|
|
|
public:
|
|
void Func(Foo* child) LOCKS_EXCLUDED(lock_) {
|
|
Foo *new_foo = new Foo;
|
|
|
|
lock_.Lock();
|
|
|
|
child->Func(new_foo); // There shouldn't be any warning here as the
|
|
// acquired lock is not in child.
|
|
child->bar(7); // \
|
|
// expected-warning {{calling function 'bar' requires holding mutex 'child->lock_' exclusively}} \
|
|
// expected-note {{found near match 'lock_'}}
|
|
child->a_ = 5; // \
|
|
// expected-warning {{writing variable 'a_' requires holding mutex 'child->lock_' exclusively}} \
|
|
// expected-note {{found near match 'lock_'}}
|
|
lock_.Unlock();
|
|
}
|
|
|
|
void bar(int y) EXCLUSIVE_LOCKS_REQUIRED(lock_) {
|
|
a_ = y;
|
|
}
|
|
};
|
|
|
|
Foo *x;
|
|
|
|
void main() {
|
|
Foo *child = new Foo;
|
|
x->Func(child);
|
|
}
|
|
} // end namespace thread_annot_lock_35_modified
|
|
|
|
namespace thread_annot_lock_36_modified {
|
|
// Modified to move the annotations to function defns.
|
|
// Test the analyzer's ability to distinguish the lock field of different
|
|
// objects
|
|
class Foo {
|
|
private:
|
|
Mutex lock_;
|
|
int a_ GUARDED_BY(lock_);
|
|
|
|
public:
|
|
void Func(Foo* child) LOCKS_EXCLUDED(lock_);
|
|
void bar(int y) EXCLUSIVE_LOCKS_REQUIRED(lock_);
|
|
};
|
|
|
|
void Foo::Func(Foo* child) {
|
|
Foo *new_foo = new Foo;
|
|
|
|
lock_.Lock();
|
|
|
|
child->lock_.Lock();
|
|
child->Func(new_foo); // expected-warning {{cannot call function 'Func' while mutex 'child->lock_' is held}}
|
|
child->bar(7);
|
|
child->a_ = 5;
|
|
child->lock_.Unlock();
|
|
|
|
lock_.Unlock();
|
|
}
|
|
|
|
void Foo::bar(int y) {
|
|
a_ = y;
|
|
}
|
|
|
|
|
|
Foo *x;
|
|
|
|
void main() {
|
|
Foo *child = new Foo;
|
|
x->Func(child);
|
|
}
|
|
} // end namespace thread_annot_lock_36_modified
|
|
|
|
|
|
namespace thread_annot_lock_42 {
|
|
// Test support of multiple lock attributes of the same kind on a decl.
|
|
class Foo {
|
|
private:
|
|
Mutex mu1, mu2, mu3;
|
|
int x GUARDED_BY(mu1) GUARDED_BY(mu2);
|
|
int y GUARDED_BY(mu2);
|
|
|
|
void f2() LOCKS_EXCLUDED(mu1) LOCKS_EXCLUDED(mu2) LOCKS_EXCLUDED(mu3) {
|
|
mu2.Lock();
|
|
y = 2;
|
|
mu2.Unlock();
|
|
}
|
|
|
|
public:
|
|
void f1() EXCLUSIVE_LOCKS_REQUIRED(mu2) EXCLUSIVE_LOCKS_REQUIRED(mu1) {
|
|
x = 5;
|
|
f2(); // expected-warning {{cannot call function 'f2' while mutex 'mu1' is held}} \
|
|
// expected-warning {{cannot call function 'f2' while mutex 'mu2' is held}}
|
|
}
|
|
|
|
// Holding only mu1 (but not mu2) is insufficient to access x.
|
|
void f3() EXCLUSIVE_LOCKS_REQUIRED(mu1) {
|
|
x = 5; // expected-warning {{writing variable 'x' requires holding mutex 'mu2' exclusively}}
|
|
}
|
|
|
|
// Holding only mu2 (but not mu1) is insufficient to access x.
|
|
void f4() EXCLUSIVE_LOCKS_REQUIRED(mu2) {
|
|
x = 5; // expected-warning {{writing variable 'x' requires holding mutex 'mu1' exclusively}}
|
|
}
|
|
};
|
|
|
|
Foo *foo;
|
|
|
|
void func()
|
|
{
|
|
foo->f1(); // expected-warning {{calling function 'f1' requires holding mutex 'foo->mu2' exclusively}} \
|
|
// expected-warning {{calling function 'f1' requires holding mutex 'foo->mu1' exclusively}}
|
|
}
|
|
} // end namespace thread_annot_lock_42
|
|
|
|
namespace guarded_by_multi {
|
|
// Test multi-arg GUARDED_BY: any one capability suffices for read; all are
|
|
// required for write.
|
|
class Foo {
|
|
Mutex mu1, mu2;
|
|
int x GUARDED_BY(mu1, mu2);
|
|
int *p PT_GUARDED_BY(mu1, mu2);
|
|
|
|
// All locks held exclusively: read and write both OK.
|
|
void f1() EXCLUSIVE_LOCKS_REQUIRED(mu1, mu2) {
|
|
x = 1;
|
|
*p = 1;
|
|
}
|
|
|
|
// Only mu1 held exclusively: read OK, write warns about mu2.
|
|
void f2() EXCLUSIVE_LOCKS_REQUIRED(mu1) {
|
|
int y = x;
|
|
int z = *p;
|
|
x = 1; // expected-warning {{writing variable 'x' requires holding mutex 'mu2' exclusively}}
|
|
*p = 1; // expected-warning {{writing the value pointed to by 'p' requires holding mutex 'mu2' exclusively}}
|
|
}
|
|
|
|
// One lock held shared: read OK, write warns about both.
|
|
void f3() SHARED_LOCKS_REQUIRED(mu1) {
|
|
int y = x;
|
|
int z = *p;
|
|
x = 1; // expected-warning {{writing variable 'x' requires holding mutex 'mu1' exclusively}} \
|
|
// expected-warning {{writing variable 'x' requires holding mutex 'mu2' exclusively}}
|
|
*p = 1; // expected-warning {{writing the value pointed to by 'p' requires holding mutex 'mu1' exclusively}} \
|
|
// expected-warning {{writing the value pointed to by 'p' requires holding mutex 'mu2' exclusively}}
|
|
}
|
|
|
|
// No locks held: read and write both warn.
|
|
void f4() {
|
|
int y = x; // expected-warning {{reading variable 'x' requires holding at least one of 'mu1', 'mu2'}}
|
|
int z = *p; // expected-warning {{reading the value pointed to by 'p' requires holding at least one of 'mu1', 'mu2'}}
|
|
x = 1; // expected-warning {{writing variable 'x' requires holding mutex 'mu1' exclusively}} \
|
|
// expected-warning {{writing variable 'x' requires holding mutex 'mu2' exclusively}}
|
|
*p = 1; // expected-warning {{writing the value pointed to by 'p' requires holding mutex 'mu1' exclusively}} \
|
|
// expected-warning {{writing the value pointed to by 'p' requires holding mutex 'mu2' exclusively}}
|
|
}
|
|
};
|
|
} // end namespace guarded_by_multi
|
|
|
|
namespace thread_annot_lock_46 {
|
|
// Test the support for annotations on virtual functions.
|
|
class Base {
|
|
public:
|
|
virtual void func1() EXCLUSIVE_LOCKS_REQUIRED(mu_);
|
|
virtual void func2() LOCKS_EXCLUDED(mu_);
|
|
Mutex mu_;
|
|
};
|
|
|
|
class Child : public Base {
|
|
public:
|
|
virtual void func1() EXCLUSIVE_LOCKS_REQUIRED(mu_);
|
|
virtual void func2() LOCKS_EXCLUDED(mu_);
|
|
};
|
|
|
|
void main() {
|
|
Child *c;
|
|
Base *b = c;
|
|
|
|
b->func1(); // expected-warning {{calling function 'func1' requires holding mutex 'c->mu_' exclusively}}
|
|
b->mu_.Lock();
|
|
b->func2(); // expected-warning {{cannot call function 'func2' while mutex 'c->mu_' is held}}
|
|
b->mu_.Unlock();
|
|
|
|
c->func1(); // expected-warning {{calling function 'func1' requires holding mutex 'c->mu_' exclusively}}
|
|
c->mu_.Lock();
|
|
c->func2(); // expected-warning {{cannot call function 'func2' while mutex 'c->mu_' is held}}
|
|
c->mu_.Unlock();
|
|
}
|
|
} // end namespace thread_annot_lock_46
|
|
|
|
namespace thread_annot_lock_67_modified {
|
|
// Modified: attributes on definitions moved to declarations
|
|
// Test annotations on out-of-line definitions of member functions where the
|
|
// annotations refer to locks that are also data members in the class.
|
|
Mutex mu;
|
|
Mutex mu3;
|
|
|
|
class Foo {
|
|
public:
|
|
int method1(int i) SHARED_LOCKS_REQUIRED(mu1, mu, mu2, mu3);
|
|
int data GUARDED_BY(mu1);
|
|
Mutex *mu1;
|
|
Mutex *mu2;
|
|
};
|
|
|
|
int Foo::method1(int i) {
|
|
return data + i;
|
|
}
|
|
|
|
void main()
|
|
{
|
|
Foo a;
|
|
a.method1(1); // expected-warning {{calling function 'method1' requires holding mutex 'a.mu1'}} \
|
|
// expected-warning {{calling function 'method1' requires holding mutex 'mu'}} \
|
|
// expected-warning {{calling function 'method1' requires holding mutex 'a.mu2'}} \
|
|
// expected-warning {{calling function 'method1' requires holding mutex 'mu3'}}
|
|
}
|
|
} // end namespace thread_annot_lock_67_modified
|
|
|
|
|
|
namespace substitution_test {
|
|
class MyData {
|
|
public:
|
|
Mutex mu;
|
|
|
|
void lockData() EXCLUSIVE_LOCK_FUNCTION(mu);
|
|
void unlockData() UNLOCK_FUNCTION(mu);
|
|
|
|
void doSomething() EXCLUSIVE_LOCKS_REQUIRED(mu) { }
|
|
void operator()() EXCLUSIVE_LOCKS_REQUIRED(mu) { }
|
|
|
|
MyData operator+(const MyData& other) const SHARED_LOCKS_REQUIRED(mu, other.mu);
|
|
};
|
|
|
|
MyData operator-(const MyData& a, const MyData& b) SHARED_LOCKS_REQUIRED(a.mu, b.mu);
|
|
|
|
class DataLocker {
|
|
public:
|
|
void lockData (MyData *d) EXCLUSIVE_LOCK_FUNCTION(d->mu);
|
|
void unlockData(MyData *d) UNLOCK_FUNCTION(d->mu);
|
|
};
|
|
|
|
|
|
class Foo {
|
|
public:
|
|
void foo(MyData* d) EXCLUSIVE_LOCKS_REQUIRED(d->mu) { }
|
|
|
|
void subst(MyData& d) {
|
|
d.doSomething(); // expected-warning {{calling function 'doSomething' requires holding mutex 'd.mu' exclusively}}
|
|
d(); // expected-warning {{calling function 'operator()' requires holding mutex 'd.mu' exclusively}}
|
|
d.operator()(); // expected-warning {{calling function 'operator()' requires holding mutex 'd.mu' exclusively}}
|
|
|
|
d.lockData();
|
|
d.doSomething();
|
|
d();
|
|
d.operator()();
|
|
d.unlockData();
|
|
}
|
|
|
|
void binop(MyData& a, MyData& b) EXCLUSIVE_LOCKS_REQUIRED(a.mu) {
|
|
a + b; // expected-warning {{calling function 'operator+' requires holding mutex 'b.mu'}}
|
|
// expected-note@-1 {{found near match 'a.mu'}}
|
|
b + a; // expected-warning {{calling function 'operator+' requires holding mutex 'b.mu'}}
|
|
// expected-note@-1 {{found near match 'a.mu'}}
|
|
a - b; // expected-warning {{calling function 'operator-' requires holding mutex 'b.mu'}}
|
|
// expected-note@-1 {{found near match 'a.mu'}}
|
|
}
|
|
|
|
void bar1(MyData* d) {
|
|
d->lockData();
|
|
foo(d);
|
|
d->unlockData();
|
|
}
|
|
|
|
void bar2(MyData* d) {
|
|
DataLocker dlr;
|
|
dlr.lockData(d);
|
|
foo(d);
|
|
dlr.unlockData(d);
|
|
}
|
|
|
|
void bar3(MyData* d1, MyData* d2) {
|
|
DataLocker dlr;
|
|
dlr.lockData(d1); // expected-note {{mutex acquired here}}
|
|
dlr.unlockData(d2); // \
|
|
// expected-warning {{releasing mutex 'd2->mu' that was not held}}
|
|
} // expected-warning {{mutex 'd1->mu' is still held at the end of function}}
|
|
|
|
void bar4(MyData* d1, MyData* d2) {
|
|
DataLocker dlr;
|
|
dlr.lockData(d1);
|
|
foo(d2); // \
|
|
// expected-warning {{calling function 'foo' requires holding mutex 'd2->mu' exclusively}} \
|
|
// expected-note {{found near match 'd1->mu'}}
|
|
dlr.unlockData(d1);
|
|
}
|
|
};
|
|
|
|
// Automatic object destructor calls don't appear as expressions in the CFG,
|
|
// so we have to handle them separately whenever substitutions are required.
|
|
struct DestructorRequires {
|
|
Mutex mu;
|
|
~DestructorRequires() EXCLUSIVE_LOCKS_REQUIRED(mu);
|
|
};
|
|
|
|
void destructorRequires() {
|
|
DestructorRequires rd;
|
|
rd.mu.AssertHeld();
|
|
}
|
|
|
|
struct DestructorExcludes {
|
|
Mutex mu;
|
|
~DestructorExcludes() LOCKS_EXCLUDED(mu);
|
|
};
|
|
|
|
void destructorExcludes() {
|
|
DestructorExcludes ed;
|
|
ed.mu.Lock(); // expected-note {{mutex acquired here}}
|
|
} // expected-warning {{cannot call function '~DestructorExcludes' while mutex 'ed.mu' is held}}
|
|
// expected-warning@-1 {{mutex 'ed.mu' is still held at the end of function}}
|
|
|
|
} // end namespace substituation_test
|
|
|
|
|
|
|
|
namespace constructor_destructor_tests {
|
|
Mutex fooMu;
|
|
int myVar GUARDED_BY(fooMu);
|
|
|
|
class Foo {
|
|
public:
|
|
Foo() EXCLUSIVE_LOCK_FUNCTION(fooMu) { }
|
|
~Foo() UNLOCK_FUNCTION(fooMu) { }
|
|
};
|
|
|
|
void fooTest() {
|
|
Foo foo;
|
|
myVar = 0;
|
|
}
|
|
}
|
|
|
|
|
|
namespace template_member_test {
|
|
|
|
struct S { int n; };
|
|
struct T {
|
|
Mutex m;
|
|
S *s GUARDED_BY(this->m);
|
|
};
|
|
Mutex m;
|
|
struct U {
|
|
union {
|
|
int n;
|
|
};
|
|
} *u GUARDED_BY(m);
|
|
|
|
template<typename U>
|
|
struct IndirectLock {
|
|
int DoNaughtyThings(T *t) {
|
|
u->n = 0; // expected-warning {{reading variable 'u' requires holding mutex 'm'}}
|
|
return t->s->n; // expected-warning {{reading variable 's' requires holding mutex 't->m'}}
|
|
}
|
|
};
|
|
|
|
template struct IndirectLock<int>; // expected-note {{here}}
|
|
|
|
struct V {
|
|
void f(int);
|
|
void f(double);
|
|
|
|
Mutex m;
|
|
V *p GUARDED_BY(this->m);
|
|
};
|
|
template<typename U> struct W {
|
|
V v;
|
|
void f(U u) {
|
|
v.p->f(u); // expected-warning {{reading variable 'p' requires holding mutex 'v.m'}}
|
|
}
|
|
};
|
|
template struct W<int>; // expected-note {{here}}
|
|
|
|
}
|
|
|
|
namespace test_scoped_lockable {
|
|
|
|
struct TestScopedLockable {
|
|
Mutex mu1;
|
|
Mutex mu2;
|
|
int a __attribute__((guarded_by(mu1)));
|
|
int b __attribute__((guarded_by(mu2)));
|
|
|
|
bool getBool();
|
|
|
|
bool lock2Bool(MutexLock);
|
|
|
|
void foo1() {
|
|
MutexLock mulock(&mu1);
|
|
a = 5;
|
|
}
|
|
|
|
#ifdef __cpp_guaranteed_copy_elision
|
|
void const_lock() {
|
|
const MutexLock mulock = MutexLock(&mu1);
|
|
a = 5;
|
|
}
|
|
#endif
|
|
|
|
void temporary() {
|
|
MutexLock{&mu1}, a = 5;
|
|
}
|
|
|
|
void temporary_cfg(int x) {
|
|
// test the case where a pair of temporary Ctor and Dtor is in different CFG blocks
|
|
lock2Bool(MutexLock{&mu1}) || x;
|
|
MutexLock{&mu1}; // no-warn
|
|
}
|
|
|
|
void lifetime_extension() {
|
|
const MutexLock &mulock = MutexLock(&mu1);
|
|
a = 5;
|
|
}
|
|
|
|
void foo2() {
|
|
ReaderMutexLock mulock1(&mu1);
|
|
if (getBool()) {
|
|
MutexLock mulock2a(&mu2);
|
|
b = a + 1;
|
|
}
|
|
else {
|
|
MutexLock mulock2b(&mu2);
|
|
b = a + 2;
|
|
}
|
|
}
|
|
|
|
void foo3() {
|
|
MutexLock mulock_a(&mu1); // expected-note{{mutex acquired here}}
|
|
MutexLock mulock_b(&mu1); // \
|
|
// expected-warning {{acquiring mutex 'mu1' that is already held}}
|
|
}
|
|
|
|
void temporary_double_lock() {
|
|
MutexLock mulock_a(&mu1); // expected-note{{mutex acquired here}}
|
|
MutexLock{&mu1}; // \
|
|
// expected-warning {{acquiring mutex 'mu1' that is already held}}
|
|
}
|
|
|
|
void foo4() {
|
|
MutexLock mulock1(&mu1), mulock2(&mu2);
|
|
a = b+1;
|
|
b = a+1;
|
|
}
|
|
|
|
void foo5() {
|
|
DoubleMutexLock mulock(&mu1, &mu2);
|
|
a = b + 1;
|
|
b = a + 1;
|
|
}
|
|
|
|
void foo6() {
|
|
TemplateMutexLock<Mutex> mulock1(&mu1), mulock2(&mu2);
|
|
a = b + 1;
|
|
b = a + 1;
|
|
}
|
|
|
|
void foo7() {
|
|
VariadicMutexLock<Mutex, Mutex> mulock(&mu1, &mu2);
|
|
a = b + 1;
|
|
b = a + 1;
|
|
}
|
|
|
|
#if __cplusplus >= 202002L
|
|
void rangeForLoopInitializer() {
|
|
for (MutexLock lock{&mu1}; int& x : (int[]){1, 2, 3}) {
|
|
a = 42;
|
|
}
|
|
}
|
|
#endif
|
|
};
|
|
|
|
namespace test_function_param_lock_unlock {
|
|
class A {
|
|
public:
|
|
A() EXCLUSIVE_LOCK_FUNCTION(mu_) { mu_.Lock(); }
|
|
~A() UNLOCK_FUNCTION(mu_) { mu_.Unlock(); }
|
|
private:
|
|
Mutex mu_;
|
|
};
|
|
int do_something(A a) { return 0; }
|
|
|
|
// Unlock in dtor without lock in ctor.
|
|
// FIXME: We cannot detect that we are releasing a lock that was never held!
|
|
class B {
|
|
public:
|
|
B() {}
|
|
B(int) {}
|
|
~B() UNLOCK_FUNCTION(mu_) { mu_.Unlock(); }
|
|
private:
|
|
Mutex mu_;
|
|
};
|
|
int do_something(B b) { return 0; }
|
|
|
|
class SCOPED_LOCKABLE MutexWrapper {
|
|
public:
|
|
MutexWrapper(Mutex *mu) : mu_(mu) {}
|
|
~MutexWrapper() UNLOCK_FUNCTION(mu_) { mu_->Unlock(); }
|
|
void Lock() EXCLUSIVE_LOCK_FUNCTION(mu_) { mu_->Lock(); }
|
|
|
|
Mutex *mu_;
|
|
};
|
|
// FIXME: This is a false-positive as the lock is released by the dtor.
|
|
void do_something(MutexWrapper mw) {
|
|
mw.Lock(); // expected-note {{mutex acquired here}}
|
|
} // expected-warning {{mutex 'mw.mu_' is still held at the end of function}}
|
|
|
|
} // namespace test_function_param_lock_unlock
|
|
|
|
} // end namespace test_scoped_lockable
|
|
|
|
|
|
namespace FunctionAttrTest {
|
|
|
|
class Foo {
|
|
public:
|
|
Mutex mu_;
|
|
int a GUARDED_BY(mu_);
|
|
};
|
|
|
|
Foo fooObj;
|
|
|
|
void foo() EXCLUSIVE_LOCKS_REQUIRED(fooObj.mu_);
|
|
|
|
void bar() {
|
|
foo(); // expected-warning {{calling function 'foo' requires holding mutex 'fooObj.mu_' exclusively}}
|
|
fooObj.mu_.Lock();
|
|
foo();
|
|
fooObj.mu_.Unlock();
|
|
}
|
|
|
|
}; // end namespace FunctionAttrTest
|
|
|
|
|
|
namespace TryLockTest {
|
|
|
|
struct TestTryLock {
|
|
Mutex mu;
|
|
Mutex mu2;
|
|
int a GUARDED_BY(mu);
|
|
bool cond;
|
|
|
|
void foo1() {
|
|
if (mu.TryLock()) {
|
|
a = 1;
|
|
mu.Unlock();
|
|
}
|
|
}
|
|
|
|
void foo2() {
|
|
if (!mu.TryLock()) return;
|
|
a = 2;
|
|
mu.Unlock();
|
|
}
|
|
|
|
void foo2_builtin_expect() {
|
|
if (__builtin_expect(!mu.TryLock(), false))
|
|
return;
|
|
a = 2;
|
|
mu.Unlock();
|
|
}
|
|
|
|
void foo3() {
|
|
bool b = mu.TryLock();
|
|
if (b) {
|
|
a = 3;
|
|
mu.Unlock();
|
|
}
|
|
}
|
|
|
|
void foo3_stmtexpr() {
|
|
if (({ bool b = mu.TryLock(); b; })) {
|
|
a = 3;
|
|
mu.Unlock();
|
|
}
|
|
}
|
|
|
|
void foo3_builtin_expect() {
|
|
bool b = mu.TryLock();
|
|
if (__builtin_expect(b, true)) {
|
|
a = 3;
|
|
mu.Unlock();
|
|
}
|
|
}
|
|
|
|
void foo3_builtin_expect_stmtexpr() {
|
|
if (({ bool b = mu.TryLock(); __builtin_expect(b, true); })) {
|
|
a = 3;
|
|
mu.Unlock();
|
|
}
|
|
}
|
|
|
|
void foo3_double_branch() {
|
|
bool failed = !mu.TryLock();
|
|
if (failed)
|
|
cond = true; // does not return; rejoins the success path
|
|
if (failed) // paths re-diverge consistently here, so no warning at
|
|
return; // the preceding join
|
|
a = 3;
|
|
mu.Unlock();
|
|
}
|
|
|
|
// Mimic the logic of the previous test, but in a statement expression.
|
|
// This pattern is typically found in macros.
|
|
void foo3_double_branch_statement_expression() {
|
|
if (({ bool failed = !mu.TryLock(); if (failed) cond = true; failed; }))
|
|
return;
|
|
a = 3;
|
|
mu.Unlock();
|
|
}
|
|
|
|
void foo3_no_rebranch_at_join() {
|
|
bool failed = !mu.TryLock(); // expected-note {{mutex acquired here}}
|
|
if (failed)
|
|
cond = true;
|
|
// Lock state genuinely differs at this join: nothing re-branches on
|
|
// 'failed' here, so the warning must be retained.
|
|
a = 3; // expected-warning {{mutex 'mu' is not held on every path through here}} \
|
|
// expected-warning {{writing variable 'a' requires holding mutex 'mu' exclusively}}
|
|
mu.Unlock(); // expected-warning {{releasing mutex 'mu' that was not held}}
|
|
}
|
|
|
|
void foo3_rebranch_after_reassign() {
|
|
bool failed = !mu.TryLock(); // expected-note {{mutex acquired here}}
|
|
if (failed)
|
|
cond = true;
|
|
failed = true; // expected-warning {{mutex 'mu' is not held on every path through here}}
|
|
if (failed) // no longer the try-lock result: the join above must warn
|
|
return;
|
|
a = 3; // expected-warning {{writing variable 'a' requires holding mutex 'mu' exclusively}}
|
|
mu.Unlock(); // expected-warning {{releasing mutex 'mu' that was not held}}
|
|
}
|
|
|
|
void foo3_rebranch_other_mutex_still_warns() {
|
|
bool failed = !mu.TryLock();
|
|
if (failed)
|
|
mu2.Lock(); // expected-note {{mutex acquired here}}
|
|
// The re-branch on 'failed' only suppresses the warning for 'mu', the
|
|
// capability the try-lock acquires; 'mu2' must still warn at the join.
|
|
if (failed) // expected-warning {{mutex 'mu2' is not held on every path through here}}
|
|
return;
|
|
a = 3;
|
|
mu.Unlock();
|
|
}
|
|
|
|
void foo4() {
|
|
bool b = mu.TryLock();
|
|
if (!b) return;
|
|
a = 4;
|
|
mu.Unlock();
|
|
}
|
|
|
|
void foo5() {
|
|
while (mu.TryLock()) {
|
|
a = a + 1;
|
|
mu.Unlock();
|
|
}
|
|
}
|
|
|
|
void foo6() {
|
|
bool b = mu.TryLock();
|
|
b = !b;
|
|
if (b) return;
|
|
a = 6;
|
|
mu.Unlock();
|
|
}
|
|
|
|
void foo7() {
|
|
bool b1 = mu.TryLock();
|
|
bool b2 = !b1;
|
|
bool b3 = !b2;
|
|
if (b3) {
|
|
a = 7;
|
|
mu.Unlock();
|
|
}
|
|
}
|
|
|
|
// Test use-def chains: join points
|
|
void foo8() {
|
|
bool b = mu.TryLock();
|
|
bool b2 = b;
|
|
if (cond)
|
|
b = true;
|
|
if (b) { // b should be unknown at this point, because of the join point
|
|
a = 8; // expected-warning {{writing variable 'a' requires holding mutex 'mu' exclusively}}
|
|
}
|
|
if (b2) { // b2 should be known at this point.
|
|
a = 8;
|
|
mu.Unlock();
|
|
}
|
|
}
|
|
|
|
// Test use-def-chains: back edges
|
|
void foo9() {
|
|
bool b = mu.TryLock();
|
|
|
|
for (int i = 0; i < 10; ++i);
|
|
|
|
if (b) { // b is still known, because the loop doesn't alter it
|
|
a = 9;
|
|
mu.Unlock();
|
|
}
|
|
}
|
|
|
|
// Test use-def chains: back edges
|
|
void foo10() {
|
|
bool b = mu.TryLock();
|
|
|
|
while (cond) {
|
|
if (b) { // b should be unknown at this point b/c of the loop
|
|
a = 10; // expected-warning {{writing variable 'a' requires holding mutex 'mu' exclusively}}
|
|
}
|
|
b = !b;
|
|
}
|
|
}
|
|
|
|
// Test merge of exclusive trylock
|
|
void foo11() {
|
|
if (cond) {
|
|
if (!mu.TryLock())
|
|
return;
|
|
}
|
|
else {
|
|
mu.Lock();
|
|
}
|
|
a = 10;
|
|
mu.Unlock();
|
|
}
|
|
|
|
// Test merge of shared trylock
|
|
void foo12() {
|
|
if (cond) {
|
|
if (!mu.ReaderTryLock())
|
|
return;
|
|
}
|
|
else {
|
|
mu.ReaderLock();
|
|
}
|
|
int i = a;
|
|
mu.Unlock();
|
|
}
|
|
|
|
// Test with conditional operator
|
|
void foo13() {
|
|
if (mu.TryLock() ? 1 : 0)
|
|
mu.Unlock();
|
|
}
|
|
|
|
void foo14() {
|
|
if (mu.TryLock() ? 0 : 1)
|
|
return;
|
|
mu.Unlock();
|
|
}
|
|
|
|
void foo15() {
|
|
if (mu.TryLock() ? 0 : 1) // expected-note{{mutex acquired here}}
|
|
mu.Unlock(); // expected-warning{{releasing mutex 'mu' that was not held}}
|
|
} // expected-warning{{mutex 'mu' is not held on every path through here}}
|
|
|
|
// A void conditional operator has no result to branch on later, so unlike
|
|
// foo13-foo15 the branch itself is honored. This is how glibc before 2.32
|
|
// spells assert().
|
|
void foo16() {
|
|
mu.TryLock() ? static_cast<void>(0) : fail();
|
|
a = 3;
|
|
mu.Unlock();
|
|
}
|
|
|
|
void foo17() {
|
|
!mu.TryLock() ? fail() : static_cast<void>(0);
|
|
a = 3;
|
|
mu.Unlock();
|
|
}
|
|
|
|
// Both arms return here, so the join disagrees -- as it would for an if.
|
|
void foo18() {
|
|
mu.TryLock() ? static_cast<void>(0) : static_cast<void>(0); // expected-note{{mutex acquired here}} \
|
|
expected-warning{{mutex 'mu' is not held on every path through here}}
|
|
mu.Unlock(); // expected-warning{{releasing mutex 'mu' that was not held}}
|
|
}
|
|
|
|
static void fail() __attribute__((noreturn));
|
|
}; // end TestTrylock
|
|
|
|
} // end namespace TrylockTest
|
|
|
|
|
|
namespace TestTemplateAttributeInstantiation {
|
|
|
|
class Foo1 {
|
|
public:
|
|
Mutex mu_;
|
|
int a GUARDED_BY(mu_);
|
|
};
|
|
|
|
class Foo2 {
|
|
public:
|
|
int a GUARDED_BY(mu_);
|
|
Mutex mu_;
|
|
};
|
|
|
|
|
|
class Bar {
|
|
public:
|
|
// Test non-dependent expressions in attributes on template functions
|
|
template <class T>
|
|
void barND(Foo1 *foo, T *fooT) EXCLUSIVE_LOCKS_REQUIRED(foo->mu_) {
|
|
foo->a = 0;
|
|
}
|
|
|
|
// Test dependent expressions in attributes on template functions
|
|
template <class T>
|
|
void barD(Foo1 *foo, T *fooT) EXCLUSIVE_LOCKS_REQUIRED(fooT->mu_) {
|
|
fooT->a = 0;
|
|
}
|
|
};
|
|
|
|
|
|
template <class T>
|
|
class BarT {
|
|
public:
|
|
Foo1 fooBase;
|
|
T fooBaseT;
|
|
|
|
// Test non-dependent expression in ordinary method on template class
|
|
void barND() EXCLUSIVE_LOCKS_REQUIRED(fooBase.mu_) {
|
|
fooBase.a = 0;
|
|
}
|
|
|
|
// Test dependent expressions in ordinary methods on template class
|
|
void barD() EXCLUSIVE_LOCKS_REQUIRED(fooBaseT.mu_) {
|
|
fooBaseT.a = 0;
|
|
}
|
|
|
|
// Test dependent expressions in template method in template class
|
|
template <class T2>
|
|
void barTD(T2 *fooT) EXCLUSIVE_LOCKS_REQUIRED(fooBaseT.mu_, fooT->mu_) {
|
|
fooBaseT.a = 0;
|
|
fooT->a = 0;
|
|
}
|
|
};
|
|
|
|
template <class T>
|
|
class Cell {
|
|
public:
|
|
Mutex mu_;
|
|
// Test dependent guarded_by
|
|
T data GUARDED_BY(mu_);
|
|
|
|
void fooEx() EXCLUSIVE_LOCKS_REQUIRED(mu_) {
|
|
data = 0;
|
|
}
|
|
|
|
void foo() {
|
|
mu_.Lock();
|
|
data = 0;
|
|
mu_.Unlock();
|
|
}
|
|
};
|
|
|
|
void test() {
|
|
Bar b;
|
|
BarT<Foo2> bt;
|
|
Foo1 f1;
|
|
Foo2 f2;
|
|
|
|
f1.mu_.Lock();
|
|
f2.mu_.Lock();
|
|
bt.fooBase.mu_.Lock();
|
|
bt.fooBaseT.mu_.Lock();
|
|
|
|
b.barND(&f1, &f2);
|
|
b.barD(&f1, &f2);
|
|
bt.barND();
|
|
bt.barD();
|
|
bt.barTD(&f2);
|
|
|
|
f1.mu_.Unlock();
|
|
bt.barTD(&f1); // \
|
|
// expected-warning {{calling function 'barTD<TestTemplateAttributeInstantiation::Foo1>' requires holding mutex 'f1.mu_' exclusively}} \
|
|
// expected-note {{found near match 'bt.fooBase.mu_'}}
|
|
|
|
bt.fooBase.mu_.Unlock();
|
|
bt.fooBaseT.mu_.Unlock();
|
|
f2.mu_.Unlock();
|
|
|
|
Cell<int> cell;
|
|
cell.data = 0; // \
|
|
// expected-warning {{writing variable 'data' requires holding mutex 'cell.mu_' exclusively}}
|
|
cell.foo();
|
|
cell.mu_.Lock();
|
|
cell.fooEx();
|
|
cell.mu_.Unlock();
|
|
}
|
|
|
|
|
|
template <class T>
|
|
class CellDelayed {
|
|
public:
|
|
// Test dependent guarded_by
|
|
T data GUARDED_BY(mu_);
|
|
static T static_data GUARDED_BY(static_mu_);
|
|
|
|
void fooEx(CellDelayed<T> *other) EXCLUSIVE_LOCKS_REQUIRED(mu_, other->mu_) {
|
|
this->data = other->data;
|
|
}
|
|
|
|
template <class T2>
|
|
void fooExT(CellDelayed<T2> *otherT) EXCLUSIVE_LOCKS_REQUIRED(mu_, otherT->mu_) {
|
|
this->data = otherT->data;
|
|
}
|
|
|
|
void foo() {
|
|
mu_.Lock();
|
|
data = 0;
|
|
mu_.Unlock();
|
|
}
|
|
|
|
Mutex mu_;
|
|
static Mutex static_mu_;
|
|
};
|
|
|
|
void testDelayed() {
|
|
CellDelayed<int> celld;
|
|
CellDelayed<int> celld2;
|
|
celld.foo();
|
|
celld.mu_.Lock();
|
|
celld2.mu_.Lock();
|
|
|
|
celld.fooEx(&celld2);
|
|
celld.fooExT(&celld2);
|
|
|
|
celld2.mu_.Unlock();
|
|
celld.mu_.Unlock();
|
|
}
|
|
|
|
}; // end namespace TestTemplateAttributeInstantiation
|
|
|
|
|
|
namespace FunctionDeclDefTest {
|
|
|
|
class Foo {
|
|
public:
|
|
Mutex mu_;
|
|
int a GUARDED_BY(mu_);
|
|
|
|
virtual void foo1(Foo *f_declared) EXCLUSIVE_LOCKS_REQUIRED(f_declared->mu_);
|
|
};
|
|
|
|
// EXCLUSIVE_LOCKS_REQUIRED should be applied, and rewritten to f_defined->mu_
|
|
void Foo::foo1(Foo *f_defined) {
|
|
f_defined->a = 0;
|
|
};
|
|
|
|
void test() {
|
|
Foo myfoo;
|
|
myfoo.foo1(&myfoo); // \
|
|
// expected-warning {{calling function 'foo1' requires holding mutex 'myfoo.mu_' exclusively}}
|
|
myfoo.mu_.Lock();
|
|
myfoo.foo1(&myfoo);
|
|
myfoo.mu_.Unlock();
|
|
}
|
|
|
|
};
|
|
|
|
namespace GoingNative {
|
|
|
|
struct LOCKABLE mutex {
|
|
void lock() EXCLUSIVE_LOCK_FUNCTION();
|
|
void unlock() UNLOCK_FUNCTION();
|
|
// ...
|
|
};
|
|
bool foo();
|
|
bool bar();
|
|
mutex m;
|
|
void test() {
|
|
m.lock();
|
|
while (foo()) { // expected-warning {{expecting mutex 'm' to be held at start of each loop}}
|
|
m.unlock();
|
|
// ...
|
|
if (bar()) {
|
|
// ...
|
|
if (foo())
|
|
continue;
|
|
//...
|
|
}
|
|
// ...
|
|
m.lock(); // expected-note {{mutex acquired here}}
|
|
}
|
|
m.unlock();
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
namespace FunctionDefinitionTest {
|
|
|
|
class Foo {
|
|
public:
|
|
void foo1();
|
|
void foo2();
|
|
void foo3(Foo *other);
|
|
|
|
template<class T>
|
|
void fooT1(const T& dummy1);
|
|
|
|
template<class T>
|
|
void fooT2(const T& dummy2) EXCLUSIVE_LOCKS_REQUIRED(mu_);
|
|
|
|
Mutex mu_;
|
|
int a GUARDED_BY(mu_);
|
|
};
|
|
|
|
template<class T>
|
|
class FooT {
|
|
public:
|
|
void foo();
|
|
|
|
Mutex mu_;
|
|
T a GUARDED_BY(mu_);
|
|
};
|
|
|
|
|
|
void Foo::foo1() NO_THREAD_SAFETY_ANALYSIS {
|
|
a = 1;
|
|
}
|
|
|
|
void Foo::foo2() EXCLUSIVE_LOCKS_REQUIRED(mu_) {
|
|
a = 2;
|
|
}
|
|
|
|
void Foo::foo3(Foo *other) EXCLUSIVE_LOCKS_REQUIRED(other->mu_) {
|
|
other->a = 3;
|
|
}
|
|
|
|
template<class T>
|
|
void Foo::fooT1(const T& dummy1) EXCLUSIVE_LOCKS_REQUIRED(mu_) {
|
|
a = dummy1;
|
|
}
|
|
|
|
/* TODO -- uncomment with template instantiation of attributes.
|
|
template<class T>
|
|
void Foo::fooT2(const T& dummy2) {
|
|
a = dummy2;
|
|
}
|
|
*/
|
|
|
|
void fooF1(Foo *f) EXCLUSIVE_LOCKS_REQUIRED(f->mu_) {
|
|
f->a = 1;
|
|
}
|
|
|
|
void fooF2(Foo *f);
|
|
void fooF2(Foo *f) EXCLUSIVE_LOCKS_REQUIRED(f->mu_) {
|
|
f->a = 2;
|
|
}
|
|
|
|
void fooF3(Foo *f) EXCLUSIVE_LOCKS_REQUIRED(f->mu_);
|
|
void fooF3(Foo *f) {
|
|
f->a = 3;
|
|
}
|
|
|
|
template<class T>
|
|
void FooT<T>::foo() EXCLUSIVE_LOCKS_REQUIRED(mu_) {
|
|
a = 0;
|
|
}
|
|
|
|
void test() {
|
|
int dummy = 0;
|
|
Foo myFoo;
|
|
|
|
myFoo.foo2(); // \
|
|
// expected-warning {{calling function 'foo2' requires holding mutex 'myFoo.mu_' exclusively}}
|
|
myFoo.foo3(&myFoo); // \
|
|
// expected-warning {{calling function 'foo3' requires holding mutex 'myFoo.mu_' exclusively}}
|
|
myFoo.fooT1(dummy); // \
|
|
// expected-warning {{calling function 'fooT1<int>' requires holding mutex 'myFoo.mu_' exclusively}}
|
|
|
|
myFoo.fooT2(dummy); // \
|
|
// expected-warning {{calling function 'fooT2<int>' requires holding mutex 'myFoo.mu_' exclusively}}
|
|
|
|
fooF1(&myFoo); // \
|
|
// expected-warning {{calling function 'fooF1' requires holding mutex 'myFoo.mu_' exclusively}}
|
|
fooF2(&myFoo); // \
|
|
// expected-warning {{calling function 'fooF2' requires holding mutex 'myFoo.mu_' exclusively}}
|
|
fooF3(&myFoo); // \
|
|
// expected-warning {{calling function 'fooF3' requires holding mutex 'myFoo.mu_' exclusively}}
|
|
|
|
myFoo.mu_.Lock();
|
|
myFoo.foo2();
|
|
myFoo.foo3(&myFoo);
|
|
myFoo.fooT1(dummy);
|
|
|
|
myFoo.fooT2(dummy);
|
|
|
|
fooF1(&myFoo);
|
|
fooF2(&myFoo);
|
|
fooF3(&myFoo);
|
|
myFoo.mu_.Unlock();
|
|
|
|
FooT<int> myFooT;
|
|
myFooT.foo(); // \
|
|
// expected-warning {{calling function 'foo' requires holding mutex 'myFooT.mu_' exclusively}}
|
|
}
|
|
|
|
} // end namespace FunctionDefinitionTest
|
|
|
|
|
|
namespace SelfLockingTest {
|
|
|
|
class LOCKABLE MyLock {
|
|
public:
|
|
int foo GUARDED_BY(this);
|
|
|
|
void lock() EXCLUSIVE_LOCK_FUNCTION();
|
|
void unlock() UNLOCK_FUNCTION();
|
|
|
|
void doSomething() {
|
|
this->lock(); // allow 'this' as a lock expression
|
|
foo = 0;
|
|
doSomethingElse();
|
|
this->unlock();
|
|
}
|
|
|
|
void doSomethingElse() EXCLUSIVE_LOCKS_REQUIRED(this) {
|
|
foo = 1;
|
|
};
|
|
|
|
void test() {
|
|
foo = 2; // \
|
|
// expected-warning {{writing variable 'foo' requires holding mutex 'this' exclusively}}
|
|
}
|
|
};
|
|
|
|
|
|
class LOCKABLE MyLock2 {
|
|
public:
|
|
Mutex mu_;
|
|
int foo GUARDED_BY(this);
|
|
|
|
// don't check inside lock and unlock functions
|
|
void lock() EXCLUSIVE_LOCK_FUNCTION() { mu_.Lock(); }
|
|
void unlock() UNLOCK_FUNCTION() { mu_.Unlock(); }
|
|
|
|
// don't check inside constructors and destructors
|
|
MyLock2() { foo = 1; }
|
|
~MyLock2() { foo = 0; }
|
|
};
|
|
|
|
|
|
} // end namespace SelfLockingTest
|
|
|
|
|
|
namespace InvalidNonstatic {
|
|
|
|
// Forward decl here causes bogus "invalid use of non-static data member"
|
|
// on reference to mutex_ in guarded_by attribute.
|
|
class Foo;
|
|
|
|
class Foo {
|
|
Mutex* mutex_;
|
|
|
|
int foo __attribute__((guarded_by(mutex_)));
|
|
};
|
|
|
|
} // end namespace InvalidNonStatic
|
|
|
|
|
|
namespace NoReturnTest {
|
|
|
|
bool condition();
|
|
void fatal() __attribute__((noreturn));
|
|
|
|
Mutex mu_;
|
|
|
|
void test1() {
|
|
MutexLock lock(&mu_);
|
|
if (condition()) {
|
|
fatal();
|
|
return;
|
|
}
|
|
}
|
|
|
|
} // end namespace NoReturnTest
|
|
|
|
|
|
namespace TestMultiDecl {
|
|
|
|
class Foo {
|
|
public:
|
|
int GUARDED_BY(mu_) a;
|
|
int GUARDED_BY(mu_) b, c;
|
|
|
|
void foo() {
|
|
a = 0; // \
|
|
// expected-warning {{writing variable 'a' requires holding mutex 'mu_' exclusively}}
|
|
b = 0; // \
|
|
// expected-warning {{writing variable 'b' requires holding mutex 'mu_' exclusively}}
|
|
c = 0; // \
|
|
// expected-warning {{writing variable 'c' requires holding mutex 'mu_' exclusively}}
|
|
}
|
|
|
|
private:
|
|
Mutex mu_;
|
|
};
|
|
|
|
} // end namespace TestMultiDecl
|
|
|
|
|
|
namespace WarnNoDecl {
|
|
|
|
class Foo {
|
|
void foo(int a); __attribute__(( // \
|
|
// expected-warning {{declaration does not declare anything}}
|
|
exclusive_locks_required(a))); // \
|
|
// expected-warning {{attribute exclusive_locks_required ignored}}
|
|
|
|
};
|
|
|
|
} // end namespace WarnNoDecl
|
|
|
|
|
|
|
|
namespace MoreLockExpressions {
|
|
|
|
class Foo {
|
|
public:
|
|
Mutex mu_;
|
|
int a GUARDED_BY(mu_);
|
|
};
|
|
|
|
class Bar {
|
|
public:
|
|
int b;
|
|
Foo* f;
|
|
|
|
Foo& getFoo() { return *f; }
|
|
Foo& getFoo2(int c) { return *f; }
|
|
Foo& getFoo3(int c, int d) { return *f; }
|
|
Foo& getFoo4(bool) { return *f; }
|
|
Foo& getFoo5(char) { return *f; }
|
|
Foo& getFoo6(char16_t) { return *f; }
|
|
Foo& getFoo7(const char*) { return *f; }
|
|
|
|
Foo& getFooey() { return *f; }
|
|
};
|
|
|
|
Foo& getBarFoo(Bar &bar, int c) { return bar.getFoo2(c); }
|
|
|
|
void test() {
|
|
Foo foo;
|
|
Foo *fooArray;
|
|
Foo &(*fooFuncPtr)();
|
|
Bar bar;
|
|
int a;
|
|
int b;
|
|
int c;
|
|
|
|
bar.getFoo().mu_.Lock();
|
|
bar.getFoo().a = 0;
|
|
bar.getFoo().mu_.Unlock();
|
|
|
|
(bar.getFoo().mu_).Lock(); // test parenthesis
|
|
bar.getFoo().a = 0;
|
|
(bar.getFoo().mu_).Unlock();
|
|
|
|
bar.getFoo2(a).mu_.Lock();
|
|
bar.getFoo2(a).a = 0;
|
|
bar.getFoo2(a).mu_.Unlock();
|
|
|
|
bar.getFoo3(a, b).mu_.Lock();
|
|
bar.getFoo3(a, b).a = 0;
|
|
bar.getFoo3(a, b).mu_.Unlock();
|
|
|
|
bar.getFoo4(true).mu_.Lock();
|
|
bar.getFoo4(true).a = 0;
|
|
bar.getFoo4(true).mu_.Unlock();
|
|
|
|
bar.getFoo5('a').mu_.Lock();
|
|
bar.getFoo5('a').a = 0;
|
|
bar.getFoo5('a').mu_.Unlock();
|
|
|
|
bar.getFoo6(u'\u1234').mu_.Lock();
|
|
bar.getFoo6(u'\u1234').a = 0;
|
|
bar.getFoo6(u'\u1234').mu_.Unlock();
|
|
|
|
bar.getFoo7("foo").mu_.Lock();
|
|
bar.getFoo7("foo").a = 0;
|
|
bar.getFoo7("foo").mu_.Unlock();
|
|
|
|
getBarFoo(bar, a).mu_.Lock();
|
|
getBarFoo(bar, a).a = 0;
|
|
getBarFoo(bar, a).mu_.Unlock();
|
|
|
|
bar.getFoo2(10).mu_.Lock();
|
|
bar.getFoo2(10).a = 0;
|
|
bar.getFoo2(10).mu_.Unlock();
|
|
|
|
bar.getFoo2(a + 1).mu_.Lock();
|
|
bar.getFoo2(a + 1).a = 0;
|
|
bar.getFoo2(a + 1).mu_.Unlock();
|
|
|
|
(a > 0 ? fooArray[1] : fooArray[b]).mu_.Lock();
|
|
(a > 0 ? fooArray[1] : fooArray[b]).a = 0;
|
|
(a > 0 ? fooArray[1] : fooArray[b]).mu_.Unlock();
|
|
|
|
fooFuncPtr().mu_.Lock();
|
|
fooFuncPtr().a = 0;
|
|
fooFuncPtr().mu_.Unlock();
|
|
}
|
|
|
|
|
|
void test2() {
|
|
Foo *fooArray;
|
|
Bar bar;
|
|
int a;
|
|
int b;
|
|
int c;
|
|
|
|
bar.getFoo().mu_.Lock();
|
|
bar.getFooey().a = 0; // \
|
|
// expected-warning {{writing variable 'a' requires holding mutex 'bar.getFooey().mu_' exclusively}} \
|
|
// expected-note {{found near match 'bar.getFoo().mu_'}}
|
|
bar.getFoo().mu_.Unlock();
|
|
|
|
bar.getFoo2(a).mu_.Lock();
|
|
bar.getFoo2(b).a = 0; // \
|
|
// expected-warning {{writing variable 'a' requires holding mutex 'bar.getFoo2(b).mu_' exclusively}} \
|
|
// expected-note {{found near match 'bar.getFoo2(a).mu_'}}
|
|
bar.getFoo2(a).mu_.Unlock();
|
|
|
|
bar.getFoo2(0).mu_.Lock();
|
|
bar.getFoo2(1).a = 0; // \
|
|
// expected-warning {{writing variable 'a' requires holding mutex 'bar.getFoo2(1).mu_' exclusively}} \
|
|
// expected-note {{found near match 'bar.getFoo2(0).mu_'}}
|
|
bar.getFoo2(0).mu_.Unlock();
|
|
|
|
bar.getFoo3(a, b).mu_.Lock();
|
|
bar.getFoo3(a, c).a = 0; // \
|
|
// expected-warning {{writing variable 'a' requires holding mutex 'bar.getFoo3(a, c).mu_' exclusively}} \
|
|
// expected-note {{found near match 'bar.getFoo3(a, b).mu_'}}
|
|
bar.getFoo3(a, b).mu_.Unlock();
|
|
|
|
bar.getFoo4(true).mu_.Lock();
|
|
bar.getFoo4(false).a = 0; // \
|
|
// expected-warning {{writing variable 'a' requires holding mutex 'bar.getFoo4(false).mu_' exclusively}} \
|
|
// expected-note {{found near match 'bar.getFoo4(true).mu_'}}
|
|
bar.getFoo4(true).mu_.Unlock();
|
|
|
|
bar.getFoo5('x').mu_.Lock();
|
|
bar.getFoo5('y').a = 0; // \
|
|
// expected-warning {{writing variable 'a' requires holding mutex 'bar.getFoo5(U'y').mu_' exclusively}} \
|
|
// expected-note {{found near match 'bar.getFoo5(U'x').mu_'}}
|
|
bar.getFoo5('x').mu_.Unlock();
|
|
|
|
bar.getFoo6(u'\u1234').mu_.Lock();
|
|
bar.getFoo6(u'\u4321').a = 0; // \
|
|
// expected-warning {{writing variable 'a' requires holding mutex 'bar.getFoo6(U'\u4321').mu_' exclusively}} \
|
|
// expected-note {{found near match 'bar.getFoo6(U'\u1234').mu_'}}
|
|
bar.getFoo6(u'\u1234').mu_.Unlock();
|
|
|
|
bar.getFoo7("foo").mu_.Lock();
|
|
bar.getFoo7("bar").a = 0; // \
|
|
// expected-warning {{writing variable 'a' requires holding mutex 'bar.getFoo7("bar").mu_' exclusively}} \
|
|
// expected-note {{found near match 'bar.getFoo7("foo").mu_'}}
|
|
bar.getFoo7("foo").mu_.Unlock();
|
|
|
|
getBarFoo(bar, a).mu_.Lock();
|
|
getBarFoo(bar, b).a = 0; // \
|
|
// expected-warning {{writing variable 'a' requires holding mutex 'getBarFoo(bar, b).mu_' exclusively}} \
|
|
// expected-note {{found near match 'getBarFoo(bar, a).mu_'}}
|
|
getBarFoo(bar, a).mu_.Unlock();
|
|
|
|
(a > 0 ? fooArray[1] : fooArray[b]).mu_.Lock();
|
|
(a > 0 ? fooArray[b] : fooArray[c]).a = 0; // \
|
|
// expected-warning {{writing variable 'a' requires holding mutex '((0 < a) ? fooArray[b] : fooArray[c]).mu_' exclusively}} \
|
|
// expected-note {{found near match '((0 < a) ? fooArray[1] : fooArray[b]).mu_'}}
|
|
(a > 0 ? fooArray[1] : fooArray[b]).mu_.Unlock();
|
|
}
|
|
|
|
|
|
} // end namespace MoreLockExpressions
|
|
|
|
|
|
namespace TrylockJoinPoint {
|
|
|
|
class Foo {
|
|
Mutex mu;
|
|
bool c;
|
|
|
|
void foo() {
|
|
if (c) {
|
|
if (!mu.TryLock())
|
|
return;
|
|
} else {
|
|
mu.Lock();
|
|
}
|
|
mu.Unlock();
|
|
}
|
|
};
|
|
|
|
} // end namespace TrylockJoinPoint
|
|
|
|
|
|
namespace LockReturned {
|
|
|
|
class Foo {
|
|
public:
|
|
int a GUARDED_BY(mu_);
|
|
void foo() EXCLUSIVE_LOCKS_REQUIRED(mu_);
|
|
void foo2(Foo* f) EXCLUSIVE_LOCKS_REQUIRED(mu_, f->mu_);
|
|
|
|
static void sfoo(Foo* f) EXCLUSIVE_LOCKS_REQUIRED(f->mu_);
|
|
|
|
Mutex* getMu() LOCK_RETURNED(mu_);
|
|
|
|
Mutex mu_;
|
|
|
|
static Mutex* getMu(Foo* f) LOCK_RETURNED(f->mu_);
|
|
};
|
|
|
|
|
|
// Calls getMu() directly to lock and unlock
|
|
void test1(Foo* f1, Foo* f2) {
|
|
f1->a = 0; // expected-warning {{writing variable 'a' requires holding mutex 'f1->mu_' exclusively}}
|
|
f1->foo(); // expected-warning {{calling function 'foo' requires holding mutex 'f1->mu_' exclusively}}
|
|
|
|
f1->foo2(f2); // expected-warning {{calling function 'foo2' requires holding mutex 'f1->mu_' exclusively}} \
|
|
// expected-warning {{calling function 'foo2' requires holding mutex 'f2->mu_' exclusively}}
|
|
Foo::sfoo(f1); // expected-warning {{calling function 'sfoo' requires holding mutex 'f1->mu_' exclusively}}
|
|
|
|
f1->getMu()->Lock();
|
|
|
|
f1->a = 0;
|
|
f1->foo();
|
|
f1->foo2(f2); // \
|
|
// expected-warning {{calling function 'foo2' requires holding mutex 'f2->mu_' exclusively}} \
|
|
// expected-note {{found near match 'f1->mu_'}}
|
|
|
|
Foo::getMu(f2)->Lock();
|
|
f1->foo2(f2);
|
|
Foo::getMu(f2)->Unlock();
|
|
|
|
Foo::sfoo(f1);
|
|
|
|
f1->getMu()->Unlock();
|
|
}
|
|
|
|
|
|
Mutex* getFooMu(Foo* f) LOCK_RETURNED(Foo::getMu(f));
|
|
|
|
class Bar : public Foo {
|
|
public:
|
|
int b GUARDED_BY(getMu());
|
|
void bar() EXCLUSIVE_LOCKS_REQUIRED(getMu());
|
|
void bar2(Bar* g) EXCLUSIVE_LOCKS_REQUIRED(getMu(this), g->getMu());
|
|
|
|
static void sbar(Bar* g) EXCLUSIVE_LOCKS_REQUIRED(g->getMu());
|
|
static void sbar2(Bar* g) EXCLUSIVE_LOCKS_REQUIRED(getFooMu(g));
|
|
};
|
|
|
|
|
|
|
|
// Use getMu() within other attributes.
|
|
// This requires at lest levels of substitution, more in the case of
|
|
void test2(Bar* b1, Bar* b2) {
|
|
b1->b = 0; // expected-warning {{writing variable 'b' requires holding mutex 'b1->mu_' exclusively}}
|
|
b1->bar(); // expected-warning {{calling function 'bar' requires holding mutex 'b1->mu_' exclusively}}
|
|
b1->bar2(b2); // expected-warning {{calling function 'bar2' requires holding mutex 'b1->mu_' exclusively}} \
|
|
// expected-warning {{calling function 'bar2' requires holding mutex 'b2->mu_' exclusively}}
|
|
Bar::sbar(b1); // expected-warning {{calling function 'sbar' requires holding mutex 'b1->mu_' exclusively}}
|
|
Bar::sbar2(b1); // expected-warning {{calling function 'sbar2' requires holding mutex 'b1->mu_' exclusively}}
|
|
|
|
b1->getMu()->Lock();
|
|
|
|
b1->b = 0;
|
|
b1->bar();
|
|
b1->bar2(b2); // \
|
|
// expected-warning {{calling function 'bar2' requires holding mutex 'b2->mu_' exclusively}} \
|
|
// // expected-note {{found near match 'b1->mu_'}}
|
|
|
|
b2->getMu()->Lock();
|
|
b1->bar2(b2);
|
|
|
|
b2->getMu()->Unlock();
|
|
|
|
Bar::sbar(b1);
|
|
Bar::sbar2(b1);
|
|
|
|
b1->getMu()->Unlock();
|
|
}
|
|
|
|
|
|
// Lock the mutex directly, but use attributes that call getMu()
|
|
// Also lock the mutex using getFooMu, which calls a lock_returned function.
|
|
void test3(Bar* b1, Bar* b2) {
|
|
b1->mu_.Lock();
|
|
b1->b = 0;
|
|
b1->bar();
|
|
|
|
getFooMu(b2)->Lock();
|
|
b1->bar2(b2);
|
|
getFooMu(b2)->Unlock();
|
|
|
|
Bar::sbar(b1);
|
|
Bar::sbar2(b1);
|
|
|
|
b1->mu_.Unlock();
|
|
}
|
|
|
|
} // end namespace LockReturned
|
|
|
|
|
|
namespace ReleasableScopedLock {
|
|
|
|
class Foo {
|
|
Mutex mu_;
|
|
bool c;
|
|
int a GUARDED_BY(mu_);
|
|
|
|
void test1();
|
|
void test2();
|
|
void test3();
|
|
void test4();
|
|
void test5();
|
|
void test6();
|
|
};
|
|
|
|
|
|
void Foo::test1() {
|
|
ReleasableMutexLock rlock(&mu_);
|
|
rlock.Release();
|
|
}
|
|
|
|
void Foo::test2() {
|
|
ReleasableMutexLock rlock(&mu_);
|
|
if (c) { // test join point -- held/not held during release
|
|
rlock.Release();
|
|
}
|
|
// No warning on join point because the lock will be released by the scope object anyway.
|
|
}
|
|
|
|
void Foo::test3() {
|
|
ReleasableMutexLock rlock(&mu_);
|
|
a = 0;
|
|
rlock.Release();
|
|
a = 1; // expected-warning {{writing variable 'a' requires holding mutex 'mu_' exclusively}}
|
|
}
|
|
|
|
void Foo::test4() {
|
|
ReleasableMutexLock rlock(&mu_);
|
|
rlock.Release(); // expected-note{{mutex released here}}
|
|
rlock.Release(); // expected-warning {{releasing mutex 'mu_' that was not held}}
|
|
}
|
|
|
|
void Foo::test5() {
|
|
ReleasableMutexLock rlock(&mu_);
|
|
if (c) {
|
|
rlock.Release();
|
|
}
|
|
// No warning on join point because the lock will be released by the scope object anyway.
|
|
rlock.Release(); // expected-warning {{releasing mutex 'mu_' that was not held}}
|
|
}
|
|
|
|
void Foo::test6() {
|
|
ReleasableMutexLock rlock(&mu_);
|
|
do {
|
|
if (c) {
|
|
rlock.Release();
|
|
break;
|
|
}
|
|
} while (c);
|
|
// No warning on join point because the lock will be released by the scope object anyway
|
|
a = 1; // expected-warning {{writing variable 'a' requires holding mutex 'mu_' exclusively}}
|
|
}
|
|
|
|
|
|
} // end namespace ReleasableScopedLock
|
|
|
|
|
|
namespace RelockableScopedLock {
|
|
|
|
class DeferTraits {};
|
|
|
|
class SCOPED_LOCKABLE RelockableExclusiveMutexLock {
|
|
public:
|
|
RelockableExclusiveMutexLock(Mutex *mu) EXCLUSIVE_LOCK_FUNCTION(mu);
|
|
RelockableExclusiveMutexLock(Mutex *mu, DeferTraits) LOCKS_EXCLUDED(mu);
|
|
~RelockableExclusiveMutexLock() EXCLUSIVE_UNLOCK_FUNCTION();
|
|
|
|
void Lock() EXCLUSIVE_LOCK_FUNCTION();
|
|
void Unlock() UNLOCK_FUNCTION();
|
|
};
|
|
|
|
struct SharedTraits {};
|
|
struct ExclusiveTraits {};
|
|
|
|
class SCOPED_LOCKABLE RelockableMutexLock {
|
|
public:
|
|
RelockableMutexLock(Mutex *mu, DeferTraits) LOCKS_EXCLUDED(mu);
|
|
RelockableMutexLock(Mutex *mu, SharedTraits) SHARED_LOCK_FUNCTION(mu);
|
|
RelockableMutexLock(Mutex *mu, ExclusiveTraits) EXCLUSIVE_LOCK_FUNCTION(mu);
|
|
~RelockableMutexLock() UNLOCK_FUNCTION();
|
|
|
|
void Lock() EXCLUSIVE_LOCK_FUNCTION();
|
|
void Unlock() UNLOCK_FUNCTION();
|
|
|
|
void ReaderLock() SHARED_LOCK_FUNCTION();
|
|
void ReaderUnlock() UNLOCK_FUNCTION();
|
|
|
|
void PromoteShared() UNLOCK_FUNCTION() EXCLUSIVE_LOCK_FUNCTION();
|
|
void DemoteExclusive() UNLOCK_FUNCTION() SHARED_LOCK_FUNCTION();
|
|
};
|
|
|
|
Mutex mu;
|
|
int x GUARDED_BY(mu);
|
|
bool b;
|
|
|
|
void print(int);
|
|
|
|
void relock() {
|
|
RelockableExclusiveMutexLock scope(&mu);
|
|
x = 2;
|
|
scope.Unlock();
|
|
|
|
x = 3; // expected-warning {{writing variable 'x' requires holding mutex 'mu' exclusively}}
|
|
|
|
scope.Lock();
|
|
x = 4;
|
|
}
|
|
|
|
void deferLock() {
|
|
RelockableExclusiveMutexLock scope(&mu, DeferTraits{});
|
|
x = 2; // expected-warning {{writing variable 'x' requires holding mutex 'mu' exclusively}}
|
|
scope.Lock();
|
|
x = 3;
|
|
}
|
|
|
|
void relockExclusive() {
|
|
RelockableMutexLock scope(&mu, SharedTraits{});
|
|
print(x);
|
|
x = 2; // expected-warning {{writing variable 'x' requires holding mutex 'mu' exclusively}}
|
|
scope.ReaderUnlock();
|
|
|
|
print(x); // expected-warning {{reading variable 'x' requires holding mutex 'mu'}}
|
|
|
|
scope.Lock();
|
|
print(x);
|
|
x = 4;
|
|
|
|
scope.DemoteExclusive();
|
|
print(x);
|
|
x = 5; // expected-warning {{writing variable 'x' requires holding mutex 'mu' exclusively}}
|
|
}
|
|
|
|
void relockShared() {
|
|
RelockableMutexLock scope(&mu, ExclusiveTraits{});
|
|
print(x);
|
|
x = 2;
|
|
scope.Unlock();
|
|
|
|
print(x); // expected-warning {{reading variable 'x' requires holding mutex 'mu'}}
|
|
|
|
scope.ReaderLock();
|
|
print(x);
|
|
x = 4; // expected-warning {{writing variable 'x' requires holding mutex 'mu' exclusively}}
|
|
|
|
scope.PromoteShared();
|
|
print(x);
|
|
x = 5;
|
|
}
|
|
|
|
void deferLockShared() {
|
|
RelockableMutexLock scope(&mu, DeferTraits{});
|
|
print(x); // expected-warning {{reading variable 'x' requires holding mutex 'mu'}}
|
|
scope.ReaderLock();
|
|
print(x);
|
|
x = 2; // expected-warning {{writing variable 'x' requires holding mutex 'mu' exclusively}}
|
|
}
|
|
|
|
void doubleUnlock() {
|
|
RelockableExclusiveMutexLock scope(&mu);
|
|
scope.Unlock(); // expected-note{{mutex released here}}
|
|
scope.Unlock(); // expected-warning {{releasing mutex 'mu' that was not held}}
|
|
}
|
|
|
|
void doubleLock1() {
|
|
RelockableExclusiveMutexLock scope(&mu); // expected-note{{mutex acquired here}}
|
|
scope.Lock(); // expected-warning {{acquiring mutex 'mu' that is already held}}
|
|
}
|
|
|
|
void doubleLock2() {
|
|
RelockableExclusiveMutexLock scope(&mu);
|
|
scope.Unlock();
|
|
scope.Lock(); // expected-note{{mutex acquired here}}
|
|
scope.Lock(); // expected-warning {{acquiring mutex 'mu' that is already held}}
|
|
}
|
|
|
|
void lockJoin() {
|
|
RelockableMutexLock scope(&mu, DeferTraits{});
|
|
if (b)
|
|
scope.Lock();
|
|
// No warning on join point because the lock will be released by the scope object anyway.
|
|
x = 2; // expected-warning {{writing variable 'x' requires holding mutex 'mu' exclusively}}
|
|
}
|
|
|
|
void unlockJoin() {
|
|
RelockableMutexLock scope(&mu, DeferTraits{});
|
|
scope.Lock();
|
|
if (b)
|
|
scope.Unlock();
|
|
// No warning on join point because the lock will be released by the scope object anyway.
|
|
x = 2; // expected-warning {{writing variable 'x' requires holding mutex 'mu' exclusively}}
|
|
}
|
|
|
|
void loopAcquire() {
|
|
RelockableMutexLock scope(&mu, DeferTraits{});
|
|
for (unsigned i = 1; i < 10; ++i)
|
|
scope.Lock(); // We could catch this double lock with negative capabilities.
|
|
}
|
|
|
|
void loopRelease() {
|
|
RelockableMutexLock scope(&mu, ExclusiveTraits{}); // expected-note {{mutex acquired here}}
|
|
// We have to warn on this join point despite the lock being managed ...
|
|
for (unsigned i = 1; i < 10; ++i) { // expected-warning {{expecting mutex 'mu' to be held at start of each loop}}
|
|
x = 1; // ... because we might miss that this doesn't always happen under lock.
|
|
if (i == 5)
|
|
scope.Unlock();
|
|
}
|
|
}
|
|
|
|
void loopPromote() {
|
|
RelockableMutexLock scope(&mu, SharedTraits{});
|
|
for (unsigned i = 1; i < 10; ++i) {
|
|
x = 1; // expected-warning {{writing variable 'x' requires holding mutex 'mu' exclusively}}
|
|
if (i == 5)
|
|
scope.PromoteShared();
|
|
}
|
|
}
|
|
|
|
void loopDemote() {
|
|
RelockableMutexLock scope(&mu, ExclusiveTraits{}); // expected-note {{the other acquisition of mutex 'mu' is here}}
|
|
// We have to warn on this join point despite the lock being managed ...
|
|
for (unsigned i = 1; i < 10; ++i) {
|
|
x = 1; // ... because we might miss that this doesn't always happen under exclusive lock.
|
|
if (i == 5)
|
|
scope.DemoteExclusive(); // expected-warning {{mutex 'mu' is acquired exclusively and shared in the same scope}}
|
|
}
|
|
}
|
|
|
|
void loopAcquireContinue() {
|
|
RelockableMutexLock scope(&mu, DeferTraits{});
|
|
for (unsigned i = 1; i < 10; ++i) {
|
|
x = 1; // expected-warning {{writing variable 'x' requires holding mutex 'mu' exclusively}}
|
|
if (i == 5) {
|
|
scope.Lock();
|
|
continue;
|
|
}
|
|
}
|
|
}
|
|
|
|
void loopReleaseContinue() {
|
|
RelockableMutexLock scope(&mu, ExclusiveTraits{}); // expected-note {{mutex acquired here}}
|
|
// We have to warn on this join point despite the lock being managed ...
|
|
for (unsigned i = 1; i < 10; ++i) { // expected-warning {{expecting mutex 'mu' to be held at start of each loop}}
|
|
x = 1; // ... because we might miss that this doesn't always happen under lock.
|
|
if (i == 5) {
|
|
scope.Unlock();
|
|
continue;
|
|
}
|
|
}
|
|
}
|
|
|
|
void loopPromoteContinue() {
|
|
RelockableMutexLock scope(&mu, SharedTraits{});
|
|
for (unsigned i = 1; i < 10; ++i) {
|
|
x = 1; // expected-warning {{writing variable 'x' requires holding mutex 'mu' exclusively}}
|
|
if (i == 5) {
|
|
scope.PromoteShared();
|
|
continue;
|
|
}
|
|
}
|
|
}
|
|
|
|
void loopDemoteContinue() {
|
|
RelockableMutexLock scope(&mu, ExclusiveTraits{}); // expected-note {{the other acquisition of mutex 'mu' is here}}
|
|
// We have to warn on this join point despite the lock being managed ...
|
|
for (unsigned i = 1; i < 10; ++i) {
|
|
x = 1; // ... because we might miss that this doesn't always happen under exclusive lock.
|
|
if (i == 5) {
|
|
scope.DemoteExclusive(); // expected-warning {{mutex 'mu' is acquired exclusively and shared in the same scope}}
|
|
continue;
|
|
}
|
|
}
|
|
}
|
|
|
|
void exclusiveSharedJoin() {
|
|
RelockableMutexLock scope(&mu, DeferTraits{});
|
|
if (b)
|
|
scope.Lock();
|
|
else
|
|
scope.ReaderLock();
|
|
// No warning on join point because the lock will be released by the scope object anyway.
|
|
print(x);
|
|
x = 2; // expected-warning {{writing variable 'x' requires holding mutex 'mu' exclusively}}
|
|
}
|
|
|
|
void sharedExclusiveJoin() {
|
|
RelockableMutexLock scope(&mu, DeferTraits{});
|
|
if (b)
|
|
scope.ReaderLock();
|
|
else
|
|
scope.Lock();
|
|
// No warning on join point because the lock will be released by the scope object anyway.
|
|
print(x);
|
|
x = 2; // expected-warning {{writing variable 'x' requires holding mutex 'mu' exclusively}}
|
|
}
|
|
|
|
void assertJoin() {
|
|
RelockableMutexLock scope(&mu, DeferTraits{});
|
|
if (b)
|
|
scope.Lock();
|
|
else
|
|
mu.AssertHeld();
|
|
x = 2;
|
|
}
|
|
|
|
void assertSharedJoin() {
|
|
RelockableMutexLock scope(&mu, DeferTraits{});
|
|
if (b)
|
|
scope.ReaderLock();
|
|
else
|
|
mu.AssertReaderHeld();
|
|
print(x);
|
|
x = 2; // expected-warning {{writing variable 'x' requires holding mutex 'mu' exclusively}}
|
|
}
|
|
|
|
void assertStrongerJoin() {
|
|
RelockableMutexLock scope(&mu, DeferTraits{});
|
|
if (b)
|
|
scope.ReaderLock();
|
|
else
|
|
mu.AssertHeld();
|
|
print(x);
|
|
x = 2; // expected-warning {{writing variable 'x' requires holding mutex 'mu' exclusively}}
|
|
}
|
|
|
|
void assertWeakerJoin() {
|
|
RelockableMutexLock scope(&mu, DeferTraits{});
|
|
if (b)
|
|
scope.Lock();
|
|
else
|
|
mu.AssertReaderHeld();
|
|
print(x);
|
|
x = 2; // expected-warning {{writing variable 'x' requires holding mutex 'mu' exclusively}}
|
|
}
|
|
|
|
void directUnlock() {
|
|
RelockableExclusiveMutexLock scope(&mu);
|
|
mu.Unlock();
|
|
// Debatable that there is no warning. Currently we don't track in the scoped
|
|
// object whether it is active, but just check if the contained locks can be
|
|
// reacquired. Here they can, because mu has been unlocked manually.
|
|
scope.Lock();
|
|
}
|
|
|
|
void directRelock() {
|
|
RelockableExclusiveMutexLock scope(&mu);
|
|
scope.Unlock();
|
|
mu.Lock();
|
|
// Similarly debatable that there is no warning.
|
|
scope.Unlock();
|
|
}
|
|
|
|
// Doesn't make a lot of sense, just making sure there is no crash.
|
|
void destructLock() {
|
|
RelockableExclusiveMutexLock scope(&mu);
|
|
scope.~RelockableExclusiveMutexLock();
|
|
scope.Lock(); // Should be UB, so we don't really care.
|
|
}
|
|
|
|
class SCOPED_LOCKABLE MemberLock {
|
|
public:
|
|
MemberLock() EXCLUSIVE_LOCK_FUNCTION(mutex);
|
|
~MemberLock() UNLOCK_FUNCTION(mutex);
|
|
void Lock() EXCLUSIVE_LOCK_FUNCTION(mutex);
|
|
Mutex mutex;
|
|
};
|
|
|
|
void relockShared2() {
|
|
MemberLock lock; // expected-note{{mutex acquired here}}
|
|
lock.Lock(); // expected-warning {{acquiring mutex 'lock.mutex' that is already held}}
|
|
}
|
|
|
|
class SCOPED_LOCKABLE WeirdScope {
|
|
private:
|
|
Mutex *other;
|
|
|
|
public:
|
|
WeirdScope(Mutex *mutex) EXCLUSIVE_LOCK_FUNCTION(mutex);
|
|
void unlock() EXCLUSIVE_UNLOCK_FUNCTION() EXCLUSIVE_UNLOCK_FUNCTION(other);
|
|
void lock() EXCLUSIVE_LOCK_FUNCTION() EXCLUSIVE_LOCK_FUNCTION(other);
|
|
~WeirdScope() EXCLUSIVE_UNLOCK_FUNCTION();
|
|
|
|
void requireOther() EXCLUSIVE_LOCKS_REQUIRED(other);
|
|
};
|
|
|
|
void relockWeird() {
|
|
WeirdScope scope(&mu);
|
|
x = 1;
|
|
scope.unlock(); // expected-warning {{releasing mutex 'scope.other' that was not held}}
|
|
x = 2; // \
|
|
// expected-warning {{writing variable 'x' requires holding mutex 'mu' exclusively}}
|
|
scope.requireOther(); // \
|
|
// expected-warning {{calling function 'requireOther' requires holding mutex 'scope.other' exclusively}}
|
|
scope.lock(); // expected-note {{mutex acquired here}}
|
|
x = 3;
|
|
scope.requireOther();
|
|
} // expected-warning {{mutex 'scope.other' is still held at the end of function}}
|
|
|
|
} // end namespace RelockableScopedLock
|
|
|
|
|
|
namespace ScopedUnlock {
|
|
|
|
class SCOPED_LOCKABLE MutexUnlock {
|
|
public:
|
|
MutexUnlock(Mutex *mu) EXCLUSIVE_UNLOCK_FUNCTION(mu);
|
|
~MutexUnlock() EXCLUSIVE_UNLOCK_FUNCTION();
|
|
|
|
void Lock() EXCLUSIVE_UNLOCK_FUNCTION();
|
|
void Unlock() EXCLUSIVE_LOCK_FUNCTION();
|
|
};
|
|
|
|
class SCOPED_LOCKABLE ReaderMutexUnlock {
|
|
public:
|
|
ReaderMutexUnlock(Mutex *mu) SHARED_UNLOCK_FUNCTION(mu);
|
|
~ReaderMutexUnlock() EXCLUSIVE_UNLOCK_FUNCTION();
|
|
|
|
void Lock() EXCLUSIVE_UNLOCK_FUNCTION();
|
|
void Unlock() EXCLUSIVE_LOCK_FUNCTION();
|
|
};
|
|
|
|
template<typename... Mus>
|
|
class SCOPED_LOCKABLE VariadicMutexUnlock {
|
|
public:
|
|
VariadicMutexUnlock(Mus *...mus) EXCLUSIVE_UNLOCK_FUNCTION(mus...);
|
|
~VariadicMutexUnlock() EXCLUSIVE_UNLOCK_FUNCTION();
|
|
|
|
void Lock() EXCLUSIVE_UNLOCK_FUNCTION();
|
|
void Unlock() EXCLUSIVE_LOCK_FUNCTION();
|
|
};
|
|
|
|
Mutex mu;
|
|
int x GUARDED_BY(mu);
|
|
bool c;
|
|
void print(int);
|
|
|
|
void simple() EXCLUSIVE_LOCKS_REQUIRED(mu) {
|
|
x = 1;
|
|
MutexUnlock scope(&mu);
|
|
x = 2; // expected-warning {{writing variable 'x' requires holding mutex 'mu' exclusively}}
|
|
}
|
|
|
|
void simpleShared() SHARED_LOCKS_REQUIRED(mu) {
|
|
print(x);
|
|
ReaderMutexUnlock scope(&mu);
|
|
print(x); // expected-warning {{reading variable 'x' requires holding mutex 'mu'}}
|
|
}
|
|
|
|
void innerUnlock() {
|
|
MutexLock outer(&mu);
|
|
if (x == 0) {
|
|
MutexUnlock inner(&mu);
|
|
x = 1; // expected-warning {{writing variable 'x' requires holding mutex 'mu' exclusively}}
|
|
}
|
|
x = 2;
|
|
}
|
|
|
|
void innerUnlockShared() {
|
|
ReaderMutexLock outer(&mu);
|
|
if (x == 0) {
|
|
ReaderMutexUnlock inner(&mu);
|
|
print(x); // expected-warning {{reading variable 'x' requires holding mutex 'mu'}}
|
|
}
|
|
print(x);
|
|
}
|
|
|
|
void manual() EXCLUSIVE_LOCKS_REQUIRED(mu) {
|
|
MutexUnlock scope(&mu);
|
|
scope.Lock();
|
|
x = 2;
|
|
scope.Unlock();
|
|
x = 3; // expected-warning {{writing variable 'x' requires holding mutex 'mu' exclusively}}
|
|
}
|
|
|
|
void join() EXCLUSIVE_LOCKS_REQUIRED(mu) {
|
|
MutexUnlock scope(&mu);
|
|
if (c)
|
|
scope.Lock();
|
|
// No warning on join point because the lock will be released by the scope object anyway.
|
|
scope.Lock();
|
|
}
|
|
|
|
void doubleLock() EXCLUSIVE_LOCKS_REQUIRED(mu) {
|
|
MutexUnlock scope(&mu);
|
|
scope.Lock(); // expected-note{{mutex acquired here}}
|
|
scope.Lock(); // expected-warning {{acquiring mutex 'mu' that is already held}}
|
|
}
|
|
|
|
void doubleUnlock() EXCLUSIVE_LOCKS_REQUIRED(mu) {
|
|
MutexUnlock scope(&mu); // expected-note{{mutex released here}}
|
|
scope.Unlock(); // expected-warning {{releasing mutex 'mu' that was not held}}
|
|
}
|
|
|
|
Mutex mu2;
|
|
int y GUARDED_BY(mu2);
|
|
|
|
void variadic() EXCLUSIVE_LOCKS_REQUIRED(mu, mu2) {
|
|
VariadicMutexUnlock<Mutex, Mutex> scope(&mu, &mu2);
|
|
x = 2; // expected-warning {{writing variable 'x' requires holding mutex 'mu' exclusively}}
|
|
y = 3; // expected-warning {{writing variable 'y' requires holding mutex 'mu2' exclusively}}
|
|
scope.Lock();
|
|
x = y = 4;
|
|
}
|
|
|
|
class SCOPED_LOCKABLE MutexLockUnlock {
|
|
public:
|
|
MutexLockUnlock(Mutex *mu1, Mutex *mu2) EXCLUSIVE_UNLOCK_FUNCTION(mu1) EXCLUSIVE_LOCK_FUNCTION(mu2);
|
|
~MutexLockUnlock() EXCLUSIVE_UNLOCK_FUNCTION();
|
|
|
|
void Release() EXCLUSIVE_UNLOCK_FUNCTION();
|
|
void Acquire() EXCLUSIVE_LOCK_FUNCTION();
|
|
};
|
|
|
|
Mutex other;
|
|
void fn() EXCLUSIVE_LOCKS_REQUIRED(other);
|
|
|
|
void lockUnlock() EXCLUSIVE_LOCKS_REQUIRED(mu) {
|
|
MutexLockUnlock scope(&mu, &other);
|
|
fn();
|
|
x = 1; // expected-warning {{writing variable 'x' requires holding mutex 'mu' exclusively}}
|
|
}
|
|
|
|
} // end namespace ScopedUnlock
|
|
|
|
namespace PassingScope {
|
|
|
|
class SCOPED_LOCKABLE RelockableScope {
|
|
public:
|
|
RelockableScope(Mutex *mu) EXCLUSIVE_LOCK_FUNCTION(mu);
|
|
void Release() UNLOCK_FUNCTION();
|
|
void Acquire() EXCLUSIVE_LOCK_FUNCTION();
|
|
~RelockableScope() EXCLUSIVE_UNLOCK_FUNCTION();
|
|
};
|
|
|
|
class SCOPED_LOCKABLE ReaderRelockableScope {
|
|
public:
|
|
ReaderRelockableScope(Mutex *mu) SHARED_LOCK_FUNCTION(mu);
|
|
void Release() UNLOCK_FUNCTION();
|
|
void Acquire() SHARED_LOCK_FUNCTION();
|
|
~ReaderRelockableScope() UNLOCK_FUNCTION();
|
|
};
|
|
|
|
Mutex mu;
|
|
Mutex mu1;
|
|
Mutex mu2;
|
|
int x GUARDED_BY(mu);
|
|
int y GUARDED_BY(mu) GUARDED_BY(mu2);
|
|
void print(int);
|
|
|
|
// Analysis inside the function with annotated parameters
|
|
|
|
void sharedRequired(ReleasableMutexLock& scope SHARED_LOCKS_REQUIRED(mu)) {
|
|
print(x); // OK.
|
|
}
|
|
|
|
void sharedAcquire(ReaderRelockableScope& scope SHARED_LOCK_FUNCTION(mu)) {
|
|
scope.Acquire();
|
|
print(x);
|
|
}
|
|
|
|
void sharedRelease(RelockableScope& scope SHARED_UNLOCK_FUNCTION(mu)) {
|
|
print(x);
|
|
scope.Release();
|
|
print(x); // expected-warning{{reading variable 'x' requires holding mutex 'mu'}}
|
|
}
|
|
|
|
void requiredLock(ReleasableMutexLock& scope EXCLUSIVE_LOCKS_REQUIRED(mu)) {
|
|
x = 1; // OK.
|
|
}
|
|
|
|
void reacquireRequiredLock(RelockableScope& scope EXCLUSIVE_LOCKS_REQUIRED(mu)) {
|
|
scope.Release();
|
|
scope.Acquire();
|
|
x = 2; // OK.
|
|
}
|
|
|
|
void releaseSingleMutex(ReleasableMutexLock& scope EXCLUSIVE_UNLOCK_FUNCTION(mu)) {
|
|
x = 1; // OK.
|
|
scope.Release();
|
|
x = 2; // expected-warning{{writing variable 'x' requires holding mutex 'mu' exclusively}}
|
|
}
|
|
|
|
void releaseMultipleMutexes(ReleasableMutexLock& scope EXCLUSIVE_UNLOCK_FUNCTION(mu, mu2)) {
|
|
y = 1; // OK.
|
|
scope.Release();
|
|
y = 2; // expected-warning{{writing variable 'y' requires holding mutex 'mu' exclusively}}
|
|
// expected-warning@-1{{writing variable 'y' requires holding mutex 'mu2' exclusively}}
|
|
}
|
|
|
|
void acquireLock(RelockableScope& scope EXCLUSIVE_LOCK_FUNCTION(mu)) {
|
|
x = 1; // expected-warning{{writing variable 'x' requires holding mutex 'mu' exclusively}}
|
|
scope.Acquire();
|
|
x = 2; // OK.
|
|
}
|
|
|
|
void acquireMultipleLocks(RelockableScope& scope EXCLUSIVE_LOCK_FUNCTION(mu, mu2)) {
|
|
y = 1; // expected-warning{{writing variable 'y' requires holding mutex 'mu' exclusively}}
|
|
// expected-warning@-1{{writing variable 'y' requires holding mutex 'mu2' exclusively}}
|
|
scope.Acquire();
|
|
y = 2; // OK.
|
|
}
|
|
|
|
void excludedLock(ReleasableMutexLock& scope LOCKS_EXCLUDED(mu)) {
|
|
x = 1; // expected-warning{{writing variable 'x' requires holding mutex 'mu' exclusively}}
|
|
}
|
|
|
|
void acquireAndReleaseExcludedLocks(RelockableScope& scope LOCKS_EXCLUDED(mu)) {
|
|
scope.Acquire();
|
|
scope.Release();
|
|
}
|
|
|
|
// expected-note@+1{{mutex acquired here}}
|
|
void unreleasedMutex(ReleasableMutexLock& scope EXCLUSIVE_UNLOCK_FUNCTION(mu)) {
|
|
x = 1; // OK.
|
|
} // expected-warning{{mutex 'mu' is still held at the end of function}}
|
|
|
|
// expected-note@+1{{mutex acquired here}}
|
|
void acquireAlreadyHeldMutex(RelockableScope& scope EXCLUSIVE_UNLOCK_FUNCTION(mu)) {
|
|
scope.Acquire(); // expected-warning{{acquiring mutex 'mu' that is already held}}
|
|
scope.Release();
|
|
}
|
|
|
|
void reacquireMutex(RelockableScope& scope EXCLUSIVE_UNLOCK_FUNCTION(mu)) {
|
|
scope.Release();
|
|
scope.Acquire(); // expected-note{{mutex acquired here}}
|
|
x = 2; // OK.
|
|
} // expected-warning{{mutex 'mu' is still held at the end of function}}
|
|
|
|
// expected-note@+1{{mutex acquired here}}
|
|
void requireSingleMutex(ReleasableMutexLock& scope EXCLUSIVE_LOCKS_REQUIRED(mu)) {
|
|
x = 1; // OK.
|
|
scope.Release();
|
|
x = 2; // expected-warning{{writing variable 'x' requires holding mutex 'mu' exclusively}}
|
|
} // expected-warning{{expecting mutex 'mu' to be held at the end of function}}
|
|
|
|
// expected-note@+1 2{{mutex acquired here}}
|
|
void requireMultipleMutexes(ReleasableMutexLock& scope EXCLUSIVE_LOCKS_REQUIRED(mu, mu2)) {
|
|
y = 1; // OK.
|
|
scope.Release();
|
|
y = 2; // expected-warning{{writing variable 'y' requires holding mutex 'mu' exclusively}}
|
|
// expected-warning@-1{{writing variable 'y' requires holding mutex 'mu2' exclusively}}
|
|
} // expected-warning{{expecting mutex 'mu' to be held at the end of function}}
|
|
// expected-warning@-1{{expecting mutex 'mu2' to be held at the end of function}}
|
|
|
|
// expected-note@+1{{mutex acquired here}}
|
|
void acquireAlreadyHeldLock(RelockableScope& scope EXCLUSIVE_LOCKS_REQUIRED(mu)) {
|
|
scope.Acquire(); // expected-warning{{acquiring mutex 'mu' that is already held}}
|
|
}
|
|
|
|
// expected-note@+1 {{mutex acquired here}}
|
|
void releaseWithoutHoldingLock(ReleasableMutexLock& scope EXCLUSIVE_LOCK_FUNCTION(mu)) {
|
|
scope.Release(); // expected-warning{{releasing mutex 'mu' that was not held}}
|
|
} // expected-warning{{expecting mutex 'mu' to be held at the end of function}}
|
|
|
|
// expected-note@+1 {{mutex acquired here}}
|
|
void endWithReleasedMutex(RelockableScope& scope EXCLUSIVE_LOCK_FUNCTION(mu)) {
|
|
scope.Acquire();
|
|
scope.Release();
|
|
} // expected-warning{{expecting mutex 'mu' to be held at the end of function}}
|
|
|
|
void acquireExcludedLock(RelockableScope& scope LOCKS_EXCLUDED(mu)) {
|
|
x = 1; // expected-warning{{writing variable 'x' requires holding mutex 'mu' exclusively}}
|
|
scope.Acquire(); // expected-note {{mutex acquired here}}
|
|
x = 2; // OK.
|
|
} // expected-warning{{mutex 'mu' is still held at the end of function}}
|
|
|
|
void acquireMultipleExcludedLocks(RelockableScope& scope LOCKS_EXCLUDED(mu, mu2)) {
|
|
y = 1; // expected-warning{{writing variable 'y' requires holding mutex 'mu' exclusively}}
|
|
// expected-warning@-1{{writing variable 'y' requires holding mutex 'mu2' exclusively}}
|
|
scope.Acquire(); // expected-note 2{{mutex acquired here}}
|
|
y = 2; // OK.
|
|
} // expected-warning{{mutex 'mu' is still held at the end of function}}
|
|
// expected-warning@-1{{mutex 'mu2' is still held at the end of function}}
|
|
|
|
void reacquireExcludedLocks(RelockableScope& scope LOCKS_EXCLUDED(mu)) {
|
|
scope.Release(); // expected-warning{{releasing mutex 'mu' that was not held}}
|
|
scope.Acquire(); // expected-note {{mutex acquired here}}
|
|
x = 2; // OK.
|
|
} // expected-warning{{mutex 'mu' is still held at the end of function}}
|
|
|
|
// expected-note@+1{{mutex acquired here}}
|
|
void sharedRequired2(ReleasableMutexLock& scope SHARED_LOCKS_REQUIRED(mu)) {
|
|
print(x); // OK.
|
|
scope.Release();
|
|
print(x); // expected-warning{{reading variable 'x' requires holding mutex 'mu'}}
|
|
} // expected-warning{{expecting mutex 'mu' to be held at the end of function}}
|
|
|
|
// expected-note@+1{{mutex acquired here}}
|
|
void sharedAcquire2(RelockableScope& scope SHARED_LOCK_FUNCTION(mu)) {
|
|
print(x); // expected-warning{{reading variable 'x' requires holding mutex 'mu'}}
|
|
scope.Release(); // expected-warning{{releasing mutex 'mu' that was not held}}
|
|
} // expected-warning{{expecting mutex 'mu' to be held at the end of function}}
|
|
|
|
// expected-note@+1 2{{mutex acquired here}}
|
|
void sharedRelease2(RelockableScope& scope SHARED_UNLOCK_FUNCTION(mu)) {
|
|
scope.Acquire(); //expected-warning{{acquiring mutex 'mu' that is already held}}
|
|
} //expected-warning{{mutex 'mu' is still held at the end of function}}
|
|
|
|
// Handling the call of the function with annotated parameters
|
|
|
|
// expected-note@+1{{see attribute on parameter here}}
|
|
void release(ReleasableMutexLock& scope EXCLUSIVE_UNLOCK_FUNCTION(mu));
|
|
// expected-note@+1{{see attribute on parameter here}}
|
|
void release_DoubleMutexLock(DoubleMutexLock& scope EXCLUSIVE_UNLOCK_FUNCTION(mu));
|
|
// expected-note@+1 2{{see attribute on parameter here}}
|
|
void release_two(ReleasableMutexLock& scope EXCLUSIVE_UNLOCK_FUNCTION(mu, mu2));
|
|
// expected-note@+1{{see attribute on parameter here}}
|
|
void release_double(DoubleMutexLock& scope EXCLUSIVE_UNLOCK_FUNCTION(mu, mu2));
|
|
void require(ReleasableMutexLock& scope EXCLUSIVE_LOCKS_REQUIRED(mu));
|
|
void acquire(RelockableScope& scope EXCLUSIVE_LOCK_FUNCTION(mu));
|
|
void exclude(RelockableScope& scope LOCKS_EXCLUDED(mu));
|
|
|
|
void release_shared(ReaderRelockableScope& scope SHARED_UNLOCK_FUNCTION(mu));
|
|
void require_shared(ReleasableMutexLock& scope SHARED_LOCKS_REQUIRED(mu));
|
|
void acquire_shared(ReaderRelockableScope& scope SHARED_LOCK_FUNCTION(mu));
|
|
|
|
void unlockCall() {
|
|
ReleasableMutexLock scope(&mu);
|
|
x = 1; // OK.
|
|
release(scope);
|
|
x = 2; // expected-warning{{writing variable 'x' requires holding mutex 'mu' exclusively}}
|
|
}
|
|
|
|
void unlockSharedCall() {
|
|
ReaderRelockableScope scope(&mu);
|
|
print(x); // OK.
|
|
release_shared(scope);
|
|
print(x); // expected-warning{{reading variable 'x' requires holding mutex 'mu'}}
|
|
}
|
|
|
|
void requireCall() {
|
|
ReleasableMutexLock scope(&mu);
|
|
x = 1; // OK.
|
|
require(scope);
|
|
x = 2; // Ok.
|
|
}
|
|
|
|
void requireSharedCall() {
|
|
ReleasableMutexLock scope(&mu);
|
|
print(x); // OK.
|
|
require_shared(scope);
|
|
print(x); // Ok.
|
|
}
|
|
|
|
void acquireCall() {
|
|
RelockableScope scope(&mu);
|
|
scope.Release();
|
|
acquire(scope);
|
|
x = 2; // Ok.
|
|
}
|
|
|
|
void acquireSharedCall() {
|
|
ReaderRelockableScope scope(&mu);
|
|
scope.Release();
|
|
acquire_shared(scope);
|
|
print(x); // Ok.
|
|
}
|
|
|
|
void writeAfterExcludeCall() {
|
|
RelockableScope scope(&mu);
|
|
scope.Release();
|
|
exclude(scope);
|
|
x = 2; // expected-warning{{writing variable 'x' requires holding mutex 'mu' exclusively}}
|
|
}
|
|
|
|
void unlockCallAfterExplicitRelease() {
|
|
ReleasableMutexLock scope(&mu);
|
|
x = 1; // OK.
|
|
scope.Release(); // expected-note{{mutex released here}}
|
|
release(scope); // expected-warning{{releasing mutex 'mu' that was not held}}
|
|
x = 2; // expected-warning{{writing variable 'x' requires holding mutex 'mu' exclusively}}
|
|
}
|
|
|
|
void unmatchedMutexes() {
|
|
ReleasableMutexLock scope(&mu2);
|
|
release(scope); // expected-warning{{mutex managed by 'scope' is 'mu2' instead of 'mu'}}
|
|
// expected-warning@-1{{releasing mutex 'mu' that was not held}}
|
|
}
|
|
|
|
void wrongOrder() {
|
|
DoubleMutexLock scope(&mu2, &mu);
|
|
release_double(scope); // expected-warning{{mutex managed by 'scope' is 'mu2' instead of 'mu'}}
|
|
}
|
|
|
|
void differentNumberOfMutexes() {
|
|
ReleasableMutexLock scope(&mu);
|
|
release_two(scope); // expected-warning{{mutex 'mu2' not managed by 'scope'}}
|
|
// expected-warning@-1{{releasing mutex 'mu2' that was not held}}
|
|
}
|
|
|
|
void differentNumberOfMutexes2() {
|
|
ReleasableMutexLock scope(&mu2);
|
|
release_two(scope); // expected-warning{{mutex managed by 'scope' is 'mu2' instead of 'mu'}}
|
|
// expected-warning@-1{{releasing mutex 'mu' that was not held}}
|
|
}
|
|
|
|
void differentNumberOfMutexes3() {
|
|
DoubleMutexLock scope(&mu, &mu2);
|
|
release_DoubleMutexLock(scope); // expected-warning{{did not expect mutex 'mu2' to be managed by 'scope'}}
|
|
}
|
|
|
|
void releaseDefault(ReleasableMutexLock& scope EXCLUSIVE_UNLOCK_FUNCTION(mu), int = 0);
|
|
|
|
void unlockFunctionDefault() {
|
|
ReleasableMutexLock scope(&mu);
|
|
x = 1; // OK.
|
|
releaseDefault(scope);
|
|
x = 2; // expected-warning{{writing variable 'x' requires holding mutex 'mu' exclusively}}
|
|
}
|
|
|
|
void requireCallWithReleasedLock() {
|
|
ReleasableMutexLock scope(&mu);
|
|
scope.Release();
|
|
require(scope); // expected-warning{{calling function 'require' requires holding mutex 'mu' exclusively}}
|
|
}
|
|
|
|
void acquireCallWithAlreadyHeldLock() {
|
|
RelockableScope scope(&mu); // expected-note{{mutex acquired here}}
|
|
acquire(scope); // expected-warning{{acquiring mutex 'mu' that is already held}}
|
|
x = 1;
|
|
}
|
|
|
|
void excludeCallWithAlreadyHeldLock() {
|
|
RelockableScope scope(&mu);
|
|
exclude(scope); // expected-warning{{cannot call function 'exclude' while mutex 'mu' is held}}
|
|
x = 2; // OK.
|
|
}
|
|
|
|
void requireConst(const ReleasableMutexLock& scope EXCLUSIVE_LOCKS_REQUIRED(mu));
|
|
void requireConstCall() {
|
|
requireConst(ReleasableMutexLock(&mu));
|
|
}
|
|
|
|
void passScopeUndeclared(ReleasableMutexLock &scope) {
|
|
release(scope); // expected-warning{{calling function 'release' requires holding mutex 'scope' exclusively}}
|
|
// expected-warning@-1{{releasing mutex 'mu' that was not held}}
|
|
}
|
|
|
|
class SCOPED_LOCKABLE ScopedWithoutLock {
|
|
public:
|
|
ScopedWithoutLock();
|
|
|
|
~ScopedWithoutLock() EXCLUSIVE_UNLOCK_FUNCTION();
|
|
};
|
|
|
|
void require(ScopedWithoutLock &scope EXCLUSIVE_LOCKS_REQUIRED(mu));
|
|
|
|
void constructWithoutLock() {
|
|
ScopedWithoutLock scope;
|
|
require(scope); // expected-warning{{calling function 'require' requires holding mutex 'mu' exclusively}}
|
|
// expected-warning@-1{{calling function 'require' requires holding mutex 'scope' exclusively}}
|
|
} // expected-warning {{releasing mutex 'scope' that was not held}}
|
|
|
|
void requireConst(const ScopedWithoutLock& scope EXCLUSIVE_LOCKS_REQUIRED(mu));
|
|
|
|
void requireCallWithReleasedLock2() {
|
|
requireConst(ScopedWithoutLock());
|
|
// expected-warning@-1{{calling function 'requireConst' requires holding mutex '#undefined' exclusively}}
|
|
// expected-warning@-2{{calling function 'requireConst' requires holding mutex 'mu' exclusively}}
|
|
}
|
|
|
|
void requireDecl(RelockableScope &scope EXCLUSIVE_LOCKS_REQUIRED(mu));
|
|
void requireDecl(RelockableScope &scope) {
|
|
scope.Release();
|
|
scope.Acquire();
|
|
}
|
|
|
|
struct foo
|
|
{
|
|
Mutex mu;
|
|
void require(RelockableScope &scope EXCLUSIVE_LOCKS_REQUIRED(mu));
|
|
void callRequire(){
|
|
RelockableScope scope(&mu);
|
|
require(scope);
|
|
}
|
|
};
|
|
|
|
struct ObjectWithMutex {
|
|
Mutex mu;
|
|
int x GUARDED_BY(mu);
|
|
};
|
|
void releaseMember(ObjectWithMutex& object, ReleasableMutexLock& scope EXCLUSIVE_UNLOCK_FUNCTION(object.mu)) {
|
|
object.x = 1;
|
|
scope.Release();
|
|
}
|
|
void releaseMemberCall() {
|
|
ObjectWithMutex obj;
|
|
ReleasableMutexLock lock(&obj.mu);
|
|
releaseMember(obj, lock);
|
|
}
|
|
|
|
} // end namespace PassingScope
|
|
|
|
namespace TrylockFunctionTest {
|
|
|
|
class Foo {
|
|
public:
|
|
Mutex mu1_;
|
|
Mutex mu2_;
|
|
bool c;
|
|
|
|
bool lockBoth() EXCLUSIVE_TRYLOCK_FUNCTION(true, mu1_, mu2_);
|
|
};
|
|
|
|
bool Foo::lockBoth() {
|
|
if (!mu1_.TryLock())
|
|
return false;
|
|
|
|
mu2_.Lock();
|
|
if (!c) {
|
|
mu1_.Unlock();
|
|
mu2_.Unlock();
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
} // end namespace TrylockFunctionTest
|
|
|
|
|
|
|
|
namespace DoubleLockBug {
|
|
|
|
class Foo {
|
|
public:
|
|
Mutex mu_;
|
|
int a GUARDED_BY(mu_);
|
|
|
|
void foo1() EXCLUSIVE_LOCKS_REQUIRED(mu_);
|
|
int foo2() SHARED_LOCKS_REQUIRED(mu_);
|
|
};
|
|
|
|
|
|
void Foo::foo1() EXCLUSIVE_LOCKS_REQUIRED(mu_) {
|
|
a = 0;
|
|
}
|
|
|
|
int Foo::foo2() SHARED_LOCKS_REQUIRED(mu_) {
|
|
return a;
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
namespace UnlockBug {
|
|
|
|
class Foo {
|
|
public:
|
|
Mutex mutex_;
|
|
|
|
void foo1() EXCLUSIVE_LOCKS_REQUIRED(mutex_) { // expected-note {{mutex acquired here}}
|
|
mutex_.Unlock();
|
|
} // expected-warning {{expecting mutex 'mutex_' to be held at the end of function}}
|
|
|
|
|
|
void foo2() SHARED_LOCKS_REQUIRED(mutex_) { // expected-note {{mutex acquired here}}
|
|
mutex_.Unlock();
|
|
} // expected-warning {{expecting mutex 'mutex_' to be held at the end of function}}
|
|
};
|
|
|
|
} // end namespace UnlockBug
|
|
|
|
|
|
|
|
namespace FoolishScopedLockableBug {
|
|
|
|
class SCOPED_LOCKABLE WTF_ScopedLockable {
|
|
public:
|
|
WTF_ScopedLockable(Mutex* mu) EXCLUSIVE_LOCK_FUNCTION(mu);
|
|
|
|
// have to call release() manually;
|
|
~WTF_ScopedLockable();
|
|
|
|
void release() UNLOCK_FUNCTION();
|
|
};
|
|
|
|
|
|
class Foo {
|
|
Mutex mu_;
|
|
int a GUARDED_BY(mu_);
|
|
bool c;
|
|
|
|
void doSomething();
|
|
|
|
void test1() {
|
|
WTF_ScopedLockable wtf(&mu_);
|
|
wtf.release();
|
|
}
|
|
|
|
void test2() {
|
|
WTF_ScopedLockable wtf(&mu_); // expected-note {{mutex acquired here}}
|
|
} // expected-warning {{mutex 'mu_' is still held at the end of function}}
|
|
|
|
void test3() {
|
|
if (c) {
|
|
WTF_ScopedLockable wtf(&mu_);
|
|
wtf.release();
|
|
}
|
|
}
|
|
|
|
void test4() {
|
|
if (c) {
|
|
doSomething();
|
|
}
|
|
else {
|
|
WTF_ScopedLockable wtf(&mu_);
|
|
wtf.release();
|
|
}
|
|
}
|
|
|
|
void test5() {
|
|
if (c) {
|
|
WTF_ScopedLockable wtf(&mu_); // expected-note {{mutex acquired here}}
|
|
}
|
|
} // expected-warning {{mutex 'mu_' is not held on every path through here}}
|
|
|
|
void test6() {
|
|
if (c) {
|
|
doSomething();
|
|
}
|
|
else {
|
|
WTF_ScopedLockable wtf(&mu_); // expected-note {{mutex acquired here}}
|
|
}
|
|
} // expected-warning {{mutex 'mu_' is not held on every path through here}}
|
|
};
|
|
|
|
|
|
} // end namespace FoolishScopedLockableBug
|
|
|
|
|
|
|
|
namespace TemporaryCleanupExpr {
|
|
|
|
class Foo {
|
|
int a GUARDED_BY(getMutexPtr().get());
|
|
|
|
SmartPtr<Mutex> getMutexPtr();
|
|
|
|
void test();
|
|
};
|
|
|
|
|
|
void Foo::test() {
|
|
{
|
|
ReaderMutexLock lock(getMutexPtr().get());
|
|
int b = a;
|
|
}
|
|
int b = a; // expected-warning {{reading variable 'a' requires holding mutex 'getMutexPtr()'}}
|
|
}
|
|
|
|
#ifdef __cpp_guaranteed_copy_elision
|
|
|
|
void guaranteed_copy_elision() {
|
|
MutexLock lock = MutexLock{&sls_mu};
|
|
sls_guard_var = 0;
|
|
}
|
|
|
|
void guaranteed_copy_elision_const() {
|
|
const MutexLock lock = MutexLock{&sls_mu};
|
|
sls_guard_var = 0;
|
|
}
|
|
|
|
#endif
|
|
|
|
} // end namespace TemporaryCleanupExpr
|
|
|
|
|
|
|
|
namespace SmartPointerTests {
|
|
|
|
class Foo {
|
|
public:
|
|
SmartPtr<Mutex> mu_;
|
|
int a GUARDED_BY(mu_);
|
|
int b GUARDED_BY(mu_.get());
|
|
int c GUARDED_BY(*mu_);
|
|
|
|
void Lock() EXCLUSIVE_LOCK_FUNCTION(mu_);
|
|
void Unlock() UNLOCK_FUNCTION(mu_);
|
|
|
|
void test0();
|
|
void test1();
|
|
void test2();
|
|
void test3();
|
|
void test4();
|
|
void test5();
|
|
void test6();
|
|
void test7();
|
|
void test8();
|
|
};
|
|
|
|
void Foo::test0() {
|
|
a = 0; // expected-warning {{writing variable 'a' requires holding mutex 'mu_' exclusively}}
|
|
b = 0; // expected-warning {{writing variable 'b' requires holding mutex 'mu_' exclusively}}
|
|
c = 0; // expected-warning {{writing variable 'c' requires holding mutex 'mu_' exclusively}}
|
|
}
|
|
|
|
void Foo::test1() {
|
|
mu_->Lock();
|
|
a = 0;
|
|
b = 0;
|
|
c = 0;
|
|
mu_->Unlock();
|
|
}
|
|
|
|
void Foo::test2() {
|
|
(*mu_).Lock();
|
|
a = 0;
|
|
b = 0;
|
|
c = 0;
|
|
(*mu_).Unlock();
|
|
}
|
|
|
|
|
|
void Foo::test3() {
|
|
mu_.get()->Lock();
|
|
a = 0;
|
|
b = 0;
|
|
c = 0;
|
|
mu_.get()->Unlock();
|
|
}
|
|
|
|
|
|
void Foo::test4() {
|
|
MutexLock lock(mu_.get());
|
|
a = 0;
|
|
b = 0;
|
|
c = 0;
|
|
}
|
|
|
|
|
|
void Foo::test5() {
|
|
MutexLock lock(&(*mu_));
|
|
a = 0;
|
|
b = 0;
|
|
c = 0;
|
|
}
|
|
|
|
|
|
void Foo::test6() {
|
|
Lock();
|
|
a = 0;
|
|
b = 0;
|
|
c = 0;
|
|
Unlock();
|
|
}
|
|
|
|
|
|
void Foo::test7() {
|
|
{
|
|
Lock();
|
|
mu_->Unlock();
|
|
}
|
|
{
|
|
mu_->Lock();
|
|
Unlock();
|
|
}
|
|
{
|
|
mu_.get()->Lock();
|
|
mu_->Unlock();
|
|
}
|
|
{
|
|
mu_->Lock();
|
|
mu_.get()->Unlock();
|
|
}
|
|
{
|
|
mu_.get()->Lock();
|
|
(*mu_).Unlock();
|
|
}
|
|
{
|
|
(*mu_).Lock();
|
|
mu_->Unlock();
|
|
}
|
|
}
|
|
|
|
|
|
void Foo::test8() {
|
|
mu_->Lock(); // expected-note 2 {{mutex acquired here}}
|
|
mu_.get()->Lock(); // expected-warning {{acquiring mutex 'mu_' that is already held}}
|
|
(*mu_).Lock(); // expected-warning {{acquiring mutex 'mu_' that is already held}}
|
|
mu_.get()->Unlock(); // expected-note {{mutex released here}}
|
|
Unlock(); // expected-warning {{releasing mutex 'mu_' that was not held}}
|
|
}
|
|
|
|
|
|
class Bar {
|
|
SmartPtr<Foo> foo;
|
|
|
|
void test0();
|
|
void test1();
|
|
void test2();
|
|
void test3();
|
|
};
|
|
|
|
|
|
void Bar::test0() {
|
|
foo->a = 0; // expected-warning {{writing variable 'a' requires holding mutex 'foo->mu_' exclusively}}
|
|
(*foo).b = 0; // expected-warning {{writing variable 'b' requires holding mutex 'foo->mu_' exclusively}}
|
|
foo.get()->c = 0; // expected-warning {{writing variable 'c' requires holding mutex 'foo->mu_' exclusively}}
|
|
}
|
|
|
|
|
|
void Bar::test1() {
|
|
foo->mu_->Lock();
|
|
foo->a = 0;
|
|
(*foo).b = 0;
|
|
foo.get()->c = 0;
|
|
foo->mu_->Unlock();
|
|
}
|
|
|
|
|
|
void Bar::test2() {
|
|
(*foo).mu_->Lock();
|
|
foo->a = 0;
|
|
(*foo).b = 0;
|
|
foo.get()->c = 0;
|
|
foo.get()->mu_->Unlock();
|
|
}
|
|
|
|
|
|
void Bar::test3() {
|
|
MutexLock lock(foo->mu_.get());
|
|
foo->a = 0;
|
|
(*foo).b = 0;
|
|
foo.get()->c = 0;
|
|
}
|
|
|
|
} // end namespace SmartPointerTests
|
|
|
|
|
|
|
|
namespace DuplicateAttributeTest {
|
|
|
|
class LOCKABLE Foo {
|
|
public:
|
|
Mutex mu1_;
|
|
Mutex mu2_;
|
|
Mutex mu3_;
|
|
int a GUARDED_BY(mu1_);
|
|
int b GUARDED_BY(mu2_);
|
|
int c GUARDED_BY(mu3_);
|
|
|
|
void lock() EXCLUSIVE_LOCK_FUNCTION();
|
|
void unlock() UNLOCK_FUNCTION();
|
|
|
|
void lock1() EXCLUSIVE_LOCK_FUNCTION(mu1_);
|
|
void slock1() SHARED_LOCK_FUNCTION(mu1_);
|
|
void lock3() EXCLUSIVE_LOCK_FUNCTION(mu1_, mu2_, mu3_);
|
|
void locklots()
|
|
EXCLUSIVE_LOCK_FUNCTION(mu1_)
|
|
EXCLUSIVE_LOCK_FUNCTION(mu2_)
|
|
EXCLUSIVE_LOCK_FUNCTION(mu1_, mu2_, mu3_);
|
|
|
|
void unlock1() UNLOCK_FUNCTION(mu1_);
|
|
void unlock3() UNLOCK_FUNCTION(mu1_, mu2_, mu3_);
|
|
void unlocklots()
|
|
UNLOCK_FUNCTION(mu1_)
|
|
UNLOCK_FUNCTION(mu2_)
|
|
UNLOCK_FUNCTION(mu1_, mu2_, mu3_);
|
|
};
|
|
|
|
|
|
void Foo::lock() EXCLUSIVE_LOCK_FUNCTION() { }
|
|
void Foo::unlock() UNLOCK_FUNCTION() { }
|
|
|
|
void Foo::lock1() EXCLUSIVE_LOCK_FUNCTION(mu1_) {
|
|
mu1_.Lock();
|
|
}
|
|
|
|
void Foo::slock1() SHARED_LOCK_FUNCTION(mu1_) {
|
|
mu1_.ReaderLock();
|
|
}
|
|
|
|
void Foo::lock3() EXCLUSIVE_LOCK_FUNCTION(mu1_, mu2_, mu3_) {
|
|
mu1_.Lock();
|
|
mu2_.Lock();
|
|
mu3_.Lock();
|
|
}
|
|
|
|
void Foo::locklots()
|
|
EXCLUSIVE_LOCK_FUNCTION(mu1_, mu2_)
|
|
EXCLUSIVE_LOCK_FUNCTION(mu2_, mu3_) {
|
|
mu1_.Lock();
|
|
mu2_.Lock();
|
|
mu3_.Lock();
|
|
}
|
|
|
|
void Foo::unlock1() UNLOCK_FUNCTION(mu1_) {
|
|
mu1_.Unlock();
|
|
}
|
|
|
|
void Foo::unlock3() UNLOCK_FUNCTION(mu1_, mu2_, mu3_) {
|
|
mu1_.Unlock();
|
|
mu2_.Unlock();
|
|
mu3_.Unlock();
|
|
}
|
|
|
|
void Foo::unlocklots()
|
|
UNLOCK_FUNCTION(mu1_, mu2_)
|
|
UNLOCK_FUNCTION(mu2_, mu3_) {
|
|
mu1_.Unlock();
|
|
mu2_.Unlock();
|
|
mu3_.Unlock();
|
|
}
|
|
|
|
|
|
void test0() {
|
|
Foo foo;
|
|
foo.lock();
|
|
foo.unlock();
|
|
|
|
foo.lock(); // expected-note{{mutex acquired here}}
|
|
foo.lock(); // expected-warning {{acquiring mutex 'foo' that is already held}}
|
|
foo.unlock(); // expected-note{{mutex released here}}
|
|
foo.unlock(); // expected-warning {{releasing mutex 'foo' that was not held}}
|
|
}
|
|
|
|
|
|
void test1() {
|
|
Foo foo;
|
|
foo.lock1();
|
|
foo.a = 0;
|
|
foo.unlock1();
|
|
|
|
foo.lock1(); // expected-note{{mutex acquired here}}
|
|
foo.lock1(); // expected-warning {{acquiring mutex 'foo.mu1_' that is already held}}
|
|
foo.a = 0;
|
|
foo.unlock1(); // expected-note{{mutex released here}}
|
|
foo.unlock1(); // expected-warning {{releasing mutex 'foo.mu1_' that was not held}}
|
|
}
|
|
|
|
|
|
int test2() {
|
|
Foo foo;
|
|
foo.slock1();
|
|
int d1 = foo.a;
|
|
foo.unlock1();
|
|
|
|
foo.slock1(); // expected-note{{mutex acquired here}}
|
|
foo.slock1(); // expected-warning {{acquiring mutex 'foo.mu1_' that is already held}}
|
|
int d2 = foo.a;
|
|
foo.unlock1(); // expected-note{{mutex released here}}
|
|
foo.unlock1(); // expected-warning {{releasing mutex 'foo.mu1_' that was not held}}
|
|
return d1 + d2;
|
|
}
|
|
|
|
|
|
void test3() {
|
|
Foo foo;
|
|
foo.lock3();
|
|
foo.a = 0;
|
|
foo.b = 0;
|
|
foo.c = 0;
|
|
foo.unlock3();
|
|
|
|
foo.lock3(); // expected-note 3 {{mutex acquired here}}
|
|
foo.lock3(); // \
|
|
// expected-warning {{acquiring mutex 'foo.mu1_' that is already held}} \
|
|
// expected-warning {{acquiring mutex 'foo.mu2_' that is already held}} \
|
|
// expected-warning {{acquiring mutex 'foo.mu3_' that is already held}}
|
|
foo.a = 0;
|
|
foo.b = 0;
|
|
foo.c = 0;
|
|
foo.unlock3(); // expected-note 3 {{mutex released here}}
|
|
foo.unlock3(); // \
|
|
// expected-warning {{releasing mutex 'foo.mu1_' that was not held}} \
|
|
// expected-warning {{releasing mutex 'foo.mu2_' that was not held}} \
|
|
// expected-warning {{releasing mutex 'foo.mu3_' that was not held}}
|
|
}
|
|
|
|
|
|
void testlots() {
|
|
Foo foo;
|
|
foo.locklots();
|
|
foo.a = 0;
|
|
foo.b = 0;
|
|
foo.c = 0;
|
|
foo.unlocklots();
|
|
|
|
foo.locklots(); // expected-note 3 {{mutex acquired here}}
|
|
foo.locklots(); // \
|
|
// expected-warning {{acquiring mutex 'foo.mu1_' that is already held}} \
|
|
// expected-warning {{acquiring mutex 'foo.mu2_' that is already held}} \
|
|
// expected-warning {{acquiring mutex 'foo.mu3_' that is already held}}
|
|
foo.a = 0;
|
|
foo.b = 0;
|
|
foo.c = 0;
|
|
foo.unlocklots(); // expected-note 3 {{mutex released here}}
|
|
foo.unlocklots(); // \
|
|
// expected-warning {{releasing mutex 'foo.mu1_' that was not held}} \
|
|
// expected-warning {{releasing mutex 'foo.mu2_' that was not held}} \
|
|
// expected-warning {{releasing mutex 'foo.mu3_' that was not held}}
|
|
}
|
|
|
|
} // end namespace DuplicateAttributeTest
|
|
|
|
|
|
|
|
namespace TryLockEqTest {
|
|
|
|
class Foo {
|
|
Mutex mu_;
|
|
int a GUARDED_BY(mu_);
|
|
bool c;
|
|
|
|
int tryLockMutexI() EXCLUSIVE_TRYLOCK_FUNCTION(1, mu_);
|
|
Mutex* tryLockMutexP() EXCLUSIVE_TRYLOCK_FUNCTION(1, mu_);
|
|
void unlock() UNLOCK_FUNCTION(mu_);
|
|
|
|
void test1();
|
|
void test2();
|
|
};
|
|
|
|
|
|
void Foo::test1() {
|
|
if (tryLockMutexP() == 0) {
|
|
a = 0; // expected-warning {{writing variable 'a' requires holding mutex 'mu_' exclusively}}
|
|
return;
|
|
}
|
|
a = 0;
|
|
unlock();
|
|
|
|
if (tryLockMutexP() != 0) {
|
|
a = 0;
|
|
unlock();
|
|
}
|
|
|
|
if (0 != tryLockMutexP()) {
|
|
a = 0;
|
|
unlock();
|
|
}
|
|
|
|
if (!(tryLockMutexP() == 0)) {
|
|
a = 0;
|
|
unlock();
|
|
}
|
|
|
|
if (tryLockMutexI() == 0) {
|
|
a = 0; // expected-warning {{writing variable 'a' requires holding mutex 'mu_' exclusively}}
|
|
return;
|
|
}
|
|
a = 0;
|
|
unlock();
|
|
|
|
if (0 == tryLockMutexI()) {
|
|
a = 0; // expected-warning {{writing variable 'a' requires holding mutex 'mu_' exclusively}}
|
|
return;
|
|
}
|
|
a = 0;
|
|
unlock();
|
|
|
|
if (tryLockMutexI() == 1) {
|
|
a = 0;
|
|
unlock();
|
|
}
|
|
|
|
if (mu_.TryLock() == false) {
|
|
a = 0; // expected-warning {{writing variable 'a' requires holding mutex 'mu_' exclusively}}
|
|
return;
|
|
}
|
|
a = 0;
|
|
unlock();
|
|
|
|
if (mu_.TryLock() == true) {
|
|
a = 0;
|
|
unlock();
|
|
}
|
|
else {
|
|
a = 0; // expected-warning {{writing variable 'a' requires holding mutex 'mu_' exclusively}}
|
|
}
|
|
|
|
#if __has_feature(cxx_nullptr)
|
|
if (tryLockMutexP() == nullptr) {
|
|
a = 0; // expected-warning {{writing variable 'a' requires holding mutex 'mu_' exclusively}}
|
|
return;
|
|
}
|
|
a = 0;
|
|
unlock();
|
|
#endif
|
|
}
|
|
|
|
} // end namespace TryLockEqTest
|
|
|
|
|
|
namespace ExistentialPatternMatching {
|
|
|
|
class Graph {
|
|
public:
|
|
Mutex mu_;
|
|
};
|
|
|
|
void LockAllGraphs() EXCLUSIVE_LOCK_FUNCTION(&Graph::mu_);
|
|
void UnlockAllGraphs() UNLOCK_FUNCTION(&Graph::mu_);
|
|
|
|
class Node {
|
|
public:
|
|
int a GUARDED_BY(&Graph::mu_);
|
|
|
|
void foo() EXCLUSIVE_LOCKS_REQUIRED(&Graph::mu_) {
|
|
a = 0;
|
|
}
|
|
void foo2() LOCKS_EXCLUDED(&Graph::mu_);
|
|
};
|
|
|
|
void test() {
|
|
Graph g1;
|
|
Graph g2;
|
|
Node n1;
|
|
|
|
n1.a = 0; // expected-warning {{writing variable 'a' requires holding mutex '&ExistentialPatternMatching::Graph::mu_' exclusively}}
|
|
n1.foo(); // expected-warning {{calling function 'foo' requires holding mutex '&ExistentialPatternMatching::Graph::mu_' exclusively}}
|
|
n1.foo2();
|
|
|
|
g1.mu_.Lock();
|
|
n1.a = 0;
|
|
n1.foo();
|
|
n1.foo2(); // expected-warning {{cannot call function 'foo2' while mutex '&ExistentialPatternMatching::Graph::mu_' is held}}
|
|
g1.mu_.Unlock();
|
|
|
|
g2.mu_.Lock();
|
|
n1.a = 0;
|
|
n1.foo();
|
|
n1.foo2(); // expected-warning {{cannot call function 'foo2' while mutex '&ExistentialPatternMatching::Graph::mu_' is held}}
|
|
g2.mu_.Unlock();
|
|
|
|
LockAllGraphs();
|
|
n1.a = 0;
|
|
n1.foo();
|
|
n1.foo2(); // expected-warning {{cannot call function 'foo2' while mutex '&ExistentialPatternMatching::Graph::mu_' is held}}
|
|
UnlockAllGraphs();
|
|
|
|
LockAllGraphs();
|
|
g1.mu_.Unlock();
|
|
|
|
LockAllGraphs();
|
|
g2.mu_.Unlock();
|
|
|
|
LockAllGraphs(); // expected-note{{mutex acquired here}}
|
|
g1.mu_.Lock(); // expected-warning {{acquiring mutex 'g1.mu_' that is already held}}
|
|
g1.mu_.Unlock();
|
|
}
|
|
|
|
} // end namespace ExistentialPatternMatching
|
|
|
|
|
|
namespace StringIgnoreTest {
|
|
|
|
class Foo {
|
|
public:
|
|
Mutex mu_;
|
|
void lock() EXCLUSIVE_LOCK_FUNCTION("");
|
|
void unlock() UNLOCK_FUNCTION("");
|
|
void goober() EXCLUSIVE_LOCKS_REQUIRED("");
|
|
void roober() SHARED_LOCKS_REQUIRED("");
|
|
};
|
|
|
|
|
|
class Bar : public Foo {
|
|
public:
|
|
void bar(Foo* f) {
|
|
f->unlock();
|
|
f->goober();
|
|
f->roober();
|
|
f->lock();
|
|
};
|
|
};
|
|
|
|
} // end namespace StringIgnoreTest
|
|
|
|
|
|
namespace LockReturnedScopeFix {
|
|
|
|
class Base {
|
|
protected:
|
|
struct Inner;
|
|
bool c;
|
|
|
|
const Mutex& getLock(const Inner* i);
|
|
|
|
void lockInner (Inner* i) EXCLUSIVE_LOCK_FUNCTION(getLock(i));
|
|
void unlockInner(Inner* i) UNLOCK_FUNCTION(getLock(i));
|
|
void foo(Inner* i) EXCLUSIVE_LOCKS_REQUIRED(getLock(i));
|
|
|
|
void bar(Inner* i);
|
|
};
|
|
|
|
|
|
struct Base::Inner {
|
|
Mutex lock_;
|
|
void doSomething() EXCLUSIVE_LOCKS_REQUIRED(lock_);
|
|
};
|
|
|
|
|
|
const Mutex& Base::getLock(const Inner* i) LOCK_RETURNED(i->lock_) {
|
|
return i->lock_;
|
|
}
|
|
|
|
|
|
void Base::foo(Inner* i) {
|
|
i->doSomething();
|
|
}
|
|
|
|
void Base::bar(Inner* i) {
|
|
if (c) {
|
|
i->lock_.Lock();
|
|
unlockInner(i);
|
|
}
|
|
else {
|
|
lockInner(i);
|
|
i->lock_.Unlock();
|
|
}
|
|
}
|
|
|
|
} // end namespace LockReturnedScopeFix
|
|
|
|
|
|
namespace TrylockWithCleanups {
|
|
|
|
struct Foo {
|
|
Mutex mu_;
|
|
int a GUARDED_BY(mu_);
|
|
};
|
|
|
|
Foo* GetAndLockFoo(const MyString& s)
|
|
EXCLUSIVE_TRYLOCK_FUNCTION(true, &Foo::mu_);
|
|
|
|
static void test() {
|
|
Foo* lt = GetAndLockFoo("foo");
|
|
if (!lt) return;
|
|
int a = lt->a;
|
|
lt->mu_.Unlock();
|
|
}
|
|
|
|
} // end namespace TrylockWithCleanups
|
|
|
|
|
|
namespace UniversalLock {
|
|
|
|
class Foo {
|
|
Mutex mu_;
|
|
bool c;
|
|
|
|
int a GUARDED_BY(mu_);
|
|
void r_foo() SHARED_LOCKS_REQUIRED(mu_);
|
|
void w_foo() EXCLUSIVE_LOCKS_REQUIRED(mu_);
|
|
|
|
void test1() {
|
|
int b;
|
|
|
|
beginNoWarnOnReads();
|
|
b = a;
|
|
r_foo();
|
|
endNoWarnOnReads();
|
|
|
|
beginNoWarnOnWrites();
|
|
a = 0;
|
|
w_foo();
|
|
endNoWarnOnWrites();
|
|
}
|
|
|
|
// don't warn on joins with universal lock
|
|
void test2() {
|
|
if (c) {
|
|
beginNoWarnOnWrites();
|
|
}
|
|
a = 0; // \
|
|
// expected-warning {{writing variable 'a' requires holding mutex 'mu_' exclusively}}
|
|
endNoWarnOnWrites(); // \
|
|
// expected-warning {{releasing wildcard '*' that was not held}}
|
|
}
|
|
|
|
|
|
// make sure the universal lock joins properly
|
|
void test3() {
|
|
if (c) {
|
|
mu_.Lock();
|
|
beginNoWarnOnWrites();
|
|
}
|
|
else {
|
|
beginNoWarnOnWrites();
|
|
mu_.Lock();
|
|
}
|
|
a = 0;
|
|
endNoWarnOnWrites();
|
|
mu_.Unlock();
|
|
}
|
|
|
|
|
|
// combine universal lock with other locks
|
|
void test4() {
|
|
beginNoWarnOnWrites();
|
|
mu_.Lock();
|
|
mu_.Unlock();
|
|
endNoWarnOnWrites();
|
|
|
|
mu_.Lock();
|
|
beginNoWarnOnWrites();
|
|
endNoWarnOnWrites();
|
|
mu_.Unlock();
|
|
|
|
mu_.Lock();
|
|
beginNoWarnOnWrites();
|
|
mu_.Unlock();
|
|
endNoWarnOnWrites();
|
|
}
|
|
};
|
|
|
|
} // end namespace UniversalLock
|
|
|
|
|
|
namespace TemplateLockReturned {
|
|
|
|
template<class T>
|
|
class BaseT {
|
|
public:
|
|
virtual void baseMethod() = 0;
|
|
Mutex* get_mutex() LOCK_RETURNED(mutex_) { return &mutex_; }
|
|
|
|
Mutex mutex_;
|
|
int a GUARDED_BY(mutex_);
|
|
};
|
|
|
|
|
|
class Derived : public BaseT<int> {
|
|
public:
|
|
void baseMethod() EXCLUSIVE_LOCKS_REQUIRED(get_mutex()) {
|
|
a = 0;
|
|
}
|
|
};
|
|
|
|
} // end namespace TemplateLockReturned
|
|
|
|
|
|
namespace ExprMatchingBugFix {
|
|
|
|
class Foo {
|
|
public:
|
|
Mutex mu_;
|
|
};
|
|
|
|
|
|
class Bar {
|
|
public:
|
|
bool c;
|
|
Foo* foo;
|
|
Bar(Foo* f) : foo(f) { }
|
|
|
|
struct Nested {
|
|
Foo* foo;
|
|
Nested(Foo* f) : foo(f) { }
|
|
|
|
void unlockFoo() UNLOCK_FUNCTION(&Foo::mu_);
|
|
};
|
|
|
|
void test();
|
|
};
|
|
|
|
|
|
void Bar::test() {
|
|
foo->mu_.Lock();
|
|
if (c) {
|
|
Nested *n = new Nested(foo);
|
|
n->unlockFoo();
|
|
}
|
|
else {
|
|
foo->mu_.Unlock();
|
|
}
|
|
}
|
|
|
|
}; // end namespace ExprMatchingBugfix
|
|
|
|
|
|
namespace ComplexNameTest {
|
|
|
|
class Foo {
|
|
public:
|
|
static Mutex mu_;
|
|
|
|
Foo() EXCLUSIVE_LOCKS_REQUIRED(mu_) { }
|
|
~Foo() EXCLUSIVE_LOCKS_REQUIRED(mu_) { }
|
|
|
|
int operator[](int i) EXCLUSIVE_LOCKS_REQUIRED(mu_) { return 0; }
|
|
};
|
|
|
|
class Bar {
|
|
public:
|
|
static Mutex mu_;
|
|
|
|
Bar() LOCKS_EXCLUDED(mu_) { }
|
|
~Bar() LOCKS_EXCLUDED(mu_) { }
|
|
|
|
int operator[](int i) LOCKS_EXCLUDED(mu_) { return 0; }
|
|
};
|
|
|
|
|
|
void test1() {
|
|
Foo f; // expected-warning {{calling function 'Foo' requires holding mutex 'mu_' exclusively}}
|
|
int a = f[0]; // expected-warning {{calling function 'operator[]' requires holding mutex 'mu_' exclusively}}
|
|
} // expected-warning {{calling function '~Foo' requires holding mutex 'mu_' exclusively}}
|
|
|
|
|
|
void test2() {
|
|
Bar::mu_.Lock();
|
|
{
|
|
Bar b; // expected-warning {{cannot call function 'Bar' while mutex 'mu_' is held}}
|
|
int a = b[0]; // expected-warning {{cannot call function 'operator[]' while mutex 'mu_' is held}}
|
|
} // expected-warning {{cannot call function '~Bar' while mutex 'mu_' is held}}
|
|
Bar::mu_.Unlock();
|
|
}
|
|
|
|
}; // end namespace ComplexNameTest
|
|
|
|
|
|
namespace UnreachableExitTest {
|
|
|
|
class FemmeFatale {
|
|
public:
|
|
FemmeFatale();
|
|
~FemmeFatale() __attribute__((noreturn));
|
|
};
|
|
|
|
void exitNow() __attribute__((noreturn));
|
|
void exitDestruct(const MyString& ms) __attribute__((noreturn));
|
|
|
|
Mutex fatalmu_;
|
|
|
|
void test1() EXCLUSIVE_LOCKS_REQUIRED(fatalmu_) {
|
|
exitNow();
|
|
}
|
|
|
|
void test2() EXCLUSIVE_LOCKS_REQUIRED(fatalmu_) {
|
|
FemmeFatale femme;
|
|
}
|
|
|
|
bool c;
|
|
|
|
void test3() EXCLUSIVE_LOCKS_REQUIRED(fatalmu_) {
|
|
if (c) {
|
|
exitNow();
|
|
}
|
|
else {
|
|
FemmeFatale femme;
|
|
}
|
|
}
|
|
|
|
void test4() EXCLUSIVE_LOCKS_REQUIRED(fatalmu_) {
|
|
exitDestruct("foo");
|
|
}
|
|
|
|
} // end namespace UnreachableExitTest
|
|
|
|
|
|
namespace VirtualMethodCanonicalizationTest {
|
|
|
|
class Base {
|
|
public:
|
|
virtual Mutex* getMutex() = 0;
|
|
};
|
|
|
|
class Base2 : public Base {
|
|
public:
|
|
Mutex* getMutex();
|
|
};
|
|
|
|
class Base3 : public Base2 {
|
|
public:
|
|
Mutex* getMutex();
|
|
};
|
|
|
|
class Derived : public Base3 {
|
|
public:
|
|
Mutex* getMutex(); // overrides Base::getMutex()
|
|
};
|
|
|
|
void baseFun(Base *b) EXCLUSIVE_LOCKS_REQUIRED(b->getMutex()) { }
|
|
|
|
void derivedFun(Derived *d) EXCLUSIVE_LOCKS_REQUIRED(d->getMutex()) {
|
|
baseFun(d);
|
|
}
|
|
|
|
} // end namespace VirtualMethodCanonicalizationTest
|
|
|
|
|
|
namespace TemplateFunctionParamRemapTest {
|
|
|
|
template <class T>
|
|
struct Cell {
|
|
T dummy_;
|
|
Mutex* mu_;
|
|
};
|
|
|
|
class Foo {
|
|
public:
|
|
template <class T>
|
|
void elr(Cell<T>* c) EXCLUSIVE_LOCKS_REQUIRED(c->mu_);
|
|
|
|
void test();
|
|
};
|
|
|
|
template<class T>
|
|
void Foo::elr(Cell<T>* c1) { }
|
|
|
|
void Foo::test() {
|
|
Cell<int> cell;
|
|
elr(&cell); // \
|
|
// expected-warning {{calling function 'elr<int>' requires holding mutex 'cell.mu_' exclusively}}
|
|
}
|
|
|
|
|
|
template<class T>
|
|
void globalELR(Cell<T>* c) EXCLUSIVE_LOCKS_REQUIRED(c->mu_);
|
|
|
|
template<class T>
|
|
void globalELR(Cell<T>* c1) { }
|
|
|
|
void globalTest() {
|
|
Cell<int> cell;
|
|
globalELR(&cell); // \
|
|
// expected-warning {{calling function 'globalELR<int>' requires holding mutex 'cell.mu_' exclusively}}
|
|
}
|
|
|
|
|
|
template<class T>
|
|
void globalELR2(Cell<T>* c) EXCLUSIVE_LOCKS_REQUIRED(c->mu_);
|
|
|
|
// second declaration
|
|
template<class T>
|
|
void globalELR2(Cell<T>* c2);
|
|
|
|
template<class T>
|
|
void globalELR2(Cell<T>* c3) { }
|
|
|
|
// re-declaration after definition
|
|
template<class T>
|
|
void globalELR2(Cell<T>* c4);
|
|
|
|
void globalTest2() {
|
|
Cell<int> cell;
|
|
globalELR2(&cell); // \
|
|
// expected-warning {{calling function 'globalELR2<int>' requires holding mutex 'cell.mu_' exclusively}}
|
|
}
|
|
|
|
|
|
template<class T>
|
|
class FooT {
|
|
public:
|
|
void elr(Cell<T>* c) EXCLUSIVE_LOCKS_REQUIRED(c->mu_);
|
|
};
|
|
|
|
template<class T>
|
|
void FooT<T>::elr(Cell<T>* c1) { }
|
|
|
|
void testFooT() {
|
|
Cell<int> cell;
|
|
FooT<int> foo;
|
|
foo.elr(&cell); // \
|
|
// expected-warning {{calling function 'elr' requires holding mutex 'cell.mu_' exclusively}}
|
|
}
|
|
|
|
} // end namespace TemplateFunctionParamRemapTest
|
|
|
|
|
|
namespace SelfConstructorTest {
|
|
|
|
class SelfLock {
|
|
public:
|
|
SelfLock() EXCLUSIVE_LOCK_FUNCTION(mu_);
|
|
~SelfLock() UNLOCK_FUNCTION(mu_);
|
|
|
|
void foo() EXCLUSIVE_LOCKS_REQUIRED(mu_);
|
|
|
|
Mutex mu_;
|
|
};
|
|
|
|
class LOCKABLE SelfLock2 {
|
|
public:
|
|
SelfLock2() EXCLUSIVE_LOCK_FUNCTION();
|
|
~SelfLock2() UNLOCK_FUNCTION();
|
|
|
|
void foo() EXCLUSIVE_LOCKS_REQUIRED(this);
|
|
};
|
|
|
|
class SelfLockDeferred {
|
|
public:
|
|
SelfLockDeferred() LOCKS_EXCLUDED(mu_);
|
|
~SelfLockDeferred() UNLOCK_FUNCTION(mu_);
|
|
|
|
Mutex mu_;
|
|
};
|
|
|
|
class LOCKABLE SelfLockDeferred2 {
|
|
public:
|
|
SelfLockDeferred2() LOCKS_EXCLUDED(this);
|
|
~SelfLockDeferred2() UNLOCK_FUNCTION();
|
|
};
|
|
|
|
|
|
void test() {
|
|
SelfLock s;
|
|
s.foo();
|
|
}
|
|
|
|
void test2() {
|
|
SelfLock2 s2;
|
|
s2.foo();
|
|
}
|
|
|
|
void testDeferredTemporary() {
|
|
SelfLockDeferred(); // expected-warning {{releasing mutex '<temporary>.mu_' that was not held}}
|
|
}
|
|
|
|
void testDeferredTemporary2() {
|
|
SelfLockDeferred2(); // expected-warning {{releasing mutex '<temporary>' that was not held}}
|
|
}
|
|
|
|
} // end namespace SelfConstructorTest
|
|
|
|
|
|
namespace MultipleAttributeTest {
|
|
|
|
class Foo {
|
|
Mutex mu1_;
|
|
Mutex mu2_;
|
|
int a GUARDED_BY(mu1_);
|
|
int b GUARDED_BY(mu2_);
|
|
int c GUARDED_BY(mu1_) GUARDED_BY(mu2_);
|
|
int* d PT_GUARDED_BY(mu1_) PT_GUARDED_BY(mu2_);
|
|
|
|
void foo1() EXCLUSIVE_LOCKS_REQUIRED(mu1_)
|
|
EXCLUSIVE_LOCKS_REQUIRED(mu2_);
|
|
void foo2() SHARED_LOCKS_REQUIRED(mu1_)
|
|
SHARED_LOCKS_REQUIRED(mu2_);
|
|
void foo3() LOCKS_EXCLUDED(mu1_)
|
|
LOCKS_EXCLUDED(mu2_);
|
|
void lock() EXCLUSIVE_LOCK_FUNCTION(mu1_)
|
|
EXCLUSIVE_LOCK_FUNCTION(mu2_);
|
|
void readerlock() SHARED_LOCK_FUNCTION(mu1_)
|
|
SHARED_LOCK_FUNCTION(mu2_);
|
|
void unlock() UNLOCK_FUNCTION(mu1_)
|
|
UNLOCK_FUNCTION(mu2_);
|
|
bool trylock() EXCLUSIVE_TRYLOCK_FUNCTION(true, mu1_)
|
|
EXCLUSIVE_TRYLOCK_FUNCTION(true, mu2_);
|
|
bool readertrylock() SHARED_TRYLOCK_FUNCTION(true, mu1_)
|
|
SHARED_TRYLOCK_FUNCTION(true, mu2_);
|
|
void assertBoth() ASSERT_EXCLUSIVE_LOCK(mu1_)
|
|
ASSERT_EXCLUSIVE_LOCK(mu2_);
|
|
|
|
void alsoAssertBoth() ASSERT_EXCLUSIVE_LOCK(mu1_, mu2_);
|
|
|
|
void assertShared() ASSERT_SHARED_LOCK(mu1_)
|
|
ASSERT_SHARED_LOCK(mu2_);
|
|
|
|
void alsoAssertShared() ASSERT_SHARED_LOCK(mu1_, mu2_);
|
|
|
|
void test();
|
|
void testAssert();
|
|
void testAssertShared();
|
|
};
|
|
|
|
|
|
void Foo::foo1() {
|
|
a = 1;
|
|
b = 2;
|
|
}
|
|
|
|
void Foo::foo2() {
|
|
int result = a + b;
|
|
}
|
|
|
|
void Foo::foo3() { }
|
|
void Foo::lock() { mu1_.Lock(); mu2_.Lock(); }
|
|
void Foo::readerlock() { mu1_.ReaderLock(); mu2_.ReaderLock(); }
|
|
void Foo::unlock() { mu1_.Unlock(); mu2_.Unlock(); }
|
|
bool Foo::trylock() { return true; }
|
|
bool Foo::readertrylock() { return true; }
|
|
|
|
|
|
void Foo::test() {
|
|
mu1_.Lock();
|
|
foo1(); // expected-warning {{}}
|
|
c = 0; // expected-warning {{}}
|
|
*d = 0; // expected-warning {{}}
|
|
mu1_.Unlock();
|
|
|
|
mu1_.ReaderLock();
|
|
foo2(); // expected-warning {{}}
|
|
int x = c; // expected-warning {{}}
|
|
int y = *d; // expected-warning {{}}
|
|
mu1_.Unlock();
|
|
|
|
mu2_.Lock();
|
|
foo3(); // expected-warning {{}}
|
|
mu2_.Unlock();
|
|
|
|
lock();
|
|
a = 0;
|
|
b = 0;
|
|
unlock();
|
|
|
|
readerlock();
|
|
int z = a + b;
|
|
unlock();
|
|
|
|
if (trylock()) {
|
|
a = 0;
|
|
b = 0;
|
|
unlock();
|
|
}
|
|
|
|
if (readertrylock()) {
|
|
int zz = a + b;
|
|
unlock();
|
|
}
|
|
}
|
|
|
|
// Force duplication of attributes
|
|
void Foo::assertBoth() { }
|
|
void Foo::alsoAssertBoth() { }
|
|
void Foo::assertShared() { }
|
|
void Foo::alsoAssertShared() { }
|
|
|
|
void Foo::testAssert() {
|
|
{
|
|
assertBoth();
|
|
a = 0;
|
|
b = 0;
|
|
}
|
|
{
|
|
alsoAssertBoth();
|
|
a = 0;
|
|
b = 0;
|
|
}
|
|
}
|
|
|
|
void Foo::testAssertShared() {
|
|
{
|
|
assertShared();
|
|
int zz = a + b;
|
|
}
|
|
|
|
{
|
|
alsoAssertShared();
|
|
int zz = a + b;
|
|
}
|
|
}
|
|
|
|
|
|
} // end namespace MultipleAttributeTest
|
|
|
|
|
|
namespace GuardedNonPrimitiveTypeTest {
|
|
|
|
|
|
class Data {
|
|
public:
|
|
Data(int i) : dat(i) { }
|
|
|
|
int getValue() const { return dat; }
|
|
void setValue(int i) { dat = i; }
|
|
|
|
int operator[](int i) const { return dat; }
|
|
int& operator[](int i) { return dat; }
|
|
|
|
void operator()() { }
|
|
|
|
Data& operator+=(int);
|
|
Data& operator-=(int);
|
|
Data& operator*=(int);
|
|
Data& operator/=(int);
|
|
Data& operator%=(int);
|
|
Data& operator^=(int);
|
|
Data& operator&=(int);
|
|
Data& operator|=(int);
|
|
Data& operator<<=(int);
|
|
Data& operator>>=(int);
|
|
Data& operator++();
|
|
Data& operator++(int);
|
|
Data& operator--();
|
|
Data& operator--(int);
|
|
|
|
private:
|
|
int dat;
|
|
};
|
|
|
|
class DataWithAddrOf : public Data {
|
|
public:
|
|
const Data* operator&();
|
|
const Data* operator&() const;
|
|
};
|
|
|
|
class DataCell {
|
|
public:
|
|
DataCell(const Data& d) : dat(d) { }
|
|
|
|
private:
|
|
Data dat;
|
|
};
|
|
|
|
|
|
void showData(const Data* d);
|
|
void showDataCell(const DataCell& dc);
|
|
|
|
|
|
class Foo {
|
|
public:
|
|
// method call tests
|
|
void test() {
|
|
data_.setValue(0); // FIXME -- should be writing \
|
|
// expected-warning {{reading variable 'data_' requires holding mutex 'mu_'}}
|
|
int a = data_.getValue(); // \
|
|
// expected-warning {{reading variable 'data_' requires holding mutex 'mu_'}}
|
|
|
|
datap1_->setValue(0); // FIXME -- should be writing \
|
|
// expected-warning {{reading variable 'datap1_' requires holding mutex 'mu_'}}
|
|
a = datap1_->getValue(); // \
|
|
// expected-warning {{reading variable 'datap1_' requires holding mutex 'mu_'}}
|
|
|
|
datap2_->setValue(0); // FIXME -- should be writing \
|
|
// expected-warning {{reading the value pointed to by 'datap2_' requires holding mutex 'mu_'}}
|
|
a = datap2_->getValue(); // \
|
|
// expected-warning {{reading the value pointed to by 'datap2_' requires holding mutex 'mu_'}}
|
|
|
|
(*datap2_).setValue(0); // FIXME -- should be writing \
|
|
// expected-warning {{reading the value pointed to by 'datap2_' requires holding mutex 'mu_'}}
|
|
a = (*datap2_).getValue(); // \
|
|
// expected-warning {{reading the value pointed to by 'datap2_' requires holding mutex 'mu_'}}
|
|
|
|
mu_.Lock();
|
|
data_.setValue(1);
|
|
datap1_->setValue(1);
|
|
datap2_->setValue(1);
|
|
mu_.Unlock();
|
|
|
|
mu_.ReaderLock();
|
|
a = data_.getValue();
|
|
datap1_->setValue(0); // reads datap1_, writes *datap1_
|
|
a = datap1_->getValue();
|
|
a = datap2_->getValue();
|
|
mu_.Unlock();
|
|
}
|
|
|
|
// operator tests
|
|
void test2() {
|
|
data_ = Data(1); // expected-warning {{writing variable 'data_' requires holding mutex 'mu_' exclusively}}
|
|
*datap1_ = data_; // expected-warning {{reading variable 'datap1_' requires holding mutex 'mu_'}} \
|
|
// expected-warning {{reading variable 'data_' requires holding mutex 'mu_'}}
|
|
*datap2_ = data_; // expected-warning {{writing the value pointed to by 'datap2_' requires holding mutex 'mu_' exclusively}} \
|
|
// expected-warning {{reading variable 'data_' requires holding mutex 'mu_'}}
|
|
data_ = *datap1_; // expected-warning {{writing variable 'data_' requires holding mutex 'mu_' exclusively}} \
|
|
// expected-warning {{reading variable 'datap1_' requires holding mutex 'mu_'}}
|
|
data_ = *datap2_; // expected-warning {{writing variable 'data_' requires holding mutex 'mu_' exclusively}} \
|
|
// expected-warning {{reading the value pointed to by 'datap2_' requires holding mutex 'mu_'}}
|
|
data_ += 1; // expected-warning {{writing variable 'data_' requires holding mutex 'mu_' exclusively}}
|
|
data_ -= 1; // expected-warning {{writing variable 'data_' requires holding mutex 'mu_' exclusively}}
|
|
data_ *= 1; // expected-warning {{writing variable 'data_' requires holding mutex 'mu_' exclusively}}
|
|
data_ /= 1; // expected-warning {{writing variable 'data_' requires holding mutex 'mu_' exclusively}}
|
|
data_ %= 1; // expected-warning {{writing variable 'data_' requires holding mutex 'mu_' exclusively}}
|
|
data_ ^= 1; // expected-warning {{writing variable 'data_' requires holding mutex 'mu_' exclusively}}
|
|
data_ &= 1; // expected-warning {{writing variable 'data_' requires holding mutex 'mu_' exclusively}}
|
|
data_ |= 1; // expected-warning {{writing variable 'data_' requires holding mutex 'mu_' exclusively}}
|
|
data_ <<= 1; // expected-warning {{writing variable 'data_' requires holding mutex 'mu_' exclusively}}
|
|
data_ >>= 1; // expected-warning {{writing variable 'data_' requires holding mutex 'mu_' exclusively}}
|
|
++data_; // expected-warning {{writing variable 'data_' requires holding mutex 'mu_' exclusively}}
|
|
data_++; // expected-warning {{writing variable 'data_' requires holding mutex 'mu_' exclusively}}
|
|
--data_; // expected-warning {{writing variable 'data_' requires holding mutex 'mu_' exclusively}}
|
|
data_--; // expected-warning {{writing variable 'data_' requires holding mutex 'mu_' exclusively}}
|
|
|
|
data_[0] = 0; // expected-warning {{reading variable 'data_' requires holding mutex 'mu_'}}
|
|
(*datap2_)[0] = 0; // expected-warning {{reading the value pointed to by 'datap2_' requires holding mutex 'mu_'}}
|
|
|
|
data_(); // expected-warning {{reading variable 'data_' requires holding mutex 'mu_'}}
|
|
|
|
// Calls operator&, and does not take the address.
|
|
(void)&data_ao_; // expected-warning {{reading variable 'data_ao_' requires holding mutex 'mu_'}}
|
|
(void)__builtin_addressof(data_ao_); // expected-warning {{passing variable 'data_ao_' by reference requires holding mutex 'mu_'}}
|
|
showData(&data_ao_); // expected-warning {{reading variable 'data_ao_' requires holding mutex 'mu_'}}
|
|
}
|
|
|
|
// const operator tests
|
|
void test3() const {
|
|
Data mydat(data_); // expected-warning {{reading variable 'data_' requires holding mutex 'mu_'}}
|
|
|
|
//FIXME
|
|
//showDataCell(data_); // xpected-warning {{reading variable 'data_' requires holding mutex 'mu_'}}
|
|
//showDataCell(*datap2_); // xpected-warning {{reading the value pointed to by 'datap2_' requires holding mutex 'mu_'}}
|
|
|
|
int a = data_[0]; // expected-warning {{reading variable 'data_' requires holding mutex 'mu_'}}
|
|
|
|
(void)&data_ao_; // expected-warning {{reading variable 'data_ao_' requires holding mutex 'mu_'}}
|
|
showData(&data_ao_); // expected-warning {{reading variable 'data_ao_' requires holding mutex 'mu_'}}
|
|
}
|
|
|
|
private:
|
|
Mutex mu_;
|
|
Data data_ GUARDED_BY(mu_);
|
|
Data* datap1_ GUARDED_BY(mu_);
|
|
Data* datap2_ PT_GUARDED_BY(mu_);
|
|
DataWithAddrOf data_ao_ GUARDED_BY(mu_);
|
|
};
|
|
|
|
} // end namespace GuardedNonPrimitiveTypeTest
|
|
|
|
|
|
namespace GuardedNonPrimitive_MemberAccess {
|
|
|
|
class Cell {
|
|
public:
|
|
Cell(int i);
|
|
|
|
void cellMethod();
|
|
|
|
int a;
|
|
};
|
|
|
|
|
|
class Foo {
|
|
public:
|
|
int a;
|
|
Cell c GUARDED_BY(cell_mu_);
|
|
Cell* cp PT_GUARDED_BY(cell_mu_);
|
|
|
|
void myMethod();
|
|
|
|
Mutex cell_mu_;
|
|
};
|
|
|
|
|
|
class Bar {
|
|
private:
|
|
Mutex mu_;
|
|
Foo foo GUARDED_BY(mu_);
|
|
Foo* foop PT_GUARDED_BY(mu_);
|
|
|
|
void test() {
|
|
foo.myMethod(); // expected-warning {{reading variable 'foo' requires holding mutex 'mu_'}}
|
|
|
|
int fa = foo.a; // expected-warning {{reading variable 'foo' requires holding mutex 'mu_'}}
|
|
foo.a = fa; // expected-warning {{writing variable 'foo' requires holding mutex 'mu_' exclusively}}
|
|
|
|
fa = foop->a; // expected-warning {{reading the value pointed to by 'foop' requires holding mutex 'mu_'}}
|
|
foop->a = fa; // expected-warning {{writing the value pointed to by 'foop' requires holding mutex 'mu_' exclusively}}
|
|
|
|
fa = (*foop).a; // expected-warning {{reading the value pointed to by 'foop' requires holding mutex 'mu_'}}
|
|
(*foop).a = fa; // expected-warning {{writing the value pointed to by 'foop' requires holding mutex 'mu_' exclusively}}
|
|
|
|
foo.c = Cell(0); // expected-warning {{writing variable 'foo' requires holding mutex 'mu_'}} \
|
|
// expected-warning {{writing variable 'c' requires holding mutex 'foo.cell_mu_' exclusively}}
|
|
foo.c.cellMethod(); // expected-warning {{reading variable 'foo' requires holding mutex 'mu_'}} \
|
|
// expected-warning {{reading variable 'c' requires holding mutex 'foo.cell_mu_'}}
|
|
|
|
foop->c = Cell(0); // expected-warning {{writing the value pointed to by 'foop' requires holding mutex 'mu_'}} \
|
|
// expected-warning {{writing variable 'c' requires holding mutex 'foop->cell_mu_' exclusively}}
|
|
foop->c.cellMethod(); // expected-warning {{reading the value pointed to by 'foop' requires holding mutex 'mu_'}} \
|
|
// expected-warning {{reading variable 'c' requires holding mutex 'foop->cell_mu_'}}
|
|
|
|
(*foop).c = Cell(0); // expected-warning {{writing the value pointed to by 'foop' requires holding mutex 'mu_'}} \
|
|
// expected-warning {{writing variable 'c' requires holding mutex 'foop->cell_mu_' exclusively}}
|
|
(*foop).c.cellMethod(); // expected-warning {{reading the value pointed to by 'foop' requires holding mutex 'mu_'}} \
|
|
// expected-warning {{reading variable 'c' requires holding mutex 'foop->cell_mu_'}}
|
|
};
|
|
};
|
|
|
|
} // namespace GuardedNonPrimitive_MemberAccess
|
|
|
|
|
|
namespace TestThrowExpr {
|
|
|
|
class Foo {
|
|
Mutex mu_;
|
|
|
|
bool hasError();
|
|
|
|
void test() {
|
|
mu_.Lock();
|
|
if (hasError()) {
|
|
throw "ugly";
|
|
}
|
|
mu_.Unlock();
|
|
}
|
|
};
|
|
|
|
} // end namespace TestThrowExpr
|
|
|
|
|
|
namespace UnevaluatedContextTest {
|
|
|
|
// parse attribute expressions in an unevaluated context.
|
|
|
|
static inline Mutex* getMutex1();
|
|
static inline Mutex* getMutex2();
|
|
|
|
void bar() EXCLUSIVE_LOCKS_REQUIRED(getMutex1());
|
|
|
|
void bar2() EXCLUSIVE_LOCKS_REQUIRED(getMutex1(), getMutex2());
|
|
|
|
} // end namespace UnevaluatedContextTest
|
|
|
|
|
|
namespace LockUnlockFunctionTest {
|
|
|
|
// Check built-in lock functions
|
|
class LOCKABLE MyLockable {
|
|
public:
|
|
void lock() EXCLUSIVE_LOCK_FUNCTION() { mu_.Lock(); }
|
|
void readerLock() SHARED_LOCK_FUNCTION() { mu_.ReaderLock(); }
|
|
void unlock() UNLOCK_FUNCTION() { mu_.Unlock(); }
|
|
|
|
private:
|
|
Mutex mu_;
|
|
};
|
|
|
|
|
|
class Foo {
|
|
public:
|
|
// Correct lock/unlock functions
|
|
void lock() EXCLUSIVE_LOCK_FUNCTION(mu_) {
|
|
mu_.Lock();
|
|
}
|
|
|
|
void readerLock() SHARED_LOCK_FUNCTION(mu_) {
|
|
mu_.ReaderLock();
|
|
}
|
|
|
|
void unlock() UNLOCK_FUNCTION(mu_) {
|
|
mu_.Unlock();
|
|
}
|
|
|
|
void unlockExclusive() EXCLUSIVE_UNLOCK_FUNCTION(mu_) {
|
|
mu_.Unlock();
|
|
}
|
|
|
|
void unlockShared() SHARED_UNLOCK_FUNCTION(mu_) {
|
|
mu_.ReaderUnlock();
|
|
}
|
|
|
|
// Check failure to lock.
|
|
void lockBad() EXCLUSIVE_LOCK_FUNCTION(mu_) { // expected-note {{mutex acquired here}}
|
|
mu2_.Lock();
|
|
mu2_.Unlock();
|
|
} // expected-warning {{expecting mutex 'mu_' to be held at the end of function}}
|
|
|
|
void readerLockBad() SHARED_LOCK_FUNCTION(mu_) { // expected-note {{mutex acquired here}}
|
|
mu2_.Lock();
|
|
mu2_.Unlock();
|
|
} // expected-warning {{expecting mutex 'mu_' to be held at the end of function}}
|
|
|
|
void unlockBad() UNLOCK_FUNCTION(mu_) { // expected-note {{mutex acquired here}}
|
|
mu2_.Lock();
|
|
mu2_.Unlock();
|
|
} // expected-warning {{mutex 'mu_' is still held at the end of function}}
|
|
|
|
// Check locking the wrong thing.
|
|
void lockBad2() EXCLUSIVE_LOCK_FUNCTION(mu_) { // expected-note {{mutex acquired here}}
|
|
mu2_.Lock(); // expected-note {{mutex acquired here}}
|
|
} // expected-warning {{expecting mutex 'mu_' to be held at the end of function}} \
|
|
// expected-warning {{mutex 'mu2_' is still held at the end of function}}
|
|
|
|
|
|
void readerLockBad2() SHARED_LOCK_FUNCTION(mu_) { // expected-note {{mutex acquired here}}
|
|
mu2_.ReaderLock(); // expected-note {{mutex acquired here}}
|
|
} // expected-warning {{expecting mutex 'mu_' to be held at the end of function}} \
|
|
// expected-warning {{mutex 'mu2_' is still held at the end of function}}
|
|
|
|
|
|
void unlockBad2() UNLOCK_FUNCTION(mu_) { // expected-note {{mutex acquired here}}
|
|
mu2_.Unlock(); // expected-warning {{releasing mutex 'mu2_' that was not held}}
|
|
} // expected-warning {{mutex 'mu_' is still held at the end of function}}
|
|
|
|
private:
|
|
Mutex mu_;
|
|
Mutex mu2_;
|
|
};
|
|
|
|
} // end namespace LockUnlockFunctionTest
|
|
|
|
|
|
namespace AssertHeldTest {
|
|
|
|
class Foo {
|
|
public:
|
|
int c;
|
|
int a GUARDED_BY(mu_);
|
|
Mutex mu_;
|
|
|
|
void test1() {
|
|
mu_.AssertHeld();
|
|
int b = a;
|
|
a = 0;
|
|
}
|
|
|
|
void test2() {
|
|
mu_.AssertReaderHeld();
|
|
int b = a;
|
|
a = 0; // expected-warning {{writing variable 'a' requires holding mutex 'mu_' exclusively}}
|
|
}
|
|
|
|
void test3() {
|
|
if (c) {
|
|
mu_.AssertHeld();
|
|
}
|
|
else {
|
|
mu_.AssertHeld();
|
|
}
|
|
int b = a;
|
|
a = 0;
|
|
}
|
|
|
|
void test4() EXCLUSIVE_LOCKS_REQUIRED(mu_) {
|
|
mu_.AssertHeld();
|
|
int b = a;
|
|
a = 0;
|
|
}
|
|
|
|
void test5() UNLOCK_FUNCTION(mu_) {
|
|
mu_.AssertHeld();
|
|
mu_.Unlock();
|
|
}
|
|
|
|
void test6() {
|
|
mu_.AssertHeld();
|
|
mu_.Unlock(); // should this be a warning?
|
|
}
|
|
|
|
void test7() {
|
|
if (c) {
|
|
mu_.AssertHeld();
|
|
}
|
|
else {
|
|
mu_.Lock();
|
|
}
|
|
int b = a;
|
|
a = 0;
|
|
mu_.Unlock();
|
|
}
|
|
|
|
void test8() {
|
|
if (c) {
|
|
mu_.Lock();
|
|
}
|
|
else {
|
|
mu_.AssertHeld();
|
|
}
|
|
// FIXME: should warn, because it's unclear whether we need to release or not.
|
|
int b = a;
|
|
a = 0;
|
|
mu_.Unlock(); // should this be a warning?
|
|
}
|
|
|
|
void test9() {
|
|
if (c) {
|
|
mu_.AssertHeld();
|
|
}
|
|
else {
|
|
mu_.Lock(); // expected-note {{mutex acquired here}}
|
|
}
|
|
} // expected-warning {{mutex 'mu_' is still held at the end of function}}
|
|
|
|
void test10() {
|
|
if (c) {
|
|
mu_.Lock(); // expected-note {{mutex acquired here}}
|
|
}
|
|
else {
|
|
mu_.AssertHeld();
|
|
}
|
|
} // expected-warning {{mutex 'mu_' is still held at the end of function}}
|
|
|
|
void assertMu() ASSERT_EXCLUSIVE_LOCK(mu_);
|
|
|
|
void test11() {
|
|
assertMu();
|
|
int b = a;
|
|
a = 0;
|
|
}
|
|
|
|
void test12() {
|
|
if (c)
|
|
mu_.ReaderLock(); // expected-warning {{mutex 'mu_' is acquired exclusively and shared in the same scope}}
|
|
else
|
|
mu_.AssertHeld(); // expected-note {{the other acquisition of mutex 'mu_' is here}}
|
|
// FIXME: should instead warn because it's unclear whether we need to release or not.
|
|
int b = a;
|
|
a = 0;
|
|
mu_.Unlock();
|
|
}
|
|
|
|
void test13() {
|
|
if (c)
|
|
mu_.Lock(); // expected-warning {{mutex 'mu_' is acquired exclusively and shared in the same scope}}
|
|
else
|
|
mu_.AssertReaderHeld(); // expected-note {{the other acquisition of mutex 'mu_' is here}}
|
|
// FIXME: should instead warn because it's unclear whether we need to release or not.
|
|
int b = a;
|
|
a = 0;
|
|
mu_.Unlock();
|
|
}
|
|
};
|
|
|
|
} // end namespace AssertHeldTest
|
|
|
|
|
|
namespace LogicalConditionalTryLock {
|
|
|
|
class Foo {
|
|
public:
|
|
Mutex mu;
|
|
int a GUARDED_BY(mu);
|
|
bool c;
|
|
|
|
bool newc();
|
|
|
|
void test1() {
|
|
if (c && mu.TryLock()) {
|
|
a = 0;
|
|
mu.Unlock();
|
|
}
|
|
}
|
|
|
|
void test2() {
|
|
bool b = mu.TryLock();
|
|
if (c && b) {
|
|
a = 0;
|
|
mu.Unlock();
|
|
}
|
|
}
|
|
|
|
void test3() {
|
|
if (c || !mu.TryLock())
|
|
return;
|
|
a = 0;
|
|
mu.Unlock();
|
|
}
|
|
|
|
void test4() {
|
|
while (c && mu.TryLock()) {
|
|
a = 0;
|
|
c = newc();
|
|
mu.Unlock();
|
|
}
|
|
}
|
|
|
|
void test5() {
|
|
while (c) {
|
|
if (newc() || !mu.TryLock())
|
|
break;
|
|
a = 0;
|
|
mu.Unlock();
|
|
}
|
|
}
|
|
|
|
void test6() {
|
|
mu.Lock();
|
|
do {
|
|
a = 0;
|
|
mu.Unlock();
|
|
} while (newc() && mu.TryLock());
|
|
}
|
|
|
|
void test7() {
|
|
for (bool b = mu.TryLock(); c && b;) {
|
|
a = 0;
|
|
mu.Unlock();
|
|
}
|
|
}
|
|
|
|
void test8() {
|
|
if (c && newc() && mu.TryLock()) {
|
|
a = 0;
|
|
mu.Unlock();
|
|
}
|
|
}
|
|
|
|
void test9() {
|
|
if (!(c && newc() && mu.TryLock()))
|
|
return;
|
|
a = 0;
|
|
mu.Unlock();
|
|
}
|
|
|
|
void test10() {
|
|
if (!(c || !mu.TryLock())) {
|
|
a = 0;
|
|
mu.Unlock();
|
|
}
|
|
}
|
|
};
|
|
|
|
} // end namespace LogicalConditionalTryLock
|
|
|
|
|
|
|
|
namespace PtGuardedByTest {
|
|
|
|
void doSomething();
|
|
|
|
class Cell {
|
|
public:
|
|
int a;
|
|
};
|
|
|
|
|
|
// This mainly duplicates earlier tests, but just to make sure...
|
|
class PtGuardedByCorrectnessTest {
|
|
Mutex mu1;
|
|
Mutex mu2;
|
|
int* a GUARDED_BY(mu1) PT_GUARDED_BY(mu2);
|
|
Cell* c GUARDED_BY(mu1) PT_GUARDED_BY(mu2);
|
|
int sa[10] GUARDED_BY(mu1);
|
|
Cell sc[10] GUARDED_BY(mu1);
|
|
|
|
static constexpr int Cell::*pa = &Cell::a;
|
|
|
|
void test1() {
|
|
mu1.Lock();
|
|
if (a == 0) doSomething(); // OK, we don't dereference.
|
|
a = 0;
|
|
c = 0;
|
|
if (sa[0] == 42) doSomething();
|
|
sa[0] = 57;
|
|
if (sc[0].a == 42) doSomething();
|
|
sc[0].a = 57;
|
|
mu1.Unlock();
|
|
}
|
|
|
|
void test2() {
|
|
mu1.ReaderLock();
|
|
if (*a == 0) doSomething(); // expected-warning {{reading the value pointed to by 'a' requires holding mutex 'mu2'}}
|
|
*a = 0; // expected-warning {{writing the value pointed to by 'a' requires holding mutex 'mu2' exclusively}}
|
|
|
|
if (c->a == 0) doSomething(); // expected-warning {{reading the value pointed to by 'c' requires holding mutex 'mu2'}}
|
|
c->a = 0; // expected-warning {{writing the value pointed to by 'c' requires holding mutex 'mu2' exclusively}}
|
|
c->*pa = 0; // expected-warning {{writing the value pointed to by 'c' requires holding mutex 'mu2' exclusively}}
|
|
|
|
if ((*c).a == 0) doSomething(); // expected-warning {{reading the value pointed to by 'c' requires holding mutex 'mu2'}}
|
|
(*c).a = 0; // expected-warning {{writing the value pointed to by 'c' requires holding mutex 'mu2' exclusively}}
|
|
(*c).*pa = 0; // expected-warning {{writing the value pointed to by 'c' requires holding mutex 'mu2' exclusively}}
|
|
|
|
if (a[0] == 42) doSomething(); // expected-warning {{reading the value pointed to by 'a' requires holding mutex 'mu2'}}
|
|
a[0] = 57; // expected-warning {{writing the value pointed to by 'a' requires holding mutex 'mu2' exclusively}}
|
|
if (c[0].a == 42) doSomething(); // expected-warning {{reading the value pointed to by 'c' requires holding mutex 'mu2'}}
|
|
c[0].a = 57; // expected-warning {{writing the value pointed to by 'c' requires holding mutex 'mu2' exclusively}}
|
|
mu1.Unlock();
|
|
}
|
|
|
|
void test3() {
|
|
mu2.Lock();
|
|
if (*a == 0) doSomething(); // expected-warning {{reading variable 'a' requires holding mutex 'mu1'}}
|
|
*a = 0; // expected-warning {{reading variable 'a' requires holding mutex 'mu1'}}
|
|
|
|
if (c->a == 0) doSomething(); // expected-warning {{reading variable 'c' requires holding mutex 'mu1'}}
|
|
c->a = 0; // expected-warning {{reading variable 'c' requires holding mutex 'mu1'}}
|
|
|
|
if ((*c).a == 0) doSomething(); // expected-warning {{reading variable 'c' requires holding mutex 'mu1'}}
|
|
(*c).a = 0; // expected-warning {{reading variable 'c' requires holding mutex 'mu1'}}
|
|
|
|
if (a[0] == 42) doSomething(); // expected-warning {{reading variable 'a' requires holding mutex 'mu1'}}
|
|
a[0] = 57; // expected-warning {{reading variable 'a' requires holding mutex 'mu1'}}
|
|
if (c[0].a == 42) doSomething(); // expected-warning {{reading variable 'c' requires holding mutex 'mu1'}}
|
|
c[0].a = 57; // expected-warning {{reading variable 'c' requires holding mutex 'mu1'}}
|
|
mu2.Unlock();
|
|
}
|
|
|
|
void test4() { // Literal arrays
|
|
if (sa[0] == 42) doSomething(); // expected-warning {{reading variable 'sa' requires holding mutex 'mu1'}}
|
|
sa[0] = 57; // expected-warning {{writing variable 'sa' requires holding mutex 'mu1' exclusively}}
|
|
if (sc[0].a == 42) doSomething(); // expected-warning {{reading variable 'sc' requires holding mutex 'mu1'}}
|
|
sc[0].a = 57; // expected-warning {{writing variable 'sc' requires holding mutex 'mu1' exclusively}}
|
|
sc[0].*pa = 57; // expected-warning {{writing variable 'sc' requires holding mutex 'mu1' exclusively}}
|
|
|
|
if (*sa == 42) doSomething(); // expected-warning {{reading variable 'sa' requires holding mutex 'mu1'}}
|
|
*sa = 57; // expected-warning {{writing variable 'sa' requires holding mutex 'mu1' exclusively}}
|
|
if ((*sc).a == 42) doSomething(); // expected-warning {{reading variable 'sc' requires holding mutex 'mu1'}}
|
|
(*sc).a = 57; // expected-warning {{writing variable 'sc' requires holding mutex 'mu1' exclusively}}
|
|
if (sc->a == 42) doSomething(); // expected-warning {{reading variable 'sc' requires holding mutex 'mu1'}}
|
|
sc->a = 57; // expected-warning {{writing variable 'sc' requires holding mutex 'mu1' exclusively}}
|
|
}
|
|
|
|
void test5() {
|
|
mu1.ReaderLock(); // OK -- correct use.
|
|
mu2.Lock();
|
|
if (*a == 0) doSomething();
|
|
*a = 0;
|
|
|
|
if (c->a == 0) doSomething();
|
|
c->a = 0;
|
|
|
|
if ((*c).a == 0) doSomething();
|
|
(*c).a = 0;
|
|
mu2.Unlock();
|
|
mu1.Unlock();
|
|
}
|
|
};
|
|
|
|
|
|
class SmartPtr_PtGuardedBy_Test {
|
|
Mutex mu1;
|
|
Mutex mu2;
|
|
SmartPtr<int> sp GUARDED_BY(mu1) PT_GUARDED_BY(mu2);
|
|
SmartPtr<Cell> sq GUARDED_BY(mu1) PT_GUARDED_BY(mu2);
|
|
|
|
static constexpr int Cell::*pa = &Cell::a;
|
|
|
|
void test1() {
|
|
mu1.ReaderLock();
|
|
mu2.Lock();
|
|
|
|
sp.get();
|
|
if (*sp == 0) doSomething();
|
|
*sp = 0;
|
|
sq->a = 0;
|
|
sq->*pa = 0;
|
|
|
|
if (sp[0] == 0) doSomething();
|
|
sp[0] = 0;
|
|
|
|
mu2.Unlock();
|
|
mu1.Unlock();
|
|
}
|
|
|
|
void test2() {
|
|
mu2.Lock();
|
|
|
|
sp.get(); // expected-warning {{reading variable 'sp' requires holding mutex 'mu1'}}
|
|
if (*sp == 0) doSomething(); // expected-warning {{reading variable 'sp' requires holding mutex 'mu1'}}
|
|
*sp = 0; // expected-warning {{reading variable 'sp' requires holding mutex 'mu1'}}
|
|
sq->a = 0; // expected-warning {{reading variable 'sq' requires holding mutex 'mu1'}}
|
|
sq->*pa = 0; // expected-warning {{reading variable 'sq' requires holding mutex 'mu1'}}
|
|
|
|
if (sp[0] == 0) doSomething(); // expected-warning {{reading variable 'sp' requires holding mutex 'mu1'}}
|
|
sp[0] = 0; // expected-warning {{reading variable 'sp' requires holding mutex 'mu1'}}
|
|
if (sq[0].a == 0) doSomething(); // expected-warning {{reading variable 'sq' requires holding mutex 'mu1'}}
|
|
sq[0].a = 0; // expected-warning {{reading variable 'sq' requires holding mutex 'mu1'}}
|
|
|
|
mu2.Unlock();
|
|
}
|
|
|
|
void test3() {
|
|
mu1.Lock();
|
|
|
|
sp.get();
|
|
if (*sp == 0) doSomething(); // expected-warning {{reading the value pointed to by 'sp' requires holding mutex 'mu2'}}
|
|
*sp = 0; // expected-warning {{reading the value pointed to by 'sp' requires holding mutex 'mu2'}}
|
|
sq->a = 0; // expected-warning {{reading the value pointed to by 'sq' requires holding mutex 'mu2'}}
|
|
sq->*pa = 0; // expected-warning {{reading the value pointed to by 'sq' requires holding mutex 'mu2'}}
|
|
|
|
if (sp[0] == 0) doSomething(); // expected-warning {{reading the value pointed to by 'sp' requires holding mutex 'mu2'}}
|
|
sp[0] = 0; // expected-warning {{reading the value pointed to by 'sp' requires holding mutex 'mu2'}}
|
|
if (sq[0].a == 0) doSomething(); // expected-warning {{reading the value pointed to by 'sq' requires holding mutex 'mu2'}}
|
|
sq[0].a = 0; // expected-warning {{reading the value pointed to by 'sq' requires holding mutex 'mu2'}}
|
|
|
|
mu1.Unlock();
|
|
}
|
|
};
|
|
|
|
} // end namespace PtGuardedByTest
|
|
|
|
|
|
namespace NonMemberCalleeICETest {
|
|
|
|
class A {
|
|
void Run() {
|
|
(RunHelper)(); // expected-warning {{calling function 'RunHelper' requires holding mutex 'M' exclusively}}
|
|
}
|
|
|
|
void RunHelper() EXCLUSIVE_LOCKS_REQUIRED(M);
|
|
Mutex M;
|
|
};
|
|
|
|
} // end namespace NonMemberCalleeICETest
|
|
|
|
|
|
namespace pt_guard_attribute_type {
|
|
int i PT_GUARDED_BY(sls_mu); // expected-warning {{'pt_guarded_by' only applies to pointer types; type here is 'int'}}
|
|
int j PT_GUARDED_VAR; // expected-warning {{'pt_guarded_var' only applies to pointer types; type here is 'int'}}
|
|
|
|
void test() {
|
|
int i PT_GUARDED_BY(sls_mu); // expected-warning {{'pt_guarded_by' attribute only applies to non-static data members and global variables}}
|
|
int j PT_GUARDED_VAR; // expected-warning {{'pt_guarded_var' attribute only applies to non-static data members and global variables}}
|
|
|
|
typedef int PT_GUARDED_BY(sls_mu) bad1; // expected-warning {{'pt_guarded_by' attribute only applies to}}
|
|
typedef int PT_GUARDED_VAR bad2; // expected-warning {{'pt_guarded_var' attribute only applies to}}
|
|
}
|
|
} // end namespace pt_guard_attribute_type
|
|
|
|
|
|
namespace ThreadAttributesOnLambdas {
|
|
|
|
class Foo {
|
|
Mutex mu_;
|
|
|
|
void LockedFunction() EXCLUSIVE_LOCKS_REQUIRED(mu_);
|
|
|
|
void test() {
|
|
auto func1 = [this]() EXCLUSIVE_LOCKS_REQUIRED(mu_) {
|
|
LockedFunction();
|
|
};
|
|
|
|
auto func2 = [this]() NO_THREAD_SAFETY_ANALYSIS {
|
|
LockedFunction();
|
|
};
|
|
|
|
auto func3 = [this]() EXCLUSIVE_LOCK_FUNCTION(mu_) {
|
|
mu_.Lock();
|
|
};
|
|
|
|
func1(); // expected-warning {{calling function 'operator()' requires holding mutex 'mu_' exclusively}}
|
|
func1.operator()(); // expected-warning {{calling function 'operator()' requires holding mutex 'mu_' exclusively}}
|
|
func2();
|
|
func2.operator()();
|
|
func3();
|
|
mu_.Unlock();
|
|
func3.operator()();
|
|
mu_.Unlock();
|
|
}
|
|
};
|
|
|
|
} // end namespace ThreadAttributesOnLambdas
|
|
|
|
|
|
|
|
namespace AttributeExpressionCornerCases {
|
|
|
|
class Foo {
|
|
int a GUARDED_BY(getMu());
|
|
|
|
Mutex* getMu() LOCK_RETURNED("");
|
|
Mutex* getUniv() LOCK_RETURNED("*");
|
|
|
|
void test1() {
|
|
a = 0;
|
|
}
|
|
|
|
void test2() EXCLUSIVE_LOCKS_REQUIRED(getUniv()) {
|
|
a = 0;
|
|
}
|
|
|
|
void foo(Mutex* mu) EXCLUSIVE_LOCKS_REQUIRED(mu);
|
|
|
|
void test3() {
|
|
foo(nullptr);
|
|
}
|
|
};
|
|
|
|
|
|
class MapTest {
|
|
struct MuCell { Mutex* mu; };
|
|
|
|
MyMap<MyString, Mutex*> map;
|
|
MyMap<MyString, MuCell> mapCell;
|
|
|
|
int a GUARDED_BY(map["foo"]);
|
|
int b GUARDED_BY(mapCell["foo"].mu);
|
|
|
|
void test() {
|
|
map["foo"]->Lock();
|
|
a = 0;
|
|
map["foo"]->Unlock();
|
|
}
|
|
|
|
void test2() {
|
|
mapCell["foo"].mu->Lock();
|
|
b = 0;
|
|
mapCell["foo"].mu->Unlock();
|
|
}
|
|
};
|
|
|
|
|
|
class PreciseSmartPtr {
|
|
SmartPtr<Mutex> mu;
|
|
int val GUARDED_BY(mu);
|
|
|
|
static bool compare(PreciseSmartPtr& a, PreciseSmartPtr &b) {
|
|
a.mu->Lock();
|
|
bool result = (a.val == b.val); // expected-warning {{reading variable 'val' requires holding mutex 'b.mu'}} \
|
|
// expected-note {{found near match 'a.mu'}}
|
|
a.mu->Unlock();
|
|
return result;
|
|
}
|
|
};
|
|
|
|
|
|
class SmartRedeclare {
|
|
SmartPtr<Mutex> mu;
|
|
int val GUARDED_BY(mu);
|
|
|
|
void test() EXCLUSIVE_LOCKS_REQUIRED(mu);
|
|
void test2() EXCLUSIVE_LOCKS_REQUIRED(mu.get());
|
|
void test3() EXCLUSIVE_LOCKS_REQUIRED(mu.get());
|
|
};
|
|
|
|
|
|
void SmartRedeclare::test() EXCLUSIVE_LOCKS_REQUIRED(mu.get()) {
|
|
val = 0;
|
|
}
|
|
|
|
void SmartRedeclare::test2() EXCLUSIVE_LOCKS_REQUIRED(mu) {
|
|
val = 0;
|
|
}
|
|
|
|
void SmartRedeclare::test3() {
|
|
val = 0;
|
|
}
|
|
|
|
|
|
namespace CustomMutex {
|
|
|
|
|
|
class LOCKABLE BaseMutex { };
|
|
class DerivedMutex : public BaseMutex { };
|
|
|
|
void customLock(const BaseMutex *m) EXCLUSIVE_LOCK_FUNCTION(m);
|
|
void customUnlock(const BaseMutex *m) UNLOCK_FUNCTION(m);
|
|
|
|
static struct DerivedMutex custMu;
|
|
|
|
static void doSomethingRequiringLock() EXCLUSIVE_LOCKS_REQUIRED(custMu) { }
|
|
|
|
void customTest() {
|
|
customLock(reinterpret_cast<BaseMutex*>(&custMu)); // ignore casts
|
|
doSomethingRequiringLock();
|
|
customUnlock(reinterpret_cast<BaseMutex*>(&custMu));
|
|
}
|
|
|
|
} // end namespace CustomMutex
|
|
|
|
} // end AttributeExpressionCornerCases
|
|
|
|
|
|
namespace ScopedLockReturnedInvalid {
|
|
|
|
class Opaque;
|
|
|
|
Mutex* getMutex(Opaque* o) LOCK_RETURNED("");
|
|
|
|
void test(Opaque* o) {
|
|
MutexLock lock(getMutex(o));
|
|
}
|
|
|
|
} // end namespace ScopedLockReturnedInvalid
|
|
|
|
|
|
namespace NegativeRequirements {
|
|
|
|
class Bar {
|
|
Mutex mu;
|
|
int a GUARDED_BY(mu);
|
|
|
|
public:
|
|
void baz() EXCLUSIVE_LOCKS_REQUIRED(!mu) {
|
|
mu.Lock();
|
|
a = 0;
|
|
mu.Unlock();
|
|
}
|
|
};
|
|
|
|
|
|
class Foo {
|
|
Mutex mu;
|
|
int a GUARDED_BY(mu);
|
|
|
|
public:
|
|
void foo() {
|
|
mu.Lock(); // warning? needs !mu?
|
|
baz(); // expected-warning {{cannot call function 'baz' while mutex 'mu' is held}}
|
|
bar();
|
|
mu.Unlock();
|
|
}
|
|
|
|
void bar() {
|
|
bar2(); // expected-warning {{calling function 'bar2' requires negative capability '!mu'}}
|
|
}
|
|
|
|
void bar2() EXCLUSIVE_LOCKS_REQUIRED(!mu) {
|
|
baz();
|
|
}
|
|
|
|
void baz() EXCLUSIVE_LOCKS_REQUIRED(!mu) {
|
|
mu.Lock();
|
|
a = 0;
|
|
mu.Unlock();
|
|
}
|
|
|
|
void test() {
|
|
Bar b;
|
|
b.baz(); // no warning -- in different class.
|
|
}
|
|
};
|
|
|
|
} // end namespace NegativeRequirements
|
|
|
|
|
|
namespace NegativeThreadRoles {
|
|
|
|
typedef int __attribute__((capability("role"))) ThreadRole;
|
|
|
|
void acquire(ThreadRole R) EXCLUSIVE_LOCK_FUNCTION(R) NO_THREAD_SAFETY_ANALYSIS {}
|
|
void release(ThreadRole R) UNLOCK_FUNCTION(R) NO_THREAD_SAFETY_ANALYSIS {}
|
|
|
|
ThreadRole FlightControl, Logger;
|
|
|
|
extern void enque_log_msg(const char *msg);
|
|
void log_msg(const char *msg) {
|
|
enque_log_msg(msg);
|
|
}
|
|
|
|
void dispatch_log(const char *msg) __attribute__((requires_capability(!FlightControl))) {}
|
|
void dispatch_log2(const char *msg) __attribute__((requires_capability(Logger))) {}
|
|
|
|
void flight_control_entry(void) __attribute__((requires_capability(FlightControl))) {
|
|
dispatch_log("wrong"); /* expected-warning {{cannot call function 'dispatch_log' while role 'FlightControl' is held}} */
|
|
dispatch_log2("also wrong"); /* expected-warning {{calling function 'dispatch_log2' requires holding role 'Logger' exclusively}} */
|
|
}
|
|
|
|
void spawn_fake_flight_control_thread(void) {
|
|
acquire(FlightControl);
|
|
flight_control_entry();
|
|
release(FlightControl);
|
|
}
|
|
|
|
extern const char *deque_log_msg(void) __attribute__((requires_capability(Logger)));
|
|
void logger_entry(void) __attribute__((requires_capability(Logger)))
|
|
__attribute__((requires_capability(!FlightControl))) {
|
|
const char *msg;
|
|
|
|
while ((msg = deque_log_msg())) {
|
|
dispatch_log(msg);
|
|
}
|
|
}
|
|
|
|
void spawn_fake_logger_thread(void) __attribute__((requires_capability(!FlightControl))) {
|
|
acquire(Logger);
|
|
logger_entry();
|
|
release(Logger);
|
|
}
|
|
|
|
int main(void) __attribute__((requires_capability(!FlightControl))) {
|
|
spawn_fake_flight_control_thread();
|
|
spawn_fake_logger_thread();
|
|
|
|
for (;;)
|
|
; /* Pretend to dispatch things. */
|
|
|
|
return 0;
|
|
}
|
|
|
|
} // end namespace NegativeThreadRoles
|
|
|
|
|
|
namespace AssertSharedExclusive {
|
|
|
|
void doSomething();
|
|
|
|
class Foo {
|
|
Mutex mu;
|
|
int a GUARDED_BY(mu);
|
|
|
|
void test() SHARED_LOCKS_REQUIRED(mu) {
|
|
mu.AssertHeld();
|
|
if (a > 0)
|
|
doSomething();
|
|
}
|
|
};
|
|
|
|
} // end namespace AssertSharedExclusive
|
|
|
|
|
|
namespace RangeBasedForAndReferences {
|
|
|
|
class Foo {
|
|
struct MyStruct {
|
|
int a;
|
|
};
|
|
|
|
Mutex mu;
|
|
int a GUARDED_BY(mu);
|
|
MyContainer<int> cntr GUARDED_BY(mu);
|
|
MyStruct s GUARDED_BY(mu);
|
|
int arr[10] GUARDED_BY(mu);
|
|
|
|
void nonref_test() {
|
|
int b = a; // expected-warning {{reading variable 'a' requires holding mutex 'mu'}}
|
|
b = 0; // no warning
|
|
}
|
|
|
|
void auto_test() {
|
|
auto b = a; // expected-warning {{reading variable 'a' requires holding mutex 'mu'}}
|
|
b = 0; // no warning
|
|
auto &c = a; // no warning
|
|
c = 0; // expected-warning {{writing variable 'a' requires holding mutex 'mu' exclusively}}
|
|
}
|
|
|
|
void ref_test() {
|
|
int &b = a;
|
|
int &c = b;
|
|
int &d = c;
|
|
b = 0; // expected-warning {{writing variable 'a' requires holding mutex 'mu' exclusively}}
|
|
c = 0; // expected-warning {{writing variable 'a' requires holding mutex 'mu' exclusively}}
|
|
d = 0; // expected-warning {{writing variable 'a' requires holding mutex 'mu' exclusively}}
|
|
|
|
MyStruct &rs = s;
|
|
rs.a = 0; // expected-warning {{writing variable 's' requires holding mutex 'mu' exclusively}}
|
|
|
|
int (&rarr)[10] = arr;
|
|
rarr[2] = 0; // expected-warning {{writing variable 'arr' requires holding mutex 'mu' exclusively}}
|
|
}
|
|
|
|
void ptr_test() {
|
|
int *b = &a;
|
|
*b = 0; // no expected warning yet
|
|
}
|
|
|
|
void for_test() {
|
|
int total = 0;
|
|
for (int i : cntr) { // expected-warning2 {{reading variable 'cntr' requires holding mutex 'mu'}}
|
|
total += i;
|
|
}
|
|
}
|
|
};
|
|
|
|
|
|
} // end namespace RangeBasedForAndReferences
|
|
|
|
|
|
|
|
namespace PassByRefTest {
|
|
|
|
class Foo {
|
|
public:
|
|
Foo() : a(0), b(0) { }
|
|
|
|
int a;
|
|
int b;
|
|
|
|
void operator+(const Foo& f);
|
|
|
|
void operator[](const Foo& g);
|
|
|
|
void operator()();
|
|
};
|
|
|
|
template<class T>
|
|
T&& mymove(T& f);
|
|
|
|
|
|
// test top-level functions
|
|
void copy(Foo f);
|
|
void write1(Foo& f);
|
|
void write2(int a, Foo& f);
|
|
void write3(Foo* f);
|
|
void write4(Foo** f);
|
|
void read1(const Foo& f);
|
|
void read2(int a, const Foo& f);
|
|
void read3(const Foo* f);
|
|
void read4(Foo* const* f);
|
|
void destroy(Foo&& f);
|
|
|
|
void operator/(const Foo& f, const Foo& g);
|
|
void operator*(const Foo& f, const Foo& g);
|
|
|
|
// Test constructors.
|
|
struct FooRead {
|
|
FooRead(const Foo &);
|
|
};
|
|
struct FooWrite {
|
|
FooWrite(Foo &);
|
|
};
|
|
|
|
// Test variadic functions
|
|
template<typename... T>
|
|
void copyVariadic(T...) {}
|
|
template<typename... T>
|
|
void writeVariadic(T&...) {}
|
|
template<typename... T>
|
|
void readVariadic(const T&...) {}
|
|
|
|
void copyVariadicC(int, ...);
|
|
|
|
class Bar {
|
|
public:
|
|
Mutex mu;
|
|
Foo foo GUARDED_BY(mu);
|
|
Foo foo2 GUARDED_BY(mu);
|
|
Foo* foop PT_GUARDED_BY(mu);
|
|
Foo* foop2 GUARDED_BY(mu);
|
|
SmartPtr<Foo> foosp PT_GUARDED_BY(mu);
|
|
|
|
// test methods.
|
|
void mwrite1(Foo& f);
|
|
void mwrite2(int a, Foo& f);
|
|
void mwrite3(Foo* f);
|
|
void mread1(const Foo& f);
|
|
void mread2(int a, const Foo& f);
|
|
void mread3(const Foo* f);
|
|
|
|
// static methods
|
|
static void smwrite1(Foo& f);
|
|
static void smwrite2(int a, Foo& f);
|
|
static void smwrite3(Foo* f);
|
|
static void smread1(const Foo& f);
|
|
static void smread2(int a, const Foo& f);
|
|
static void smread3(const Foo* f);
|
|
|
|
void operator<<(const Foo& f);
|
|
|
|
void test1() {
|
|
copy(foo); // expected-warning {{reading variable 'foo' requires holding mutex 'mu'}}
|
|
write1(foo); // expected-warning {{passing variable 'foo' by reference requires holding mutex 'mu'}}
|
|
write2(10, foo); // expected-warning {{passing variable 'foo' by reference requires holding mutex 'mu'}}
|
|
read1(foo); // expected-warning {{passing variable 'foo' by reference requires holding mutex 'mu'}}
|
|
read2(10, foo); // expected-warning {{passing variable 'foo' by reference requires holding mutex 'mu'}}
|
|
destroy(mymove(foo)); // expected-warning {{passing variable 'foo' by reference requires holding mutex 'mu'}}
|
|
|
|
copyVariadic(foo); // expected-warning {{reading variable 'foo' requires holding mutex 'mu'}}
|
|
readVariadic(foo); // expected-warning {{passing variable 'foo' by reference requires holding mutex 'mu'}}
|
|
writeVariadic(foo); // expected-warning {{passing variable 'foo' by reference requires holding mutex 'mu'}}
|
|
copyVariadicC(1, foo); // expected-warning {{reading variable 'foo' requires holding mutex 'mu'}}
|
|
|
|
FooRead reader(foo); // expected-warning {{passing variable 'foo' by reference requires holding mutex 'mu'}}
|
|
FooWrite writer(foo); // expected-warning {{passing variable 'foo' by reference requires holding mutex 'mu'}}
|
|
|
|
mwrite1(foo); // expected-warning {{passing variable 'foo' by reference requires holding mutex 'mu'}}
|
|
mwrite2(10, foo); // expected-warning {{passing variable 'foo' by reference requires holding mutex 'mu'}}
|
|
mread1(foo); // expected-warning {{passing variable 'foo' by reference requires holding mutex 'mu'}}
|
|
mread2(10, foo); // expected-warning {{passing variable 'foo' by reference requires holding mutex 'mu'}}
|
|
|
|
smwrite1(foo); // expected-warning {{passing variable 'foo' by reference requires holding mutex 'mu'}}
|
|
smwrite2(10, foo); // expected-warning {{passing variable 'foo' by reference requires holding mutex 'mu'}}
|
|
smread1(foo); // expected-warning {{passing variable 'foo' by reference requires holding mutex 'mu'}}
|
|
smread2(10, foo); // expected-warning {{passing variable 'foo' by reference requires holding mutex 'mu'}}
|
|
|
|
foo + foo2; // expected-warning {{reading variable 'foo' requires holding mutex 'mu'}} \
|
|
// expected-warning {{passing variable 'foo2' by reference requires holding mutex 'mu'}}
|
|
foo / foo2; // expected-warning {{reading variable 'foo' requires holding mutex 'mu'}} \
|
|
// expected-warning {{passing variable 'foo2' by reference requires holding mutex 'mu'}}
|
|
foo * foo2; // expected-warning {{reading variable 'foo' requires holding mutex 'mu'}} \
|
|
// expected-warning {{passing variable 'foo2' by reference requires holding mutex 'mu'}}
|
|
foo[foo2]; // expected-warning {{reading variable 'foo' requires holding mutex 'mu'}} \
|
|
// expected-warning {{passing variable 'foo2' by reference requires holding mutex 'mu'}}
|
|
foo(); // expected-warning {{reading variable 'foo' requires holding mutex 'mu'}}
|
|
(*this) << foo; // expected-warning {{passing variable 'foo' by reference requires holding mutex 'mu'}}
|
|
|
|
copy(*foop); // expected-warning {{reading the value pointed to by 'foop' requires holding mutex 'mu'}}
|
|
write1(*foop); // expected-warning {{passing the value that 'foop' points to by reference requires holding mutex 'mu'}}
|
|
write2(10, *foop); // expected-warning {{passing the value that 'foop' points to by reference requires holding mutex 'mu'}}
|
|
read1(*foop); // expected-warning {{passing the value that 'foop' points to by reference requires holding mutex 'mu'}}
|
|
read2(10, *foop); // expected-warning {{passing the value that 'foop' points to by reference requires holding mutex 'mu'}}
|
|
destroy(mymove(*foop)); // expected-warning {{passing the value that 'foop' points to by reference requires holding mutex 'mu'}}
|
|
|
|
copy(*foosp); // expected-warning {{reading the value pointed to by 'foosp' requires holding mutex 'mu'}}
|
|
write1(*foosp); // expected-warning {{reading the value pointed to by 'foosp' requires holding mutex 'mu'}}
|
|
write2(10, *foosp); // expected-warning {{reading the value pointed to by 'foosp' requires holding mutex 'mu'}}
|
|
read1(*foosp); // expected-warning {{reading the value pointed to by 'foosp' requires holding mutex 'mu'}}
|
|
read2(10, *foosp); // expected-warning {{reading the value pointed to by 'foosp' requires holding mutex 'mu'}}
|
|
destroy(mymove(*foosp)); // expected-warning {{reading the value pointed to by 'foosp' requires holding mutex 'mu'}}
|
|
|
|
// TODO -- these require better smart pointer handling.
|
|
copy(*foosp.get());
|
|
write1(*foosp.get());
|
|
write2(10, *foosp.get());
|
|
read1(*foosp.get());
|
|
read2(10, *foosp.get());
|
|
destroy(mymove(*foosp.get()));
|
|
}
|
|
|
|
void test_pass_pointer() {
|
|
(void)&foo; // no warning
|
|
(void)foop; // no warning
|
|
|
|
write3(&foo); // expected-warning {{passing pointer to variable 'foo' requires holding mutex 'mu'}}
|
|
write3(foop); // expected-warning {{passing pointer 'foop' requires holding mutex 'mu'}}
|
|
write3(&*foop); // expected-warning {{passing pointer 'foop' requires holding mutex 'mu'}}
|
|
write4((Foo **)&foo); // expected-warning {{passing pointer to variable 'foo' requires holding mutex 'mu'}}
|
|
write4(&foop); // no warning
|
|
read3(&foo); // expected-warning {{passing pointer to variable 'foo' requires holding mutex 'mu'}}
|
|
read3(foop); // expected-warning {{passing pointer 'foop' requires holding mutex 'mu'}}
|
|
read3(&*foop); // expected-warning {{passing pointer 'foop' requires holding mutex 'mu'}}
|
|
read4((Foo **)&foo); // expected-warning {{passing pointer to variable 'foo' requires holding mutex 'mu'}}
|
|
read4(&foop); // no warning
|
|
mwrite3(&foo); // expected-warning {{passing pointer to variable 'foo' requires holding mutex 'mu'}}
|
|
mwrite3(foop); // expected-warning {{passing pointer 'foop' requires holding mutex 'mu'}}
|
|
mwrite3(&*foop); // expected-warning {{passing pointer 'foop' requires holding mutex 'mu'}}
|
|
mread3(&foo); // expected-warning {{passing pointer to variable 'foo' requires holding mutex 'mu'}}
|
|
mread3(foop); // expected-warning {{passing pointer 'foop' requires holding mutex 'mu'}}
|
|
mread3(&*foop); // expected-warning {{passing pointer 'foop' requires holding mutex 'mu'}}
|
|
smwrite3(&foo); // expected-warning {{passing pointer to variable 'foo' requires holding mutex 'mu'}}
|
|
smwrite3(foop); // expected-warning {{passing pointer 'foop' requires holding mutex 'mu'}}
|
|
smwrite3(&*foop); // expected-warning {{passing pointer 'foop' requires holding mutex 'mu'}}
|
|
smread3(&foo); // expected-warning {{passing pointer to variable 'foo' requires holding mutex 'mu'}}
|
|
smread3(foop); // expected-warning {{passing pointer 'foop' requires holding mutex 'mu'}}
|
|
smread3(&*foop); // expected-warning {{passing pointer 'foop' requires holding mutex 'mu'}}
|
|
|
|
write3(foop2); // expected-warning {{reading variable 'foop2' requires holding mutex 'mu'}}
|
|
write3(&*foop2); // expected-warning {{reading variable 'foop2' requires holding mutex 'mu'}}
|
|
write4(&foop2); // expected-warning {{passing pointer to variable 'foop2' requires holding mutex 'mu'}}
|
|
read3(foop2); // expected-warning {{reading variable 'foop2' requires holding mutex 'mu'}}
|
|
read3(&*foop2); // expected-warning {{reading variable 'foop2' requires holding mutex 'mu'}}
|
|
read4(&foop2); // expected-warning {{passing pointer to variable 'foop2' requires holding mutex 'mu'}}
|
|
mwrite3(foop2); // expected-warning {{reading variable 'foop2' requires holding mutex 'mu'}}
|
|
mwrite3(&*foop2); // expected-warning {{reading variable 'foop2' requires holding mutex 'mu'}}
|
|
mread3(foop2); // expected-warning {{reading variable 'foop2' requires holding mutex 'mu'}}
|
|
mread3(&*foop2); // expected-warning {{reading variable 'foop2' requires holding mutex 'mu'}}
|
|
smwrite3(foop2); // expected-warning {{reading variable 'foop2' requires holding mutex 'mu'}}
|
|
smwrite3(&*foop2); // expected-warning {{reading variable 'foop2' requires holding mutex 'mu'}}
|
|
smread3(foop2); // expected-warning {{reading variable 'foop2' requires holding mutex 'mu'}}
|
|
smread3(&*foop2); // expected-warning {{reading variable 'foop2' requires holding mutex 'mu'}}
|
|
|
|
mu.Lock();
|
|
write3(&foo);
|
|
write3(foop);
|
|
write3(&*foop);
|
|
write3(foop2);
|
|
write4(&foop2);
|
|
read3(&foo);
|
|
read3(foop);
|
|
read3(&*foop);
|
|
read3(foop2);
|
|
read4(&foop2);
|
|
mwrite3(&foo);
|
|
mwrite3(foop);
|
|
mwrite3(&*foop);
|
|
mwrite3(foop2);
|
|
mread3(&foo);
|
|
mread3(foop);
|
|
mread3(&*foop);
|
|
mread3(foop2);
|
|
smwrite3(&foo);
|
|
smwrite3(foop);
|
|
smwrite3(&*foop);
|
|
smwrite3(foop2);
|
|
smread3(&foo);
|
|
smread3(foop);
|
|
smread3(&*foop);
|
|
smread3(foop2);
|
|
mu.Unlock();
|
|
|
|
mu.ReaderLock();
|
|
write3(&foo);
|
|
write3(foop);
|
|
write3(&*foop);
|
|
write3(foop2);
|
|
write4(&foop2);
|
|
read3(&foo);
|
|
read3(foop);
|
|
read3(&*foop);
|
|
read3(foop2);
|
|
read4(&foop2);
|
|
mwrite3(&foo);
|
|
mwrite3(foop);
|
|
mwrite3(&*foop);
|
|
mwrite3(foop2);
|
|
mread3(&foo);
|
|
mread3(foop);
|
|
mread3(&*foop);
|
|
mread3(foop2);
|
|
smwrite3(&foo);
|
|
smwrite3(foop);
|
|
smwrite3(&*foop);
|
|
smwrite3(foop2);
|
|
smread3(&foo);
|
|
smread3(foop);
|
|
smread3(&*foop);
|
|
smread3(foop2);
|
|
mu.ReaderUnlock();
|
|
}
|
|
};
|
|
|
|
class Return {
|
|
Mutex mu;
|
|
Foo foo GUARDED_BY(mu);
|
|
Foo* foo_ptr PT_GUARDED_BY(mu);
|
|
Foo foo_depr GUARDED_VAR; // test deprecated attribute
|
|
Foo* foo_ptr_depr PT_GUARDED_VAR; // test deprecated attribute
|
|
|
|
Foo returns_value_locked() {
|
|
MutexLock lock(&mu);
|
|
return foo;
|
|
}
|
|
|
|
Foo returns_value_locks_required() EXCLUSIVE_LOCKS_REQUIRED(mu) {
|
|
return foo;
|
|
}
|
|
|
|
Foo returns_value_releases_lock_after_return() UNLOCK_FUNCTION(mu) {
|
|
MutexLock lock(&mu, true);
|
|
return foo;
|
|
}
|
|
|
|
Foo returns_value_aquires_lock() EXCLUSIVE_LOCK_FUNCTION(mu) {
|
|
mu.Lock();
|
|
return foo;
|
|
}
|
|
|
|
Foo returns_value_not_locked() {
|
|
return foo; // expected-warning {{reading variable 'foo' requires holding mutex 'mu'}}
|
|
}
|
|
|
|
Foo returns_value_releases_lock_before_return() UNLOCK_FUNCTION(mu) {
|
|
mu.Unlock();
|
|
return foo; // expected-warning {{reading variable 'foo' requires holding mutex 'mu'}}
|
|
}
|
|
|
|
Foo &returns_ref_not_locked() {
|
|
return foo; // expected-warning {{returning variable 'foo' by reference requires holding mutex 'mu'}}
|
|
}
|
|
|
|
Foo &returns_ref_locked() {
|
|
MutexLock lock(&mu);
|
|
return foo; // expected-warning {{returning variable 'foo' by reference requires holding mutex 'mu'}}
|
|
}
|
|
|
|
Foo &returns_ref_shared_locks_required() SHARED_LOCKS_REQUIRED(mu) {
|
|
return foo; // expected-warning {{returning variable 'foo' by reference requires holding mutex 'mu' exclusively}}
|
|
}
|
|
|
|
Foo &returns_ref_exclusive_locks_required() EXCLUSIVE_LOCKS_REQUIRED(mu) {
|
|
return foo;
|
|
}
|
|
|
|
Foo &returns_ref_releases_lock_after_return() UNLOCK_FUNCTION(mu) {
|
|
MutexLock lock(&mu, true);
|
|
return foo; // expected-warning {{returning variable 'foo' by reference requires holding mutex 'mu' exclusively}}
|
|
}
|
|
|
|
Foo& returns_ref_releases_lock_before_return() UNLOCK_FUNCTION(mu) {
|
|
mu.Unlock();
|
|
return foo; // // expected-warning {{returning variable 'foo' by reference requires holding mutex 'mu' exclusively}}
|
|
}
|
|
|
|
Foo &returns_ref_aquires_lock() EXCLUSIVE_LOCK_FUNCTION(mu) {
|
|
mu.Lock();
|
|
return foo;
|
|
}
|
|
|
|
const Foo &returns_constref_shared_locks_required() SHARED_LOCKS_REQUIRED(mu) {
|
|
return foo;
|
|
}
|
|
|
|
Foo *returns_ptr_exclusive_locks_required() EXCLUSIVE_LOCKS_REQUIRED(mu) {
|
|
return &foo;
|
|
}
|
|
|
|
Foo *returns_pt_ptr_exclusive_locks_required() EXCLUSIVE_LOCKS_REQUIRED(mu) {
|
|
return foo_ptr;
|
|
}
|
|
|
|
Foo *returns_ptr_shared_locks_required() SHARED_LOCKS_REQUIRED(mu) {
|
|
return &foo; // expected-warning {{returning pointer to variable 'foo' requires holding mutex 'mu' exclusively}}
|
|
}
|
|
|
|
Foo *returns_pt_ptr_shared_locks_required() SHARED_LOCKS_REQUIRED(mu) {
|
|
return foo_ptr; // expected-warning {{returning pointer 'foo_ptr' requires holding mutex 'mu' exclusively}}
|
|
}
|
|
|
|
const Foo *returns_constptr_shared_locks_required() SHARED_LOCKS_REQUIRED(mu) {
|
|
return &foo;
|
|
}
|
|
|
|
const Foo *returns_pt_constptr_shared_locks_required() SHARED_LOCKS_REQUIRED(mu) {
|
|
return foo_ptr;
|
|
}
|
|
|
|
Foo *returns_ptr() {
|
|
return &foo; // expected-warning {{returning pointer to variable 'foo' requires holding mutex 'mu' exclusively}}
|
|
}
|
|
|
|
Foo *returns_pt_ptr() {
|
|
return foo_ptr; // expected-warning {{returning pointer 'foo_ptr' requires holding mutex 'mu' exclusively}}
|
|
}
|
|
|
|
Foo &returns_ref2() {
|
|
return *foo_ptr; // expected-warning {{returning the value that 'foo_ptr' points to by reference requires holding mutex 'mu' exclusively}}
|
|
}
|
|
|
|
Foo *returns_ptr_deprecated() {
|
|
return &foo_depr; // expected-warning {{writing variable 'foo_depr' requires holding any mutex exclusively}}
|
|
}
|
|
|
|
Foo *returns_pt_ptr_deprecated() {
|
|
return foo_ptr_depr; // expected-warning {{writing the value pointed to by 'foo_ptr_depr' requires holding any mutex exclusively}}
|
|
}
|
|
|
|
Foo &returns_ref_deprecated() {
|
|
return *foo_ptr_depr; // expected-warning {{writing the value pointed to by 'foo_ptr_depr' requires holding any mutex exclusively}}
|
|
}
|
|
|
|
// FIXME: Basic alias analysis would help catch cases like below.
|
|
Foo *returns_ptr_alias() {
|
|
mu.Lock();
|
|
Foo *ret = &foo;
|
|
mu.Unlock();
|
|
return ret; // no warning (false negative)
|
|
}
|
|
|
|
Foo *returns_pt_ptr_alias() {
|
|
mu.Lock();
|
|
Foo *ret = foo_ptr;
|
|
mu.Unlock();
|
|
return ret; // no warning (false negative)
|
|
}
|
|
|
|
Foo &returns_ref2_alias() {
|
|
mu.Lock();
|
|
Foo *ret = foo_ptr;
|
|
mu.Unlock();
|
|
return *ret; // no warning (false negative)
|
|
}
|
|
};
|
|
|
|
|
|
} // end namespace PassByRefTest
|
|
|
|
|
|
namespace AcquiredBeforeAfterText {
|
|
|
|
class Foo {
|
|
Mutex mu1 ACQUIRED_BEFORE(mu2, mu3);
|
|
Mutex mu2;
|
|
Mutex mu3;
|
|
|
|
void test1() {
|
|
mu1.Lock();
|
|
mu2.Lock();
|
|
mu3.Lock();
|
|
|
|
mu3.Unlock();
|
|
mu2.Unlock();
|
|
mu1.Unlock();
|
|
}
|
|
|
|
void test2() {
|
|
mu2.Lock();
|
|
mu1.Lock(); // expected-warning {{mutex 'mu1' must be acquired before 'mu2'}}
|
|
mu1.Unlock();
|
|
mu2.Unlock();
|
|
}
|
|
|
|
void test3() {
|
|
mu3.Lock();
|
|
mu1.Lock(); // expected-warning {{mutex 'mu1' must be acquired before 'mu3'}}
|
|
mu1.Unlock();
|
|
mu3.Unlock();
|
|
}
|
|
|
|
void test4() EXCLUSIVE_LOCKS_REQUIRED(mu1) {
|
|
mu2.Lock();
|
|
mu2.Unlock();
|
|
}
|
|
|
|
void test5() EXCLUSIVE_LOCKS_REQUIRED(mu2) {
|
|
mu1.Lock(); // expected-warning {{mutex 'mu1' must be acquired before 'mu2'}}
|
|
mu1.Unlock();
|
|
}
|
|
|
|
void test6() EXCLUSIVE_LOCKS_REQUIRED(mu2) {
|
|
mu1.AssertHeld();
|
|
}
|
|
|
|
void test7() EXCLUSIVE_LOCKS_REQUIRED(mu1, mu2, mu3) { }
|
|
|
|
void test8() EXCLUSIVE_LOCKS_REQUIRED(mu3, mu2, mu1) { }
|
|
};
|
|
|
|
|
|
class Foo2 {
|
|
Mutex mu1;
|
|
Mutex mu2 ACQUIRED_AFTER(mu1);
|
|
Mutex mu3 ACQUIRED_AFTER(mu1);
|
|
|
|
void test1() {
|
|
mu1.Lock();
|
|
mu2.Lock();
|
|
mu3.Lock();
|
|
|
|
mu3.Unlock();
|
|
mu2.Unlock();
|
|
mu1.Unlock();
|
|
}
|
|
|
|
void test2() {
|
|
mu2.Lock();
|
|
mu1.Lock(); // expected-warning {{mutex 'mu1' must be acquired before 'mu2'}}
|
|
mu1.Unlock();
|
|
mu2.Unlock();
|
|
}
|
|
|
|
void test3() {
|
|
mu3.Lock();
|
|
mu1.Lock(); // expected-warning {{mutex 'mu1' must be acquired before 'mu3'}}
|
|
mu1.Unlock();
|
|
mu3.Unlock();
|
|
}
|
|
};
|
|
|
|
|
|
class Foo3 {
|
|
Mutex mu1 ACQUIRED_BEFORE(mu2);
|
|
Mutex mu2;
|
|
Mutex mu3 ACQUIRED_AFTER(mu2) ACQUIRED_BEFORE(mu4);
|
|
Mutex mu4;
|
|
|
|
void test1() {
|
|
mu1.Lock();
|
|
mu2.Lock();
|
|
mu3.Lock();
|
|
mu4.Lock();
|
|
|
|
mu4.Unlock();
|
|
mu3.Unlock();
|
|
mu2.Unlock();
|
|
mu1.Unlock();
|
|
}
|
|
|
|
void test2() {
|
|
mu4.Lock();
|
|
mu2.Lock(); // expected-warning {{mutex 'mu2' must be acquired before 'mu4'}}
|
|
|
|
mu2.Unlock();
|
|
mu4.Unlock();
|
|
}
|
|
|
|
void test3() {
|
|
mu4.Lock();
|
|
mu1.Lock(); // expected-warning {{mutex 'mu1' must be acquired before 'mu4'}}
|
|
|
|
mu1.Unlock();
|
|
mu4.Unlock();
|
|
}
|
|
|
|
void test4() {
|
|
mu3.Lock();
|
|
mu1.Lock(); // expected-warning {{mutex 'mu1' must be acquired before 'mu3'}}
|
|
|
|
mu1.Unlock();
|
|
mu3.Unlock();
|
|
}
|
|
};
|
|
|
|
|
|
// Test transitive DAG traversal with AFTER
|
|
class Foo4 {
|
|
Mutex mu1;
|
|
Mutex mu2 ACQUIRED_AFTER(mu1);
|
|
Mutex mu3 ACQUIRED_AFTER(mu1);
|
|
Mutex mu4 ACQUIRED_AFTER(mu2, mu3);
|
|
Mutex mu5 ACQUIRED_AFTER(mu4);
|
|
Mutex mu6 ACQUIRED_AFTER(mu4);
|
|
Mutex mu7 ACQUIRED_AFTER(mu5, mu6);
|
|
Mutex mu8 ACQUIRED_AFTER(mu7);
|
|
|
|
void test() {
|
|
mu8.Lock();
|
|
mu1.Lock(); // expected-warning {{mutex 'mu1' must be acquired before 'mu8'}}
|
|
mu1.Unlock();
|
|
mu8.Unlock();
|
|
}
|
|
};
|
|
|
|
|
|
// Test transitive DAG traversal with BEFORE
|
|
class Foo5 {
|
|
Mutex mu1 ACQUIRED_BEFORE(mu2, mu3);
|
|
Mutex mu2 ACQUIRED_BEFORE(mu4);
|
|
Mutex mu3 ACQUIRED_BEFORE(mu4);
|
|
Mutex mu4 ACQUIRED_BEFORE(mu5, mu6);
|
|
Mutex mu5 ACQUIRED_BEFORE(mu7);
|
|
Mutex mu6 ACQUIRED_BEFORE(mu7);
|
|
Mutex mu7 ACQUIRED_BEFORE(mu8);
|
|
Mutex mu8;
|
|
|
|
void test() {
|
|
mu8.Lock();
|
|
mu1.Lock(); // expected-warning {{mutex 'mu1' must be acquired before 'mu8'}}
|
|
mu1.Unlock();
|
|
mu8.Unlock();
|
|
}
|
|
};
|
|
|
|
|
|
class Foo6 {
|
|
Mutex mu1 ACQUIRED_AFTER(mu3); // expected-warning {{cycle in acquired_before/after dependencies, starting with 'mu1'}}
|
|
Mutex mu2 ACQUIRED_AFTER(mu1); // expected-warning {{cycle in acquired_before/after dependencies, starting with 'mu2'}}
|
|
Mutex mu3 ACQUIRED_AFTER(mu2); // expected-warning {{cycle in acquired_before/after dependencies, starting with 'mu3'}}
|
|
|
|
Mutex mu_b ACQUIRED_BEFORE(mu_b); // expected-warning {{cycle in acquired_before/after dependencies, starting with 'mu_b'}}
|
|
Mutex mu_a ACQUIRED_AFTER(mu_a); // expected-warning {{cycle in acquired_before/after dependencies, starting with 'mu_a'}}
|
|
|
|
void test0() {
|
|
mu_a.Lock();
|
|
mu_b.Lock();
|
|
mu_b.Unlock();
|
|
mu_a.Unlock();
|
|
}
|
|
|
|
void test1a() {
|
|
mu1.Lock();
|
|
mu1.Unlock();
|
|
}
|
|
|
|
void test1b() {
|
|
mu1.Lock();
|
|
mu_a.Lock();
|
|
mu_b.Lock();
|
|
mu_b.Unlock();
|
|
mu_a.Unlock();
|
|
mu1.Unlock();
|
|
}
|
|
|
|
void test() {
|
|
mu2.Lock();
|
|
mu2.Unlock();
|
|
}
|
|
|
|
void test3() {
|
|
mu3.Lock();
|
|
mu3.Unlock();
|
|
}
|
|
};
|
|
|
|
} // end namespace AcquiredBeforeAfterTest
|
|
|
|
|
|
namespace ScopedAdoptTest {
|
|
|
|
class Foo {
|
|
Mutex mu;
|
|
int a GUARDED_BY(mu);
|
|
int b;
|
|
|
|
void test1() EXCLUSIVE_UNLOCK_FUNCTION(mu) {
|
|
MutexLock slock(&mu, true);
|
|
a = 0;
|
|
}
|
|
|
|
void test2() SHARED_UNLOCK_FUNCTION(mu) {
|
|
ReaderMutexLock slock(&mu, true);
|
|
b = a;
|
|
}
|
|
|
|
void test3() EXCLUSIVE_LOCKS_REQUIRED(mu) { // expected-note {{mutex acquired here}}
|
|
MutexLock slock(&mu, true);
|
|
a = 0;
|
|
} // expected-warning {{expecting mutex 'mu' to be held at the end of function}}
|
|
|
|
void test4() SHARED_LOCKS_REQUIRED(mu) { // expected-note {{mutex acquired here}}
|
|
ReaderMutexLock slock(&mu, true);
|
|
b = a;
|
|
} // expected-warning {{expecting mutex 'mu' to be held at the end of function}}
|
|
|
|
};
|
|
|
|
} // end namespace ScopedAdoptTest
|
|
|
|
|
|
namespace TestReferenceNoThreadSafetyAnalysis {
|
|
|
|
#define TS_UNCHECKED_READ(x) ts_unchecked_read(x)
|
|
|
|
// Takes a reference to a guarded data member, and returns an unguarded
|
|
// reference.
|
|
template <class T>
|
|
inline const T& ts_unchecked_read(const T& v) NO_THREAD_SAFETY_ANALYSIS {
|
|
return v;
|
|
}
|
|
|
|
template <class T>
|
|
inline T& ts_unchecked_read(T& v) NO_THREAD_SAFETY_ANALYSIS {
|
|
return v;
|
|
}
|
|
|
|
|
|
class Foo {
|
|
public:
|
|
Foo(): a(0) { }
|
|
|
|
int a;
|
|
};
|
|
|
|
|
|
class Bar {
|
|
public:
|
|
Bar() : a(0) { }
|
|
|
|
Mutex mu;
|
|
int a GUARDED_BY(mu);
|
|
Foo foo GUARDED_BY(mu);
|
|
};
|
|
|
|
|
|
void test() {
|
|
Bar bar;
|
|
const Bar cbar;
|
|
|
|
int a = TS_UNCHECKED_READ(bar.a); // nowarn
|
|
TS_UNCHECKED_READ(bar.a) = 1; // nowarn
|
|
|
|
int b = TS_UNCHECKED_READ(bar.foo).a; // nowarn
|
|
TS_UNCHECKED_READ(bar.foo).a = 1; // nowarn
|
|
|
|
int c = TS_UNCHECKED_READ(cbar.a); // nowarn
|
|
}
|
|
|
|
#undef TS_UNCHECKED_READ
|
|
|
|
} // end namespace TestReferenceNoThreadSafetyAnalysis
|
|
|
|
|
|
namespace GlobalAcquiredBeforeAfterTest {
|
|
|
|
Mutex mu1;
|
|
Mutex mu2 ACQUIRED_AFTER(mu1);
|
|
|
|
void test3() {
|
|
mu2.Lock();
|
|
mu1.Lock(); // expected-warning {{mutex 'mu1' must be acquired before 'mu2'}}
|
|
mu1.Unlock();
|
|
mu2.Unlock();
|
|
}
|
|
|
|
} // end namespace GlobalAcquiredBeforeAfterTest
|
|
|
|
|
|
namespace LifetimeExtensionText {
|
|
|
|
struct Holder {
|
|
virtual ~Holder() throw() {}
|
|
int i = 0;
|
|
};
|
|
|
|
void test() {
|
|
// Should not crash.
|
|
const auto &value = Holder().i;
|
|
}
|
|
|
|
} // end namespace LifetimeExtensionTest
|
|
|
|
|
|
namespace LockableUnions {
|
|
|
|
union LOCKABLE MutexUnion {
|
|
int a;
|
|
char* b;
|
|
|
|
void Lock() EXCLUSIVE_LOCK_FUNCTION();
|
|
void Unlock() UNLOCK_FUNCTION();
|
|
};
|
|
|
|
MutexUnion muun2;
|
|
MutexUnion muun1 ACQUIRED_BEFORE(muun2);
|
|
|
|
void test() {
|
|
muun2.Lock();
|
|
muun1.Lock(); // expected-warning {{mutex 'muun1' must be acquired before 'muun2'}}
|
|
muun1.Unlock();
|
|
muun2.Unlock();
|
|
}
|
|
|
|
} // end namespace LockableUnions
|
|
|
|
// This used to crash.
|
|
class acquired_before_empty_str {
|
|
void WaitUntilSpaceAvailable() {
|
|
lock_.ReaderLock(); // expected-note {{acquired here}}
|
|
} // expected-warning {{mutex 'lock_' is still held at the end of function}}
|
|
Mutex lock_ ACQUIRED_BEFORE("");
|
|
};
|
|
|
|
namespace PR34800 {
|
|
struct A {
|
|
operator int() const;
|
|
};
|
|
struct B {
|
|
bool g() __attribute__((locks_excluded(h))); // expected-warning {{'locks_excluded' attribute requires arguments whose type is annotated with 'capability' attribute; type here is 'int'}}
|
|
int h;
|
|
};
|
|
struct C {
|
|
B *operator[](int);
|
|
};
|
|
C c;
|
|
void f() { c[A()]->g(); }
|
|
} // namespace PR34800
|
|
|
|
#ifdef __cpp_guaranteed_copy_elision
|
|
|
|
namespace ReturnScopedLockable {
|
|
|
|
class Object {
|
|
public:
|
|
MutexLock lock() EXCLUSIVE_LOCK_FUNCTION(mutex) {
|
|
return MutexLock(&mutex);
|
|
}
|
|
|
|
ReaderMutexLock lockShared() SHARED_LOCK_FUNCTION(mutex) {
|
|
return ReaderMutexLock(&mutex);
|
|
}
|
|
|
|
MutexLock adopt() EXCLUSIVE_LOCKS_REQUIRED(mutex) {
|
|
return MutexLock(&mutex, true);
|
|
}
|
|
|
|
ReaderMutexLock adoptShared() SHARED_LOCKS_REQUIRED(mutex) {
|
|
return ReaderMutexLock(&mutex, true);
|
|
}
|
|
|
|
int x GUARDED_BY(mutex);
|
|
void needsLock() EXCLUSIVE_LOCKS_REQUIRED(mutex);
|
|
|
|
void testInside() {
|
|
MutexLock scope = lock();
|
|
x = 1;
|
|
needsLock();
|
|
}
|
|
|
|
Mutex mutex;
|
|
};
|
|
|
|
Object obj;
|
|
|
|
void testLock() {
|
|
MutexLock scope = obj.lock();
|
|
obj.x = 1;
|
|
obj.needsLock();
|
|
}
|
|
|
|
int testSharedLock() {
|
|
ReaderMutexLock scope = obj.lockShared();
|
|
obj.x = 1; // expected-warning {{writing variable 'x' requires holding mutex 'obj.mutex' exclusively}}
|
|
return obj.x;
|
|
}
|
|
|
|
void testAdopt() {
|
|
obj.mutex.Lock();
|
|
MutexLock scope = obj.adopt();
|
|
obj.x = 1;
|
|
}
|
|
|
|
int testAdoptShared() {
|
|
obj.mutex.Lock();
|
|
ReaderMutexLock scope = obj.adoptShared();
|
|
obj.x = 1;
|
|
return obj.x;
|
|
}
|
|
|
|
} // namespace ReturnScopedLockable
|
|
|
|
#endif // __cpp_guaranteed_copy_elision
|
|
|
|
namespace PR38640 {
|
|
void f() {
|
|
// Self-referencing assignment previously caused an infinite loop when thread
|
|
// safety analysis was enabled.
|
|
int &i = i; // expected-warning {{reference 'i' is not yet bound to a value when used within its own initialization}}
|
|
}
|
|
} // namespace PR38640
|
|
|
|
namespace Derived_Smart_Pointer {
|
|
template <class T>
|
|
class SmartPtr_Derived : public SmartPtr<T> {};
|
|
|
|
class Foo {
|
|
public:
|
|
SmartPtr_Derived<Mutex> mu_;
|
|
int a GUARDED_BY(mu_);
|
|
int b GUARDED_BY(mu_.get());
|
|
int c GUARDED_BY(*mu_);
|
|
|
|
void Lock() EXCLUSIVE_LOCK_FUNCTION(mu_);
|
|
void Unlock() UNLOCK_FUNCTION(mu_);
|
|
|
|
void test0() {
|
|
a = 1; // expected-warning {{writing variable 'a' requires holding mutex 'mu_' exclusively}}
|
|
b = 1; // expected-warning {{writing variable 'b' requires holding mutex 'mu_' exclusively}}
|
|
c = 1; // expected-warning {{writing variable 'c' requires holding mutex 'mu_' exclusively}}
|
|
}
|
|
|
|
void test1() {
|
|
Lock();
|
|
a = 1;
|
|
b = 1;
|
|
c = 1;
|
|
Unlock();
|
|
}
|
|
};
|
|
|
|
class Bar {
|
|
SmartPtr_Derived<Foo> foo;
|
|
|
|
void test0() {
|
|
foo->a = 1; // expected-warning {{writing variable 'a' requires holding mutex 'foo->mu_' exclusively}}
|
|
(*foo).b = 1; // expected-warning {{writing variable 'b' requires holding mutex 'foo->mu_' exclusively}}
|
|
foo.get()->c = 1; // expected-warning {{writing variable 'c' requires holding mutex 'foo->mu_' exclusively}}
|
|
}
|
|
|
|
void test1() {
|
|
foo->Lock();
|
|
foo->a = 1;
|
|
foo->Unlock();
|
|
|
|
foo->mu_->Lock();
|
|
foo->b = 1;
|
|
foo->mu_->Unlock();
|
|
|
|
MutexLock lock(foo->mu_.get());
|
|
foo->c = 1;
|
|
}
|
|
};
|
|
|
|
class PointerGuard {
|
|
Mutex mu1;
|
|
Mutex mu2;
|
|
SmartPtr_Derived<int> i GUARDED_BY(mu1) PT_GUARDED_BY(mu2);
|
|
|
|
void test0() {
|
|
i.get(); // expected-warning {{reading variable 'i' requires holding mutex 'mu1'}}
|
|
*i = 2; // expected-warning {{reading variable 'i' requires holding mutex 'mu1'}} \
|
|
// expected-warning {{reading the value pointed to by 'i' requires holding mutex 'mu2'}}
|
|
|
|
}
|
|
|
|
void test1() {
|
|
mu1.Lock();
|
|
|
|
i.get();
|
|
*i = 2; // expected-warning {{reading the value pointed to by 'i' requires holding mutex 'mu2'}}
|
|
|
|
mu1.Unlock();
|
|
}
|
|
|
|
void test2() {
|
|
mu2.Lock();
|
|
|
|
i.get(); // expected-warning {{reading variable 'i' requires holding mutex 'mu1'}}
|
|
*i = 2; // expected-warning {{reading variable 'i' requires holding mutex 'mu1'}}
|
|
|
|
mu2.Unlock();
|
|
}
|
|
|
|
void test3() {
|
|
mu1.Lock();
|
|
mu2.Lock();
|
|
|
|
i.get();
|
|
*i = 2;
|
|
|
|
mu2.Unlock();
|
|
mu1.Unlock();
|
|
}
|
|
};
|
|
} // namespace Derived_Smart_Pointer
|
|
|
|
// Test for capabilities that are heap-allocated and stored in static variables.
|
|
namespace FunctionStaticVariable {
|
|
struct Data {
|
|
Mutex mu;
|
|
int x GUARDED_BY(mu);
|
|
};
|
|
|
|
void testStaticVariable() {
|
|
}
|
|
|
|
void testHeapAllocation() {
|
|
static Data *d = new Data;
|
|
d->mu.Lock();
|
|
d->x = 5;
|
|
d->mu.Unlock();
|
|
}
|
|
|
|
void testHeapAllocationBug() {
|
|
static auto *d = new Data;
|
|
d->x = 10; // expected-warning{{writing variable 'x' requires holding mutex 'd->mu' exclusively}}
|
|
}
|
|
|
|
void testHeapAllocationScopedLock() {
|
|
static Mutex *mu = new Mutex;
|
|
MutexLock lock(mu);
|
|
}
|
|
} // namespace FunctionStaticVariable
|
|
|
|
namespace Reentrancy {
|
|
|
|
class LOCKABLE REENTRANT_CAPABILITY ReentrantMutex {
|
|
public:
|
|
void Lock() EXCLUSIVE_LOCK_FUNCTION();
|
|
void ReaderLock() SHARED_LOCK_FUNCTION();
|
|
void Unlock() UNLOCK_FUNCTION();
|
|
void ExclusiveUnlock() EXCLUSIVE_UNLOCK_FUNCTION();
|
|
void ReaderUnlock() SHARED_UNLOCK_FUNCTION();
|
|
bool TryLock() EXCLUSIVE_TRYLOCK_FUNCTION(true);
|
|
bool ReaderTryLock() SHARED_TRYLOCK_FUNCTION(true);
|
|
|
|
// for negative capabilities
|
|
const ReentrantMutex& operator!() const { return *this; }
|
|
|
|
void AssertHeld() ASSERT_EXCLUSIVE_LOCK();
|
|
void AssertReaderHeld() ASSERT_SHARED_LOCK();
|
|
};
|
|
|
|
class SCOPED_LOCKABLE ReentrantMutexLock {
|
|
public:
|
|
ReentrantMutexLock(ReentrantMutex *mu) EXCLUSIVE_LOCK_FUNCTION(mu);
|
|
~ReentrantMutexLock() UNLOCK_FUNCTION();
|
|
};
|
|
|
|
class SCOPED_LOCKABLE ReentrantReaderMutexLock {
|
|
public:
|
|
ReentrantReaderMutexLock(ReentrantMutex *mu) SHARED_LOCK_FUNCTION(mu);
|
|
~ReentrantReaderMutexLock() UNLOCK_FUNCTION();
|
|
};
|
|
|
|
class SCOPED_LOCKABLE RelockableReentrantMutexLock {
|
|
public:
|
|
RelockableReentrantMutexLock(ReentrantMutex *mu) EXCLUSIVE_LOCK_FUNCTION(mu);
|
|
~RelockableReentrantMutexLock() EXCLUSIVE_UNLOCK_FUNCTION();
|
|
|
|
void Lock() EXCLUSIVE_LOCK_FUNCTION();
|
|
void Unlock() UNLOCK_FUNCTION();
|
|
};
|
|
|
|
ReentrantMutex rmu;
|
|
int guard_var __attribute__((guarded_var)) = 0;
|
|
int guardby_var __attribute__((guarded_by(rmu))) = 0;
|
|
|
|
void testReentrantMany() {
|
|
rmu.Lock();
|
|
rmu.Lock();
|
|
rmu.Lock();
|
|
rmu.Lock();
|
|
rmu.Lock();
|
|
rmu.Lock();
|
|
rmu.Lock();
|
|
rmu.Lock();
|
|
rmu.Unlock();
|
|
rmu.Unlock();
|
|
rmu.Unlock();
|
|
rmu.Unlock();
|
|
rmu.Unlock();
|
|
rmu.Unlock();
|
|
rmu.Unlock();
|
|
rmu.Unlock();
|
|
}
|
|
|
|
void testReentrantManyReader() {
|
|
rmu.ReaderLock();
|
|
rmu.ReaderLock();
|
|
rmu.ReaderLock();
|
|
rmu.ReaderLock();
|
|
rmu.ReaderLock();
|
|
rmu.ReaderLock();
|
|
rmu.ReaderLock();
|
|
rmu.ReaderLock();
|
|
rmu.ReaderUnlock();
|
|
rmu.ReaderUnlock();
|
|
rmu.ReaderUnlock();
|
|
rmu.ReaderUnlock();
|
|
rmu.ReaderUnlock();
|
|
rmu.ReaderUnlock();
|
|
rmu.ReaderUnlock();
|
|
rmu.ReaderUnlock();
|
|
}
|
|
|
|
void testReentrantLock1() {
|
|
rmu.Lock();
|
|
guard_var = 2;
|
|
rmu.Lock();
|
|
guard_var = 2;
|
|
rmu.Unlock();
|
|
guard_var = 2;
|
|
rmu.Unlock();
|
|
guard_var = 2; // expected-warning{{writing variable 'guard_var' requires holding any mutex exclusively}}
|
|
}
|
|
|
|
void testReentrantReaderLock1() {
|
|
rmu.ReaderLock();
|
|
int x = guard_var;
|
|
rmu.ReaderLock();
|
|
int y = guard_var;
|
|
rmu.ReaderUnlock();
|
|
int z = guard_var;
|
|
rmu.ReaderUnlock();
|
|
int a = guard_var; // expected-warning{{reading variable 'guard_var' requires holding any mutex}}
|
|
}
|
|
|
|
void testReentrantLock2() {
|
|
rmu.Lock();
|
|
guardby_var = 2;
|
|
rmu.Lock();
|
|
guardby_var = 2;
|
|
rmu.Unlock();
|
|
guardby_var = 2;
|
|
rmu.Unlock();
|
|
guardby_var = 2; // expected-warning{{writing variable 'guardby_var' requires holding mutex 'rmu' exclusively}}
|
|
}
|
|
|
|
void testReentrantReaderLock2() {
|
|
rmu.ReaderLock();
|
|
int x = guardby_var;
|
|
rmu.ReaderLock();
|
|
int y = guardby_var;
|
|
rmu.ReaderUnlock();
|
|
int z = guardby_var;
|
|
rmu.ReaderUnlock();
|
|
int a = guardby_var; // expected-warning{{reading variable 'guardby_var' requires holding mutex 'rmu'}}
|
|
}
|
|
|
|
void testReentrantTryLock1() {
|
|
if (rmu.TryLock()) {
|
|
guardby_var = 1;
|
|
if (rmu.TryLock()) {
|
|
guardby_var = 1;
|
|
rmu.Unlock();
|
|
}
|
|
guardby_var = 1;
|
|
rmu.Unlock();
|
|
}
|
|
guardby_var = 1; // expected-warning{{writing variable 'guardby_var' requires holding mutex 'rmu' exclusively}}
|
|
}
|
|
|
|
void testReentrantTryLock2() {
|
|
rmu.Lock();
|
|
guardby_var = 1;
|
|
if (rmu.TryLock()) {
|
|
guardby_var = 1;
|
|
rmu.Unlock();
|
|
}
|
|
guardby_var = 1;
|
|
rmu.Unlock();
|
|
guardby_var = 1; // expected-warning{{writing variable 'guardby_var' requires holding mutex 'rmu' exclusively}}
|
|
}
|
|
|
|
void testReentrantNotHeld() {
|
|
rmu.Unlock(); // \
|
|
// expected-warning{{releasing mutex 'rmu' that was not held}}
|
|
}
|
|
|
|
void testReentrantMissingUnlock() {
|
|
rmu.Lock(); // expected-note{{mutex acquired here}}
|
|
rmu.Lock(); // reenter
|
|
rmu.Unlock();
|
|
} // expected-warning{{mutex 'rmu' is still held at the end of function}}
|
|
|
|
// Acquiring the same capability with different access privileges is not
|
|
// considered reentrant.
|
|
void testMixedSharedExclusive() {
|
|
rmu.ReaderLock(); // expected-note{{mutex acquired here}}
|
|
rmu.Lock(); // expected-warning{{acquiring mutex 'rmu' that is already held}}
|
|
rmu.Unlock(); // expected-note{{mutex released here}}
|
|
rmu.ReaderUnlock(); // expected-warning{{releasing mutex 'rmu' that was not held}}
|
|
}
|
|
|
|
void testReentrantIntersection() {
|
|
rmu.Lock();
|
|
if (guardby_var) {
|
|
rmu.Lock();
|
|
rmu.Lock();
|
|
rmu.Unlock();
|
|
} else {
|
|
rmu.Lock();
|
|
guardby_var = 1;
|
|
}
|
|
guardby_var = 1;
|
|
rmu.Unlock();
|
|
guardby_var = 1;
|
|
rmu.Unlock();
|
|
guardby_var = 1; // expected-warning{{writing variable 'guardby_var' requires holding mutex 'rmu' exclusively}}
|
|
}
|
|
|
|
void testReentrantIntersectionBad() {
|
|
rmu.Lock(); // expected-note{{mutex acquired here}}
|
|
if (guardby_var) {
|
|
rmu.Lock();
|
|
rmu.Lock();
|
|
} else {
|
|
rmu.Lock();
|
|
guardby_var = 1;
|
|
}
|
|
guardby_var = 1; // expected-warning{{mutex 'rmu' is not held on every path through here with equal reentrancy depth}}
|
|
rmu.Unlock();
|
|
guardby_var = 1;
|
|
rmu.Unlock();
|
|
guardby_var = 1;
|
|
rmu.Unlock();
|
|
}
|
|
|
|
void testReentrantLoopBad() {
|
|
rmu.Lock(); // expected-note{{mutex acquired here}}
|
|
while (guardby_var) { // expected-warning{{expecting mutex 'rmu' to be held at start of each loop with equal reentrancy depth}}
|
|
rmu.Lock();
|
|
}
|
|
rmu.Unlock();
|
|
}
|
|
|
|
void testLocksRequiredReentrant() EXCLUSIVE_LOCKS_REQUIRED(rmu) {
|
|
guardby_var = 1;
|
|
rmu.Lock();
|
|
rmu.Lock();
|
|
guardby_var = 1;
|
|
rmu.Unlock();
|
|
rmu.Unlock();
|
|
guardby_var = 1;
|
|
}
|
|
|
|
void testAssertReentrant() {
|
|
rmu.AssertHeld();
|
|
guardby_var = 1;
|
|
rmu.Lock();
|
|
guardby_var = 1;
|
|
rmu.Unlock();
|
|
guardby_var = 1;
|
|
}
|
|
|
|
void testAssertReaderReentrant() {
|
|
rmu.AssertReaderHeld();
|
|
int x = guardby_var;
|
|
rmu.ReaderLock();
|
|
int y = guardby_var;
|
|
rmu.ReaderUnlock();
|
|
int z = guardby_var;
|
|
}
|
|
|
|
struct TestScopedReentrantLockable {
|
|
ReentrantMutex mu1;
|
|
ReentrantMutex mu2;
|
|
int a __attribute__((guarded_by(mu1)));
|
|
int b __attribute__((guarded_by(mu2)));
|
|
|
|
bool getBool();
|
|
|
|
void foo1() {
|
|
ReentrantMutexLock mulock1(&mu1);
|
|
a = 5;
|
|
ReentrantMutexLock mulock2(&mu1);
|
|
a = 5;
|
|
}
|
|
|
|
void foo2() {
|
|
ReentrantMutexLock mulock1(&mu1);
|
|
a = 5;
|
|
mu1.Lock();
|
|
a = 5;
|
|
mu1.Unlock();
|
|
a = 5;
|
|
}
|
|
|
|
#ifdef __cpp_guaranteed_copy_elision
|
|
void const_lock() {
|
|
const ReentrantMutexLock mulock1 = ReentrantMutexLock(&mu1);
|
|
a = 5;
|
|
const ReentrantMutexLock mulock2 = ReentrantMutexLock(&mu1);
|
|
a = 3;
|
|
}
|
|
#endif
|
|
|
|
void temporary() {
|
|
ReentrantMutexLock{&mu1}, a = 1, ReentrantMutexLock{&mu1}, a = 5;
|
|
}
|
|
|
|
void lifetime_extension() {
|
|
const ReentrantMutexLock &mulock1 = ReentrantMutexLock(&mu1);
|
|
a = 5;
|
|
const ReentrantMutexLock &mulock2 = ReentrantMutexLock(&mu1);
|
|
a = 5;
|
|
}
|
|
|
|
void foo3() {
|
|
ReentrantReaderMutexLock mulock1(&mu1);
|
|
if (getBool()) {
|
|
ReentrantMutexLock mulock2a(&mu2);
|
|
b = a + 1;
|
|
}
|
|
else {
|
|
ReentrantMutexLock mulock2b(&mu2);
|
|
b = a + 2;
|
|
}
|
|
}
|
|
|
|
void foo4() {
|
|
ReentrantMutexLock mulock_a(&mu1);
|
|
ReentrantMutexLock mulock_b(&mu1);
|
|
}
|
|
|
|
void temporary_double_lock() {
|
|
ReentrantMutexLock mulock_a(&mu1);
|
|
ReentrantMutexLock{&mu1};
|
|
}
|
|
|
|
void foo5() {
|
|
ReentrantMutexLock mulock1(&mu1), mulock2(&mu2);
|
|
{
|
|
ReentrantMutexLock mulock3(&mu1), mulock4(&mu2);
|
|
a = b+1;
|
|
}
|
|
b = a+1;
|
|
}
|
|
};
|
|
|
|
void scopedDoubleUnlock() {
|
|
RelockableReentrantMutexLock scope(&rmu);
|
|
scope.Unlock(); // expected-note{{mutex released here}}
|
|
scope.Unlock(); // expected-warning {{releasing mutex 'rmu' that was not held}}
|
|
}
|
|
|
|
void scopedDoubleLock1() {
|
|
RelockableReentrantMutexLock scope(&rmu);
|
|
scope.Lock();
|
|
scope.Unlock();
|
|
}
|
|
|
|
void scopedDoubleLock2() {
|
|
RelockableReentrantMutexLock scope(&rmu);
|
|
scope.Unlock();
|
|
scope.Lock();
|
|
scope.Lock();
|
|
scope.Unlock();
|
|
}
|
|
|
|
typedef int __attribute__((capability("bitlock"))) REENTRANT_CAPABILITY *bitlock_t;
|
|
void bit_lock(bitlock_t l) EXCLUSIVE_LOCK_FUNCTION(l);
|
|
void bit_unlock(bitlock_t l) UNLOCK_FUNCTION(l);
|
|
bitlock_t bl;
|
|
void testReentrantTypedef() {
|
|
bit_lock(bl);
|
|
bit_lock(bl);
|
|
bit_unlock(bl);
|
|
bit_unlock(bl);
|
|
}
|
|
|
|
class TestNegativeWithReentrantMutex {
|
|
ReentrantMutex rmu;
|
|
int a GUARDED_BY(rmu);
|
|
|
|
public:
|
|
void baz() EXCLUSIVE_LOCKS_REQUIRED(!rmu) {
|
|
rmu.Lock();
|
|
rmu.Lock();
|
|
a = 0;
|
|
rmu.Unlock();
|
|
rmu.Unlock();
|
|
}
|
|
};
|
|
|
|
} // namespace Reentrancy
|
|
|
|
// Tests for tracking aliases of capabilities.
|
|
namespace CapabilityAliases {
|
|
struct Foo {
|
|
Mutex mu;
|
|
int data GUARDED_BY(mu);
|
|
};
|
|
|
|
Foo *returnsFoo();
|
|
Foo *returnsFoo(Foo *foo);
|
|
void locksRequired(Foo *foo) EXCLUSIVE_LOCKS_REQUIRED(foo->mu);
|
|
void escapeAlias(int a, Foo *&ptr);
|
|
void escapeAlias(int b, Foo **ptr);
|
|
void passByConstRef(Foo* const& ptr);
|
|
|
|
void testBasicPointerAlias(Foo *f) {
|
|
Foo *ptr = f;
|
|
ptr->mu.Lock(); // lock through alias
|
|
f->data = 42; // access through original
|
|
ptr->mu.Unlock(); // unlock through alias
|
|
}
|
|
|
|
void testCastPointerAlias(Foo *f) {
|
|
// Cast to void* from unsigned long to test non-pointer cast indirection.
|
|
void *priv = (void *)(__UINTPTR_TYPE__)(&f->mu);
|
|
f->mu.Lock();
|
|
f->data = 42;
|
|
auto *mu = (Mutex *)priv;
|
|
mu->Unlock();
|
|
}
|
|
|
|
void testBasicPointerAliasNoInit(Foo *f) {
|
|
Foo *ptr;
|
|
|
|
ptr = nullptr;
|
|
ptr = f;
|
|
ptr->mu.Lock();
|
|
f->data = 42;
|
|
ptr->mu.Unlock();
|
|
ptr = nullptr;
|
|
}
|
|
|
|
void testBasicPointerAliasLoop() {
|
|
for (;;) {
|
|
Foo *f = returnsFoo();
|
|
Foo *ptr = f;
|
|
if (!ptr)
|
|
break;
|
|
ptr->mu.Lock();
|
|
f->data = 42;
|
|
ptr->mu.Unlock();
|
|
}
|
|
}
|
|
|
|
void testPointerAliasNoEscape1(Foo *f) {
|
|
Foo *ptr = f;
|
|
testBasicPointerAlias(ptr); // pass alias by value
|
|
|
|
ptr->mu.Lock();
|
|
f->data = 42;
|
|
ptr->mu.Unlock();
|
|
}
|
|
|
|
void testPointerAliasNoEscape2(Foo *f) {
|
|
Foo *ptr = f;
|
|
passByConstRef(ptr); // pass alias by const ref
|
|
|
|
ptr->mu.Lock();
|
|
f->data = 42;
|
|
ptr->mu.Unlock();
|
|
}
|
|
|
|
void testPointerAliasNoEscape3() {
|
|
Foo *ptr = returnsFoo();
|
|
ptr->mu.Lock();
|
|
locksRequired(ptr);
|
|
ptr->mu.Unlock();
|
|
}
|
|
|
|
void testPointerAliasEscape1(Foo *f) {
|
|
Foo *ptr = f;
|
|
escapeAlias(0, ptr);
|
|
|
|
ptr->mu.Lock();
|
|
f->data = 42; // expected-warning{{writing variable 'data' requires holding mutex 'f->mu' exclusively}} \
|
|
// expected-note{{found near match 'ptr->mu'}}
|
|
ptr->mu.Unlock();
|
|
}
|
|
|
|
void testPointerAliasEscape2(Foo *f) {
|
|
Foo *ptr = f;
|
|
escapeAlias(0, &ptr);
|
|
|
|
ptr->mu.Lock();
|
|
f->data = 42; // expected-warning{{writing variable 'data' requires holding mutex 'f->mu' exclusively}} \
|
|
// expected-note{{found near match 'ptr->mu'}}
|
|
ptr->mu.Unlock();
|
|
}
|
|
|
|
void testPointerAliasEscape3(Foo *f) {
|
|
Foo *ptr;
|
|
|
|
ptr = f;
|
|
escapeAlias(0, &ptr);
|
|
|
|
ptr->mu.Lock();
|
|
f->data = 42; // expected-warning{{writing variable 'data' requires holding mutex 'f->mu' exclusively}} \
|
|
// expected-note{{found near match 'ptr->mu'}}
|
|
ptr->mu.Unlock();
|
|
}
|
|
|
|
void testPointerAliasEscapeAndReset(Foo *f) {
|
|
Foo *ptr;
|
|
|
|
ptr = f;
|
|
escapeAlias(0, &ptr);
|
|
ptr = f;
|
|
|
|
ptr->mu.Lock();
|
|
f->data = 42;
|
|
ptr->mu.Unlock();
|
|
}
|
|
|
|
struct LOCKABLE Entry;
|
|
void getLockedEntry(Entry **entry) EXCLUSIVE_LOCK_FUNCTION(*entry);
|
|
void useLockedEntry(Entry *entry) EXCLUSIVE_LOCKS_REQUIRED(entry);
|
|
void unlockEntry(Entry *entry) UNLOCK_FUNCTION(entry);
|
|
void assertLockedEntry(Entry **entry) ASSERT_EXCLUSIVE_LOCK(*entry);
|
|
|
|
void testOutParamAcquireCap(Entry *in) {
|
|
Entry *entry = in;
|
|
|
|
// 'getLockedEntry' guarantees that '*entry' is locked after return:
|
|
getLockedEntry(&entry);
|
|
useLockedEntry(entry);
|
|
if (1) {
|
|
useLockedEntry(entry);
|
|
}
|
|
unlockEntry(entry);
|
|
}
|
|
|
|
void testOutParamAcquireCap_invalidation(Entry *in) {
|
|
Entry *entry = in;
|
|
|
|
// 'getLockedEntry' invalidates 'entry' at the pre-state and ensures
|
|
// 'entry' is locked at the post-state. So 'in' is not locked.
|
|
getLockedEntry(&entry); // expected-note{{mutex acquired here}}
|
|
useLockedEntry(in); // expected-warning{{calling function 'useLockedEntry' requires holding mutex 'in' exclusively}}
|
|
unlockEntry(in); // expected-warning{{releasing mutex 'in' that was not held}}
|
|
} // expected-warning{{mutex 'entry' is still held at the end of function}}
|
|
|
|
void getLockedEntryRef(Entry *&entry) EXCLUSIVE_LOCK_FUNCTION(entry);
|
|
|
|
void testAcquireCapability_outParamRef(Entry *in) {
|
|
Entry *entry = in;
|
|
getLockedEntryRef(entry);
|
|
// 'entry' has been invalidated. So it holds the lock after the call,
|
|
// but 'in' does not:
|
|
useLockedEntry(entry);
|
|
unlockEntry(entry);
|
|
useLockedEntry(in); // expected-warning{{calling function 'useLockedEntry' requires holding mutex 'in' exclusively}}
|
|
unlockEntry(in); // expected-warning{{releasing mutex 'in' that was not held}}
|
|
}
|
|
|
|
void testAssertCapability_outParam(Entry *in) {
|
|
Entry *entry = in;
|
|
assertLockedEntry(&entry);
|
|
// 'entry' has been invalidated. So it is assumed to hold the lock
|
|
// after the call, but 'in' does not:
|
|
useLockedEntry(entry);
|
|
unlockEntry(entry);
|
|
useLockedEntry(in); // expected-warning{{calling function 'useLockedEntry' requires holding mutex 'in' exclusively}}
|
|
unlockEntry(in); // expected-warning{{releasing mutex 'in' that was not held}}
|
|
}
|
|
|
|
void getMultiLockedEntries(Entry **e1, Entry **e2, Entry *e3) EXCLUSIVE_LOCK_FUNCTION(*e1, *e2, e3);
|
|
|
|
void testAcquireCapability_outParmMultiAttrArg(Entry *in1, Entry *in2, Entry *in3) {
|
|
Entry *entry1 = in1;
|
|
Entry *entry2 = in2;
|
|
Entry *entry3 = in3;
|
|
getMultiLockedEntries(&entry1, &entry2, entry3);
|
|
useLockedEntry(entry1);
|
|
useLockedEntry(entry2);
|
|
unlockEntry(entry1);
|
|
unlockEntry(entry2);
|
|
|
|
useLockedEntry(in1); // expected-warning{{calling function 'useLockedEntry' requires holding mutex 'in1' exclusively}}
|
|
useLockedEntry(in2); // expected-warning{{calling function 'useLockedEntry' requires holding mutex 'in2' exclusively}}
|
|
useLockedEntry(in3);
|
|
unlockEntry(in1); // expected-warning{{releasing mutex 'in1' that was not held}}
|
|
unlockEntry(in2); // expected-warning{{releasing mutex 'in2' that was not held}}
|
|
unlockEntry(in3);
|
|
}
|
|
|
|
void allActions(Entry **e1, Entry **e2, Entry **e3, Entry **e4, Entry **e5)
|
|
EXCLUSIVE_LOCK_FUNCTION(*e1) EXCLUSIVE_LOCKS_REQUIRED(*e2)
|
|
UNLOCK_FUNCTION(*e3) ASSERT_EXCLUSIVE_LOCK(*e4) LOCKS_EXCLUDED(*e5);
|
|
|
|
// Test pre- and post-state handling involving various kinds of
|
|
// attributes at a single call-site.
|
|
void testAllActions_outParms() {
|
|
Entry *e1 = nullptr, *e2 = nullptr, *e3 = nullptr,
|
|
*e4 = nullptr;
|
|
|
|
getLockedEntry(&e2);
|
|
// locks e1, requires then unlocks e2, assumes e3 is locked, requires !e4:
|
|
allActions(&e1, &e2, &e2, &e3, &e4);
|
|
useLockedEntry(e1);
|
|
useLockedEntry(e3);
|
|
unlockEntry(e1);
|
|
unlockEntry(e3);
|
|
}
|
|
|
|
void testAllActions_outParms_negative() {
|
|
Entry *e1 = nullptr, *e2 = nullptr, *e3 = nullptr,
|
|
*e4 = nullptr;
|
|
|
|
getLockedEntry(&e2);
|
|
getLockedEntry(&e4); // expected-note{{mutex acquired here}}
|
|
allActions(&e1, &e2, &e2, &e3, &e4); // expected-warning{{cannot call function 'allActions' while mutex 'e4' is held}} \
|
|
// expected-note{{mutex released here}}
|
|
useLockedEntry(e1);
|
|
useLockedEntry(e3);
|
|
unlockEntry(e1);
|
|
unlockEntry(e2); // expected-warning{{releasing mutex 'e2' that was not held}}
|
|
} // expected-warning{{mutex 'e4' is still held at the end of function}}
|
|
|
|
|
|
// A function that may do anything to the objects referred to by the inputs.
|
|
void escapeAliasMultiple(void *, void *, void *);
|
|
void testPointerAliasEscapeMultiple(Foo *F) {
|
|
Foo *L;
|
|
F->mu.Lock(); // expected-note{{mutex acquired here}}
|
|
Foo *Fp = F;
|
|
escapeAliasMultiple(&L, &L, &Fp);
|
|
Fp->mu.Unlock(); // expected-warning{{releasing mutex 'Fp->mu' that was not held}}
|
|
} // expected-warning{{mutex 'F->mu' is still held at the end of function}}
|
|
|
|
void unlockFooWithEscapablePointer(Foo **Fp) EXCLUSIVE_UNLOCK_FUNCTION((*Fp)->mu);
|
|
void testEscapeInvalidationHappensRightAfterTheCall(Foo* F) {
|
|
Foo* L;
|
|
L = F;
|
|
L->mu.Lock();
|
|
// Release the lock held by 'L' before clearing its definition.
|
|
unlockFooWithEscapablePointer(&L);
|
|
}
|
|
|
|
void testEscapeInvalidationHappensRightAfterTheCtorCall(Foo* F) {
|
|
Foo* L = F;
|
|
MutexLock ScopeLock(&L->mu);
|
|
|
|
struct {
|
|
int DataMember GUARDED_BY(F->mu);
|
|
} Data;
|
|
|
|
Data.DataMember = 0;
|
|
}
|
|
|
|
void testCleanUpFunctionWithLocalVarUpdated(Foo* F) {
|
|
F->mu.Lock();
|
|
Foo * __attribute__((unused, cleanup(unlockFooWithEscapablePointer))) L = F;
|
|
}
|
|
|
|
void testPointerAliasTryLock1() {
|
|
Foo *ptr = returnsFoo();
|
|
if (ptr->mu.TryLock()) {
|
|
locksRequired(ptr);
|
|
ptr->mu.Unlock();
|
|
}
|
|
}
|
|
|
|
void testPointerAliasTryLock2() {
|
|
Foo *ptr;
|
|
ptr = returnsFoo();
|
|
Foo *ptr2 = ptr;
|
|
if (ptr->mu.TryLock()) {
|
|
locksRequired(ptr);
|
|
ptr2->mu.Unlock();
|
|
}
|
|
}
|
|
|
|
// FIXME: This test demonstrates a dubious pattern that the analysis correctly
|
|
// flags as unsafe, though the user might perceive it as a false positive. The
|
|
// pattern combines a TryLock() failure path with a conditional reassignment of
|
|
// the pointer being locked:
|
|
//
|
|
// 1. The conditional reassignment `ptr = returnsFoo(ptr);` forces `ptr` to
|
|
// become a phi node in the CFG at the subsequent merge point. The alias
|
|
// analysis correctly tracks that `ptr` could refer to one of two distinct
|
|
// objects.
|
|
//
|
|
// 2. The lock acquired on the `!TryLock()` path should be (conceptually)
|
|
// `phi(P1, P2)->mu`, while the lock on the successful path is on the
|
|
// original `P1->mu`. When the paths merge the analysis currently discards
|
|
// the alias as it cannot prove a single alias on that path.
|
|
//
|
|
// While this pattern is stylistically fragile and difficult to reason about, a
|
|
// robust solution would require a more advanced symbolic representation of
|
|
// capabilities within the analyzer. For now, we warn on such ambiguity.
|
|
void testPointerAliasTryLockDubious(int x) {
|
|
Foo *ptr = returnsFoo();
|
|
if (!ptr->mu.TryLock()) { // expected-note{{mutex acquired here}}
|
|
if (x)
|
|
ptr = returnsFoo(ptr); // <-- this breaks the pattern
|
|
ptr->mu.Lock(); // expected-note{{mutex acquired here}}
|
|
}
|
|
ptr->data = 42; // expected-warning{{writing variable 'data' requires holding mutex 'ptr->mu' exclusively}} \
|
|
// expected-warning{{mutex 'ptr->mu' is not held on every path through here}} \
|
|
// expected-warning{{mutex 'returnsFoo().mu' is not held on every path through here}}
|
|
ptr->mu.Unlock(); // expected-warning{{releasing mutex 'ptr->mu' that was not held}}
|
|
}
|
|
|
|
void testReassignment() {
|
|
Foo f1, f2;
|
|
Foo *ptr = &f1;
|
|
ptr->mu.Lock();
|
|
f1.data = 42;
|
|
ptr->mu.Unlock();
|
|
|
|
ptr = &f2;
|
|
ptr->mu.Lock();
|
|
f2.data = 42;
|
|
f1.data = 42; // expected-warning{{writing variable 'data' requires holding mutex 'f1.mu'}} \
|
|
// expected-note{{found near match 'f2.mu'}}
|
|
ptr->mu.Unlock();
|
|
}
|
|
|
|
// Alias reassignment through pointer-to-pointer (nor ptr-to-ptr-to-ptr...) does
|
|
// not invalidate aliases for now.
|
|
//
|
|
// While this may result in either false positives or negatives, there's rarely
|
|
// a good reason not to just do direct assignment within the same scope. For the
|
|
// time being, we retain this as a deliberate "escape hatch": specifically, this
|
|
// may be used to help the alias analysis to "see through" complex helper macros
|
|
// that e.g. read a value (such as a pointer) via inline assembly or other
|
|
// opaque helpers.
|
|
void testReassignmentPointerToAlias(Foo *f) {
|
|
Mutex *mu = &f->mu;
|
|
// Escape hatch.
|
|
Mutex **mu_p = μ
|
|
Mutex *actual_mu = [&] { /* ... */ return mu; }();
|
|
*mu_p = actual_mu;
|
|
|
|
mu->Lock();
|
|
f->data = 42;
|
|
mu->Unlock();
|
|
}
|
|
|
|
// Nested field access through pointer
|
|
struct Container {
|
|
Foo foo;
|
|
};
|
|
|
|
void testNestedAccess(Container *c) {
|
|
Foo *ptr = &c->foo; // pointer to nested field
|
|
ptr->mu.Lock();
|
|
c->foo.data = 42;
|
|
ptr->mu.Unlock();
|
|
}
|
|
|
|
void testNestedAcquire(Container *c) EXCLUSIVE_LOCK_FUNCTION(&c->foo.mu) {
|
|
Foo *buf = &c->foo;
|
|
buf->mu.Lock();
|
|
}
|
|
|
|
void testArrayOfContainers() {
|
|
Container array[10];
|
|
|
|
Foo *ptr1 = &array[0].foo;
|
|
Foo *ptr2 = &array[1].foo;
|
|
ptr1->mu.Lock();
|
|
ptr2->mu.Lock();
|
|
array[0].foo.data = 0;
|
|
array[1].foo.data = 1;
|
|
ptr2->mu.Unlock();
|
|
ptr1->mu.Unlock();
|
|
}
|
|
|
|
struct ContainerOfPtr {
|
|
Foo *foo_ptr;
|
|
ContainerOfPtr *next;
|
|
};
|
|
|
|
void testIndirectAccess(ContainerOfPtr *fc) {
|
|
Foo *ptr = fc->foo_ptr; // get pointer
|
|
ptr->mu.Lock();
|
|
fc->foo_ptr->data = 42; // access via original
|
|
ptr->mu.Unlock();
|
|
}
|
|
|
|
void testAliasChainUnrelatedReassignment1(ContainerOfPtr *list) {
|
|
Foo *eb = list->foo_ptr;
|
|
eb->mu.Lock();
|
|
list = list->next;
|
|
eb->data = 42;
|
|
eb->mu.Unlock();
|
|
}
|
|
|
|
void testAliasChainUnrelatedReassignment2(ContainerOfPtr *list) {
|
|
ContainerOfPtr *busyp = list;
|
|
Foo *eb = busyp->foo_ptr;
|
|
eb->mu.Lock();
|
|
busyp = busyp->next;
|
|
eb->data = 42;
|
|
eb->mu.Unlock();
|
|
}
|
|
|
|
struct GuardedByIndirection {
|
|
int data GUARDED_BY(foo_ptr->mu);
|
|
Foo *foo_ptr;
|
|
};
|
|
|
|
void testGuardedByIndirection(GuardedByIndirection *x) {
|
|
Foo *f = x->foo_ptr;
|
|
f->mu.Lock();
|
|
x->data = 42;
|
|
f->mu.Unlock();
|
|
}
|
|
|
|
void testControlFlowDoWhile(Foo *f, int x) {
|
|
Foo *ptr = f;
|
|
|
|
f->mu.Lock();
|
|
if (x) {
|
|
// complex merge
|
|
do { } while (x--);
|
|
}
|
|
ptr->data = 42;
|
|
ptr->mu.Unlock();
|
|
}
|
|
|
|
// FIXME: No alias tracking through complex control flow.
|
|
void testComplexControlFlow(Foo *f1, Foo *f2, bool cond) {
|
|
Foo *ptr;
|
|
if (cond) {
|
|
ptr = f1;
|
|
} else {
|
|
ptr = f2;
|
|
}
|
|
ptr->mu.Lock();
|
|
if (cond) {
|
|
f1->data = 42; // expected-warning{{writing variable 'data' requires holding mutex 'f1->mu' exclusively}} \
|
|
// expected-note{{found near match 'ptr->mu'}}
|
|
} else {
|
|
f2->data = 42; // expected-warning{{writing variable 'data' requires holding mutex 'f2->mu' exclusively}} \
|
|
// expected-note{{found near match 'ptr->mu'}}
|
|
}
|
|
ptr->mu.Unlock();
|
|
}
|
|
|
|
void testLockFunction(Foo *f) EXCLUSIVE_LOCK_FUNCTION(&f->mu) {
|
|
Mutex *mu = &f->mu;
|
|
mu->Lock();
|
|
}
|
|
|
|
void testUnlockFunction(Foo *f) UNLOCK_FUNCTION(&f->mu) {
|
|
Mutex *mu = &f->mu;
|
|
mu->Unlock();
|
|
}
|
|
|
|
// This is an idiom to deal with "pointer to returned object has a lock held,
|
|
// but you must unlock it later" where the statement expression would be hidden
|
|
// behind a macro.
|
|
void lockWithinStatementExpr() {
|
|
Foo *f = ({ auto x = returnsFoo(); x->mu.Lock(); x; });
|
|
f->data = 42;
|
|
f->mu.Unlock();
|
|
}
|
|
|
|
// Semantically UB, but let's not crash the compiler with this (should be
|
|
// handled by -Wuninitialized).
|
|
void testSelfInit() {
|
|
Mutex *mu = mu; // don't do this at home
|
|
mu->Lock();
|
|
mu->Unlock();
|
|
}
|
|
|
|
void testSelfAssign() {
|
|
Foo *f = returnsFoo();
|
|
f = f;
|
|
f->mu.Lock();
|
|
f->data = 42;
|
|
f->mu.Unlock();
|
|
}
|
|
|
|
void testRecursiveAssign() {
|
|
Foo *f = returnsFoo();
|
|
f = returnsFoo(f);
|
|
f->mu.Lock();
|
|
f->data = 42;
|
|
f->mu.Unlock();
|
|
}
|
|
|
|
void testNew(Mutex *&out, int &x) {
|
|
Mutex *mu = new Mutex;
|
|
__atomic_store_n(&out, mu, __ATOMIC_RELEASE);
|
|
mu->Lock();
|
|
x = 42; // ... perhaps guarded by mu
|
|
mu->Unlock();
|
|
}
|
|
|
|
void testNestedLoopInvariant(Container *c, int n) {
|
|
Foo *ptr = &c->foo;
|
|
ptr->mu.Lock();
|
|
|
|
for (int i = 0; i < n; ++i) {
|
|
for (int j = 0; j < n; ++j) {
|
|
}
|
|
}
|
|
|
|
c->foo.data = 42; // ok: alias still valid
|
|
ptr->mu.Unlock();
|
|
}
|
|
|
|
void testLoopWithBreak(Foo *f, bool cond) {
|
|
Foo *ptr = f;
|
|
ptr->mu.Lock();
|
|
for (int i = 0; i < 10; ++i) {
|
|
if (cond) {
|
|
break; // merge point is after the loop
|
|
}
|
|
}
|
|
f->data = 42; // ok
|
|
ptr->mu.Unlock();
|
|
}
|
|
|
|
void testLoopWithContinue(Foo *f, bool cond) {
|
|
Foo *ptr = f;
|
|
ptr->mu.Lock();
|
|
for (int i = 0; i < 10; ++i) {
|
|
if (cond) {
|
|
continue; // tests merge at the top of loop.
|
|
}
|
|
}
|
|
f->data = 42; // ok
|
|
ptr->mu.Unlock();
|
|
}
|
|
|
|
void testLoopConditionalReassignment(Foo *f1, Foo *f2, bool cond) {
|
|
Foo *ptr = f1;
|
|
ptr->mu.Lock(); // expected-note{{mutex acquired here}}
|
|
|
|
for (int i = 0; i < 10; ++i) {
|
|
if (cond) {
|
|
ptr = f2; // alias is reassigned on some path inside the loop.
|
|
}
|
|
}
|
|
f1->data = 42;
|
|
ptr->mu.Unlock(); // expected-warning{{releasing mutex 'ptr->mu' that was not held}}
|
|
} // expected-warning{{mutex 'f1->mu' is still held at the end of function}}
|
|
} // namespace CapabilityAliases
|
|
|
|
namespace WideStringLiteral {
|
|
|
|
class Foo {
|
|
public:
|
|
Mutex mu;
|
|
Mutex* getMu(const wchar_t* s) { return μ }
|
|
Mutex* getMu2(const char16_t* s) { return μ }
|
|
Mutex* getMu3(const char32_t* s) { return μ }
|
|
|
|
int a GUARDED_BY(getMu(L"abc"));
|
|
int b GUARDED_BY(getMu2(u"abc"));
|
|
int c GUARDED_BY(getMu3(U"abc"));
|
|
};
|
|
|
|
Foo g_foo;
|
|
|
|
void test() {
|
|
g_foo.getMu(L"abc")->Lock();
|
|
g_foo.a = 0;
|
|
g_foo.getMu(L"abc")->Unlock();
|
|
}
|
|
|
|
void test2() {
|
|
g_foo.getMu2(u"abc")->Lock();
|
|
g_foo.b = 0;
|
|
g_foo.getMu2(u"abc")->Unlock();
|
|
}
|
|
|
|
void test3() {
|
|
g_foo.getMu3(U"abc")->Lock();
|
|
g_foo.c = 0;
|
|
g_foo.getMu3(U"abc")->Unlock();
|
|
}
|
|
|
|
} // namespace WideStringLiteral
|
|
|
|
namespace FunctionPointers {
|
|
|
|
Mutex mu;
|
|
int x GUARDED_BY(mu);
|
|
|
|
void (*lock_fn)(void) EXCLUSIVE_LOCK_FUNCTION(mu);
|
|
void (*shared_lock_fn)(void) SHARED_LOCK_FUNCTION(mu);
|
|
void (*unlock_fn)(void) UNLOCK_FUNCTION(mu);
|
|
void (*shared_unlock_fn)(void) SHARED_UNLOCK_FUNCTION(mu);
|
|
void (*requires_fn)(void) EXCLUSIVE_LOCKS_REQUIRED(mu);
|
|
void (*shared_requires_fn)(void) SHARED_LOCKS_REQUIRED(mu);
|
|
void (*excludes_fn)(void) LOCKS_EXCLUDED(mu);
|
|
bool (*try_lock_fn)(void) EXCLUSIVE_TRYLOCK_FUNCTION(true, mu);
|
|
bool (*shared_try_lock_fn)(void) SHARED_TRYLOCK_FUNCTION(true, mu);
|
|
void (*assert_fn)(void) ASSERT_EXCLUSIVE_LOCK(mu);
|
|
void (*shared_assert_fn)(void) ASSERT_SHARED_LOCK(mu);
|
|
|
|
void testAcquireRelease() {
|
|
lock_fn();
|
|
x = 1;
|
|
unlock_fn();
|
|
}
|
|
|
|
void testSharedAcquireRelease() {
|
|
shared_lock_fn();
|
|
(void)x;
|
|
shared_unlock_fn();
|
|
}
|
|
|
|
void testSharedAcquireWriteFail() {
|
|
shared_lock_fn();
|
|
x = 1; // expected-warning {{writing variable 'x' requires holding mutex 'mu' exclusively}}
|
|
shared_unlock_fn();
|
|
}
|
|
|
|
void testAcquireNoRelease() {
|
|
lock_fn(); // expected-note {{mutex acquired here}}
|
|
x = 1;
|
|
} // expected-warning {{mutex 'mu' is still held at the end of function}}
|
|
|
|
void testNoAcquire() {
|
|
x = 1; // expected-warning {{writing variable 'x' requires holding mutex 'mu' exclusively}}
|
|
}
|
|
|
|
void testRequires() {
|
|
mu.Lock();
|
|
requires_fn();
|
|
mu.Unlock();
|
|
}
|
|
|
|
void testRequiresFail() {
|
|
requires_fn(); // expected-warning {{calling function 'requires_fn' requires holding mutex 'mu' exclusively}}
|
|
}
|
|
|
|
void testSharedRequires() {
|
|
mu.ReaderLock();
|
|
shared_requires_fn();
|
|
mu.ReaderUnlock();
|
|
}
|
|
|
|
void testSharedRequiresFail() {
|
|
shared_requires_fn(); // expected-warning {{calling function 'shared_requires_fn' requires holding mutex 'mu'}}
|
|
}
|
|
|
|
void testExcludes() {
|
|
excludes_fn();
|
|
}
|
|
|
|
void testExcludesFail() {
|
|
mu.Lock();
|
|
excludes_fn(); // expected-warning {{cannot call function 'excludes_fn' while mutex 'mu' is held}}
|
|
mu.Unlock();
|
|
}
|
|
|
|
void testTryLock() {
|
|
if (try_lock_fn()) {
|
|
x = 1;
|
|
mu.Unlock();
|
|
}
|
|
}
|
|
|
|
void testSharedTryLock() {
|
|
if (shared_try_lock_fn()) {
|
|
(void)x;
|
|
mu.Unlock();
|
|
}
|
|
}
|
|
|
|
void testAssert() {
|
|
assert_fn();
|
|
x = 1;
|
|
}
|
|
|
|
void testSharedAssert() {
|
|
shared_assert_fn();
|
|
(void)x;
|
|
}
|
|
|
|
struct Ops {
|
|
void (*lock)(void) EXCLUSIVE_LOCK_FUNCTION(mu);
|
|
void (*unlock)(void) UNLOCK_FUNCTION(mu);
|
|
void (*do_thing)(void) EXCLUSIVE_LOCKS_REQUIRED(mu);
|
|
void (*read_thing)(void) SHARED_LOCKS_REQUIRED(mu);
|
|
};
|
|
|
|
void testStructOps(Ops *ops) {
|
|
ops->lock();
|
|
x = 1;
|
|
ops->do_thing();
|
|
ops->read_thing();
|
|
ops->unlock();
|
|
}
|
|
|
|
void testStructFail(Ops *ops) {
|
|
ops->do_thing(); // expected-warning {{calling function 'do_thing' requires holding mutex 'mu' exclusively}}
|
|
ops->read_thing(); // expected-warning {{calling function 'read_thing' requires holding mutex 'mu'}}
|
|
}
|
|
|
|
// Incompatible reassignment not an error.
|
|
void otherLock();
|
|
void testReassignIncompatible() {
|
|
lock_fn = otherLock;
|
|
lock_fn();
|
|
x = 1;
|
|
mu.Unlock();
|
|
}
|
|
|
|
// A function pointer attribute may reference the pointer's own parameter.
|
|
void (*lock_param_fn)(Mutex *m) EXCLUSIVE_LOCK_FUNCTION(m);
|
|
void (*req_param_fn)(Mutex *m) EXCLUSIVE_LOCKS_REQUIRED(m);
|
|
|
|
void test_attr_refers_to_param(Mutex *m) {
|
|
req_param_fn(m); // expected-warning {{calling function 'req_param_fn' requires holding mutex 'm' exclusively}}
|
|
lock_param_fn(m);
|
|
req_param_fn(m);
|
|
m->Unlock();
|
|
}
|
|
|
|
// Function references name a function to call just like function pointers do.
|
|
void lock_impl(void) EXCLUSIVE_LOCK_FUNCTION(mu);
|
|
void unlock_impl(void) UNLOCK_FUNCTION(mu);
|
|
void requires_impl(void) EXCLUSIVE_LOCKS_REQUIRED(mu);
|
|
|
|
void (&lock_ref)(void) EXCLUSIVE_LOCK_FUNCTION(mu) = lock_impl;
|
|
void (&unlock_ref)(void) UNLOCK_FUNCTION(mu) = unlock_impl;
|
|
void (&requires_ref)(void) EXCLUSIVE_LOCKS_REQUIRED(mu) = requires_impl;
|
|
|
|
void testReferenceAcquireRelease() {
|
|
lock_ref();
|
|
x = 1;
|
|
requires_ref();
|
|
unlock_ref();
|
|
}
|
|
|
|
void testReferenceRequiresFail() {
|
|
requires_ref(); // expected-warning {{calling function 'requires_ref' requires holding mutex 'mu' exclusively}}
|
|
}
|
|
|
|
// Capability attributes on a parameter that names a function to call -- a
|
|
// function pointer, a function reference, or a reference to either -- describe
|
|
// the function reached through the parameter, not a capability that the bound
|
|
// argument stands for. They are checked where the parameter is called, and are
|
|
// neither requirements on nor effects for callers of the enclosing function.
|
|
|
|
void callback(int) EXCLUSIVE_LOCKS_REQUIRED(mu);
|
|
|
|
void takes_ptr(void (*cb)(int) EXCLUSIVE_LOCKS_REQUIRED(mu), int n);
|
|
void takes_ref(void (&cb)(int) EXCLUSIVE_LOCKS_REQUIRED(mu), int n);
|
|
void takes_ptr_ref(void (*&cb)(int) EXCLUSIVE_LOCKS_REQUIRED(mu), int n);
|
|
|
|
// Passing an annotated callee is not itself a use of the capability.
|
|
void testPassCallback(void (*&pcb)(int), int n) {
|
|
takes_ptr(callback, n);
|
|
takes_ptr(&callback, n);
|
|
takes_ref(callback, n);
|
|
takes_ptr_ref(pcb, n);
|
|
}
|
|
|
|
// The attributes are checked at the indirect call instead ...
|
|
void testCallPtr(void (*cb)(int) EXCLUSIVE_LOCKS_REQUIRED(mu), int n) {
|
|
cb(n); // expected-warning {{calling function 'cb' requires holding mutex 'mu' exclusively}}
|
|
}
|
|
|
|
void testCallRef(void (&cb)(int) EXCLUSIVE_LOCKS_REQUIRED(mu), int n) {
|
|
cb(n); // expected-warning {{calling function 'cb' requires holding mutex 'mu' exclusively}}
|
|
}
|
|
|
|
void testCallPtrRef(void (*&cb)(int) EXCLUSIVE_LOCKS_REQUIRED(mu), int n) {
|
|
cb(n); // expected-warning {{calling function 'cb' requires holding mutex 'mu' exclusively}}
|
|
}
|
|
|
|
// ... where the enclosing function's own requirements can satisfy them.
|
|
void testCallRefLocked(void (&cb)(int) EXCLUSIVE_LOCKS_REQUIRED(mu), int n)
|
|
EXCLUSIVE_LOCKS_REQUIRED(mu) {
|
|
cb(n);
|
|
}
|
|
|
|
// Acquire and release likewise describe the function called through the
|
|
// parameter, so calling the enclosing function neither acquires nor releases.
|
|
void takes_locker(void (&lock)(void) EXCLUSIVE_LOCK_FUNCTION(mu));
|
|
|
|
void testAcquireNotTransferred() {
|
|
takes_locker(lock_impl);
|
|
x = 1; // expected-warning {{writing variable 'x' requires holding mutex 'mu' exclusively}}
|
|
}
|
|
|
|
void testCallAcquires(void (&lock)(void) EXCLUSIVE_LOCK_FUNCTION(mu)) {
|
|
lock();
|
|
x = 1;
|
|
mu.Unlock();
|
|
}
|
|
|
|
// A dependent parameter type is classified after instantiation.
|
|
template <typename F>
|
|
void callDependent(F cb EXCLUSIVE_LOCKS_REQUIRED(mu), int n) {
|
|
cb(n); // expected-warning 2 {{calling function 'cb' requires holding mutex 'mu' exclusively}}
|
|
}
|
|
|
|
void testDependent(int n) {
|
|
callDependent<void (*)(int)>(callback, n); // expected-note {{in instantiation of function template specialization 'FunctionPointers::callDependent<void (*)(int)>' requested here}}
|
|
callDependent<void (&)(int)>(callback, n); // expected-note {{in instantiation of function template specialization 'FunctionPointers::callDependent<void (&)(int)>' requested here}}
|
|
}
|
|
|
|
} // namespace FunctionPointers
|