线程安全分析¶
简介¶
Clang 线程安全分析是 C++ 语言扩展,用于警告代码中潜在的竞争条件。分析完全是静态的(即编译时);没有运行时开销。分析仍在积极开发中,但已经足够成熟,可以在工业环境中部署。它由 Google 开发,与 CERT/SEI 合作,并被广泛应用于 Google 的内部代码库。
线程安全分析非常类似于多线程程序的类型系统。除了声明数据的类型(例如 int
、float
等),程序员还可以(可选地)声明在多线程环境中如何控制对该数据的访问。例如,如果 foo
由互斥量 mu
保护,那么分析将在每次代码在未先锁定 mu
的情况下读取或写入 foo
时发出警告。类似地,如果存在仅应由 GUI 线程调用的特定例程,那么分析将在其他线程调用这些例程时发出警告。
入门¶
#include "mutex.h"
class BankAccount {
private:
Mutex mu;
int balance GUARDED_BY(mu);
void depositImpl(int amount) {
balance += amount; // WARNING! Cannot write balance without locking mu.
}
void withdrawImpl(int amount) REQUIRES(mu) {
balance -= amount; // OK. Caller must have locked mu.
}
public:
void withdraw(int amount) {
mu.Lock();
withdrawImpl(amount); // OK. We've locked mu.
} // WARNING! Failed to unlock mu.
void transferFrom(BankAccount& b, int amount) {
mu.Lock();
b.withdrawImpl(amount); // WARNING! Calling withdrawImpl() requires locking b.mu.
depositImpl(amount); // OK. depositImpl() has no requirements.
mu.Unlock();
}
};
此示例演示了分析背后的基本概念。 GUARDED_BY
属性声明线程必须锁定 mu
才能读取或写入 balance
,从而确保增量和减量操作是原子的。类似地,REQUIRES
声明调用线程必须锁定 mu
才能调用 withdrawImpl
。因为调用者被假定为已锁定 mu
,所以修改方法主体内的 balance
是安全的。
depositImpl()
方法没有 REQUIRES
,因此分析会发出警告。线程安全分析不是跨过程的,因此必须显式声明调用者要求。 transferFrom()
中也存在警告,因为尽管该方法锁定了 this->mu
,但它没有锁定 b.mu
。分析理解这些是两个不同的对象中的两个独立的互斥量。
最后, withdraw()
方法中存在警告,因为它未能解锁 mu
。每个锁都必须有一个相应的解锁,分析将检测双重锁定和双重解锁。函数被允许获取锁而不会释放它(反之亦然),但必须使用 ACQUIRE
/RELEASE
进行注释。
运行分析¶
要运行分析,只需使用 -Wthread-safety
标志编译,例如
clang -c -Wthread-safety example.cpp
请注意,此示例假定存在一个适当注释的 mutex.h,它声明哪些方法执行锁定、解锁等。
基本概念:功能¶
线程安全分析提供了一种使用功能保护资源的方法。资源要么是数据成员,要么是提供对某些底层资源访问权限的函数/方法。分析确保调用线程无法访问资源(即调用函数或读取/写入数据),除非它具有执行此操作的功能。
功能与声明特定方法来获取和释放功能的命名 C++ 对象相关联。对象的名称用于标识功能。最常见的示例是互斥量。例如,如果 mu
是一个互斥量,那么调用 mu.Lock()
会导致调用线程获得访问由 mu
保护的数据的功能。类似地,调用 mu.Unlock()
会释放该功能。
线程可以独占或共享地持有功能。一次只有一个线程可以持有独占功能,而多个线程可以同时持有共享功能。此机制强制执行多读者、单写者模式。对受保护数据的写操作需要独占访问权限,而读操作只需要共享访问权限。
在程序执行期间的任何给定时刻,线程都持有特定的一组功能(例如,它已锁定的互斥量集)。这些充当允许线程访问给定资源的密钥或令牌。就像物理安全密钥一样,线程无法复制功能,也无法销毁功能。线程只能将功能释放给另一个线程,或从另一个线程获取功能。注释有意回避用于获取和释放功能的精确机制;它假定底层实现(例如,互斥量实现)以适当的方式进行传递。
在程序执行期间的给定时间点,给定线程实际持有的功能集是一个运行时概念。静态分析通过计算该集合的近似值,称为功能环境来工作。功能环境是针对每个程序点计算的,并描述在该特定点静态已知持有或未持有的功能集。此环境是运行时线程实际持有的完整功能集的保守近似。
参考指南¶
线程安全分析使用属性来声明线程约束。属性必须附加到命名声明,例如类、方法和数据成员。强烈建议用户为各种属性定义宏;示例定义可以在下面的 mutex.h 中找到。以下文档假设使用宏。
属性只控制线程安全分析做出的假设和它发出的警告。它们不影响生成的代码或运行时的行为。
由于历史原因,线程安全的先前版本使用的宏名称非常以锁为中心。这些宏后来被重命名为适合更通用的功能模型。先前名称仍在使用中,并在适当的地方在标签之前下提及。
GUARDED_BY(c) 和 PT_GUARDED_BY(c)¶
GUARDED_BY
是数据成员上的属性,它声明该数据成员由给定功能保护。对数据的读取操作需要共享访问权限,而写操作需要独占访问权限。
PT_GUARDED_BY
类似,但用于指针和智能指针。对数据成员本身没有约束,但它指向的数据受给定功能保护。
Mutex mu;
int *p1 GUARDED_BY(mu);
int *p2 PT_GUARDED_BY(mu);
unique_ptr<int> p3 PT_GUARDED_BY(mu);
void test() {
p1 = 0; // Warning!
*p2 = 42; // Warning!
p2 = new int; // OK.
*p3 = 42; // Warning!
p3.reset(new int); // OK.
}
EXCLUDES(…)¶
之前: LOCKS_EXCLUDED
EXCLUDES
是函数或方法上的属性,它声明调用者不得持有给定功能。此注释用于防止死锁。许多互斥量实现不是可重入的,因此如果函数第二次获取互斥量,就会发生死锁。
Mutex mu;
int a GUARDED_BY(mu);
void clear() EXCLUDES(mu) {
mu.Lock();
a = 0;
mu.Unlock();
}
void reset() {
mu.Lock();
clear(); // Warning! Caller cannot hold 'mu'.
mu.Unlock();
}
与 REQUIRES
不同, EXCLUDES
是可选的。如果属性丢失,分析不会发出警告,这在某些情况下会导致假阴性。此问题将在 负功能 中进一步讨论。
NO_THREAD_SAFETY_ANALYSIS¶
NO_THREAD_SAFETY_ANALYSIS
是函数或方法上的一个属性,它会关闭该方法的线程安全检查。它为以下两种情况提供了逃生门:(1)故意线程不安全的函数,或(2)线程安全的函数,但过于复杂,分析无法理解。 原因(2)将在下面 已知限制 中描述。
class Counter {
Mutex mu;
int a GUARDED_BY(mu);
void unsafeIncrement() NO_THREAD_SAFETY_ANALYSIS { a++; }
};
与其他属性不同,NO_THREAD_SAFETY_ANALYSIS
不是函数接口的一部分,因此应放在函数定义(在 .cc
或 .cpp
文件中),而不是函数声明(在头文件中)。
RETURN_CAPABILITY(c)¶
以前: LOCK_RETURNED
RETURN_CAPABILITY
是函数或方法上的一个属性,它声明该函数返回对给定能力的引用。它用于注释返回互斥锁的 getter 方法。
class MyClass {
private:
Mutex mu;
int a GUARDED_BY(mu);
public:
Mutex* getMu() RETURN_CAPABILITY(mu) { return μ }
// analysis knows that getMu() == mu
void clear() REQUIRES(getMu()) { a = 0; }
};
ACQUIRED_BEFORE(…), ACQUIRED_AFTER(…)¶
ACQUIRED_BEFORE
和 ACQUIRED_AFTER
是成员声明上的属性,专门用于互斥锁或其他能力的声明。这些声明强制执行互斥锁必须获取的特定顺序,以防止死锁。
Mutex m1;
Mutex m2 ACQUIRED_AFTER(m1);
// Alternative declaration
// Mutex m2;
// Mutex m1 ACQUIRED_BEFORE(m2);
void foo() {
m2.Lock();
m1.Lock(); // Warning! m2 must be acquired after m1.
m1.Unlock();
m2.Unlock();
}
CAPABILITY(<string>)¶
以前: LOCKABLE
CAPABILITY
是类上的一个属性,它指定类的对象可以用作能力。字符串参数在错误消息中指定能力的类型,例如 "mutex"
。请参阅上面给出的 Container
示例,或 mutex.h 中的 Mutex
类。
SCOPED_CAPABILITY¶
以前: SCOPED_LOCKABLE
SCOPED_CAPABILITY
是在类上实现 RAII 样式锁定的属性,其中能力在构造函数中获取,并在析构函数中释放。此类类需要特殊处理,因为构造函数和析构函数通过不同的名称引用能力;请参阅下面 mutex.h 中的 MutexLocker
类。
作用域能力被视为在构造时隐式获取并在析构时释放的能力。它们与构造函数或按值返回它们的函数(使用 C++17 保证的复制省略)上的线程安全属性中命名的(常规)能力集相关联。其他成员函数上的获取类型属性被视为应用于该关联能力集,而 RELEASE
意味着函数以任何模式释放所有关联能力。
GUARDED_VAR 和 PT_GUARDED_VAR¶
使用这些属性已被弃用。
警告标志¶
-Wthread-safety
: 启用以下内容的总括标志-Wthread-safety-attributes
: 线程安全属性的语义检查。-Wthread-safety-analysis
: 核心分析。-Wthread-safety-precise
: 要求互斥锁表达式完全匹配。此警告可以为具有大量别名的代码禁用。
-Wthread-safety-reference
: 检查受保护成员何时按引用传递。
负面能力 是一项实验性功能,通过以下方式启用
-Wthread-safety-negative
: 负面能力。默认情况下关闭。
当分析中添加新功能和检查时,它们通常会引入额外的警告。这些警告最初会作为beta 警告发布一段时间,之后它们会迁移到标准分析中。
-Wthread-safety-beta
: 新功能。默认情况下关闭。
负面能力¶
线程安全分析旨在防止竞争条件和死锁。GUARDED_BY 和 REQUIRES 属性通过确保在读取或写入受保护数据之前拥有能力来防止竞争条件,而 EXCLUDES 属性通过确保互斥锁未被拥有来防止死锁。
但是,EXCLUDES 是一个可选属性,它没有提供与 REQUIRES 相同的安全保证。特别是
获取能力的函数不必排除它。
调用排除能力的函数的函数不会传递排除该能力。
因此,EXCLUDES 很容易产生假阴性
class Foo {
Mutex mu;
void foo() {
mu.Lock();
bar(); // No warning.
baz(); // No warning.
mu.Unlock();
}
void bar() { // No warning. (Should have EXCLUDES(mu)).
mu.Lock();
// ...
mu.Unlock();
}
void baz() {
bif(); // No warning. (Should have EXCLUDES(mu)).
}
void bif() EXCLUDES(mu);
};
负面要求是 EXCLUDES 的替代方法,它提供更强的安全保证。负面要求使用 REQUIRES 属性以及 !
运算符,以指示不应该拥有能力。
例如,使用 REQUIRES(!mu)
而不是 EXCLUDES(mu)
将产生适当的警告
class FooNeg {
Mutex mu;
void foo() REQUIRES(!mu) { // foo() now requires !mu.
mu.Lock();
bar();
baz();
mu.Unlock();
}
void bar() {
mu.Lock(); // WARNING! Missing REQUIRES(!mu).
// ...
mu.Unlock();
}
void baz() {
bif(); // WARNING! Missing REQUIRES(!mu).
}
void bif() REQUIRES(!mu);
};
负面要求是一项实验性功能,默认情况下处于关闭状态,因为它将在现有代码中产生许多警告。可以通过传递 -Wthread-safety-negative
来启用它。
常见问题解答¶
我应该将属性放在头文件中还是在 .cc/.cpp/.cxx 文件中?
(A) 属性是函数正式接口的一部分,应该始终放在头文件中,因为它们对包含该头文件的任何内容都是可见的。 .cpp 文件中的属性在立即翻译单元之外不可见,这会导致假阴性和假阳性。
“此处的每条路径上都没有锁定互斥锁?” 这是什么意思?
请参阅下面 没有条件持有的锁。。
已知限制¶
词法范围¶
线程安全属性包含普通的 C++ 表达式,因此遵循普通的 C++ 范围规则。特别是,这意味着互斥锁和其他能力必须在可以在属性中使用之前声明。在单个类中,使用前声明是可以的,因为属性与方法体同时解析。(C++ 将方法体的解析延迟到类末尾。)但是,不允许在类之间使用前声明,如下所示。
class Foo;
class Bar {
void bar(Foo* f) REQUIRES(f->mu); // Error: mu undeclared.
};
class Foo {
Mutex mu;
};
私有互斥锁¶
良好的软件工程实践规定互斥锁应该是私有成员,因为线程安全类使用的锁定机制是其内部实现的一部分。但是,私有互斥锁有时会泄漏到类的公共接口中。线程安全属性遵循正常的 C++ 访问限制,因此如果 mu
是 c
的私有成员,那么在属性中编写 c.mu
是错误的。
一种解决方法是(滥用)RETURN_CAPABILITY
属性为私有互斥锁提供公共名称,而无需实际公开底层互斥锁。例如
class MyClass {
private:
Mutex mu;
public:
// For thread safety analysis only. Does not need to be defined.
Mutex* getMu() RETURN_CAPABILITY(mu);
void doSomething() REQUIRES(mu);
};
void doSomethingTwice(MyClass& c) REQUIRES(c.getMu()) {
// The analysis thinks that c.getMu() == c.mu
c.doSomething();
c.doSomething();
}
在上面的示例中,doSomethingTwice()
是一个需要锁定 c.mu
的外部例程,由于 mu
是私有的,因此无法直接声明它。这种模式不建议使用,因为它违反了封装,但有时是必要的,尤其是在向现有代码库添加注释时。解决方法是将 getMu()
定义为一个假的 getter 方法,它只为线程安全分析的利益提供。
没有条件持有的锁。¶
分析必须能够在每个程序点确定锁是否被持有,或未被持有。因此,锁可能被持有的代码段将生成虚假警告(假阳性)。例如
void foo() {
bool b = needsToLock();
if (b) mu.Lock();
... // Warning! Mutex 'mu' is not held on every path through here.
if (b) mu.Unlock();
}
在构造函数和析构函数中没有检查。¶
分析目前不会在构造函数或析构函数中执行任何检查。换句话说,每个构造函数和析构函数都被视为使用 NO_THREAD_SAFETY_ANALYSIS
进行注释。这样做是因为在初始化期间,通常只有一个线程可以访问正在初始化的对象,因此在不获取任何锁的情况下初始化受保护的成员是安全的(也是常见的做法)。析构函数也是如此。
理想情况下,分析应该允许在初始化或销毁正在初始化的对象时初始化受保护的成员,同时仍然对其他所有内容强制执行通常的访问限制。然而,这在实践中很难强制执行,因为在复杂的基于指针的数据结构中,很难确定哪些数据由封闭对象拥有。
不内联。¶
线程安全分析严格来说是过程内的,就像普通的类型检查一样。它只依赖于函数的声明属性,不会尝试内联任何方法调用。因此,以下代码将不起作用
template<class T>
class AutoCleanup {
T* object;
void (T::*mp)();
public:
AutoCleanup(T* obj, void (T::*imp)()) : object(obj), mp(imp) { }
~AutoCleanup() { (object->*mp)(); }
};
Mutex mu;
void foo() {
mu.Lock();
AutoCleanup<Mutex>(&mu, &Mutex::Unlock);
// ...
} // Warning, mu is not unlocked.
在这种情况下,Autocleanup
的析构函数调用了 mu.Unlock()
,因此警告是错误的。但是,线程安全分析无法看到解锁,因为它没有尝试内联析构函数。此外,没有办法注释析构函数,因为析构函数正在调用一个静态未知的函数。这种模式 simply 不支持。
无别名分析。¶
分析目前不跟踪指针别名。因此,如果两个指针都指向同一个互斥锁,可能会出现误报。
class MutexUnlocker {
Mutex* mu;
public:
MutexUnlocker(Mutex* m) RELEASE(m) : mu(m) { mu->Unlock(); }
~MutexUnlocker() ACQUIRE(mu) { mu->Lock(); }
};
Mutex mutex;
void test() REQUIRES(mutex) {
{
MutexUnlocker munl(&mutex); // unlocks mutex
doSomeIO();
} // Warning: locks munl.mu
}
MutexUnlocker 类旨在作为 MutexLocker 类的对偶,定义在 mutex.h 中。但是,它不起作用,因为分析不知道 munl.mu == mutex。SCOPED_CAPABILITY 属性处理 MutexLocker 的别名,但只对该特定模式这样做。
ACQUIRED_BEFORE(…) 和 ACQUIRED_AFTER(…) 的支持仍在试验阶段。¶
ACQUIRED_BEFORE(…) 和 ACQUIRED_AFTER(…) 目前正在 -Wthread-safety-beta
标记下开发。
mutex.h¶
线程安全分析可用于任何线程库,但它确实需要将线程 API 包装在具有适当注释的类和方法中。以下代码提供 mutex.h
作为示例;这些方法应该被填充以调用相应的底层实现。
#ifndef THREAD_SAFETY_ANALYSIS_MUTEX_H
#define THREAD_SAFETY_ANALYSIS_MUTEX_H
// Enable thread safety attributes only with clang.
// The attributes can be safely erased when compiling with other compilers.
#if defined(__clang__) && (!defined(SWIG))
#define THREAD_ANNOTATION_ATTRIBUTE__(x) __attribute__((x))
#else
#define THREAD_ANNOTATION_ATTRIBUTE__(x) // no-op
#endif
#define CAPABILITY(x) \
THREAD_ANNOTATION_ATTRIBUTE__(capability(x))
#define SCOPED_CAPABILITY \
THREAD_ANNOTATION_ATTRIBUTE__(scoped_lockable)
#define GUARDED_BY(x) \
THREAD_ANNOTATION_ATTRIBUTE__(guarded_by(x))
#define PT_GUARDED_BY(x) \
THREAD_ANNOTATION_ATTRIBUTE__(pt_guarded_by(x))
#define ACQUIRED_BEFORE(...) \
THREAD_ANNOTATION_ATTRIBUTE__(acquired_before(__VA_ARGS__))
#define ACQUIRED_AFTER(...) \
THREAD_ANNOTATION_ATTRIBUTE__(acquired_after(__VA_ARGS__))
#define REQUIRES(...) \
THREAD_ANNOTATION_ATTRIBUTE__(requires_capability(__VA_ARGS__))
#define REQUIRES_SHARED(...) \
THREAD_ANNOTATION_ATTRIBUTE__(requires_shared_capability(__VA_ARGS__))
#define ACQUIRE(...) \
THREAD_ANNOTATION_ATTRIBUTE__(acquire_capability(__VA_ARGS__))
#define ACQUIRE_SHARED(...) \
THREAD_ANNOTATION_ATTRIBUTE__(acquire_shared_capability(__VA_ARGS__))
#define RELEASE(...) \
THREAD_ANNOTATION_ATTRIBUTE__(release_capability(__VA_ARGS__))
#define RELEASE_SHARED(...) \
THREAD_ANNOTATION_ATTRIBUTE__(release_shared_capability(__VA_ARGS__))
#define RELEASE_GENERIC(...) \
THREAD_ANNOTATION_ATTRIBUTE__(release_generic_capability(__VA_ARGS__))
#define TRY_ACQUIRE(...) \
THREAD_ANNOTATION_ATTRIBUTE__(try_acquire_capability(__VA_ARGS__))
#define TRY_ACQUIRE_SHARED(...) \
THREAD_ANNOTATION_ATTRIBUTE__(try_acquire_shared_capability(__VA_ARGS__))
#define EXCLUDES(...) \
THREAD_ANNOTATION_ATTRIBUTE__(locks_excluded(__VA_ARGS__))
#define ASSERT_CAPABILITY(x) \
THREAD_ANNOTATION_ATTRIBUTE__(assert_capability(x))
#define ASSERT_SHARED_CAPABILITY(x) \
THREAD_ANNOTATION_ATTRIBUTE__(assert_shared_capability(x))
#define RETURN_CAPABILITY(x) \
THREAD_ANNOTATION_ATTRIBUTE__(lock_returned(x))
#define NO_THREAD_SAFETY_ANALYSIS \
THREAD_ANNOTATION_ATTRIBUTE__(no_thread_safety_analysis)
// Defines an annotated interface for mutexes.
// These methods can be implemented to use any internal mutex implementation.
class CAPABILITY("mutex") Mutex {
public:
// Acquire/lock this mutex exclusively. Only one thread can have exclusive
// access at any one time. Write operations to guarded data require an
// exclusive lock.
void Lock() ACQUIRE();
// Acquire/lock this mutex for read operations, which require only a shared
// lock. This assumes a multiple-reader, single writer semantics. Multiple
// threads may acquire the mutex simultaneously as readers, but a writer
// must wait for all of them to release the mutex before it can acquire it
// exclusively.
void ReaderLock() ACQUIRE_SHARED();
// Release/unlock an exclusive mutex.
void Unlock() RELEASE();
// Release/unlock a shared mutex.
void ReaderUnlock() RELEASE_SHARED();
// Generic unlock, can unlock exclusive and shared mutexes.
void GenericUnlock() RELEASE_GENERIC();
// Try to acquire the mutex. Returns true on success, and false on failure.
bool TryLock() TRY_ACQUIRE(true);
// Try to acquire the mutex for read operations.
bool ReaderTryLock() TRY_ACQUIRE_SHARED(true);
// Assert that this mutex is currently held by the calling thread.
void AssertHeld() ASSERT_CAPABILITY(this);
// Assert that is mutex is currently held for read operations.
void AssertReaderHeld() ASSERT_SHARED_CAPABILITY(this);
// For negative capabilities.
const Mutex& operator!() const { return *this; }
};
// Tag types for selecting a constructor.
struct adopt_lock_t {} inline constexpr adopt_lock = {};
struct defer_lock_t {} inline constexpr defer_lock = {};
struct shared_lock_t {} inline constexpr shared_lock = {};
// MutexLocker is an RAII class that acquires a mutex in its constructor, and
// releases it in its destructor.
class SCOPED_CAPABILITY MutexLocker {
private:
Mutex* mut;
bool locked;
public:
// Acquire mu, implicitly acquire *this and associate it with mu.
MutexLocker(Mutex *mu) ACQUIRE(mu) : mut(mu), locked(true) {
mu->Lock();
}
// Assume mu is held, implicitly acquire *this and associate it with mu.
MutexLocker(Mutex *mu, adopt_lock_t) REQUIRES(mu) : mut(mu), locked(true) {}
// Acquire mu in shared mode, implicitly acquire *this and associate it with mu.
MutexLocker(Mutex *mu, shared_lock_t) ACQUIRE_SHARED(mu) : mut(mu), locked(true) {
mu->ReaderLock();
}
// Assume mu is held in shared mode, implicitly acquire *this and associate it with mu.
MutexLocker(Mutex *mu, adopt_lock_t, shared_lock_t) REQUIRES_SHARED(mu)
: mut(mu), locked(true) {}
// Assume mu is not held, implicitly acquire *this and associate it with mu.
MutexLocker(Mutex *mu, defer_lock_t) EXCLUDES(mu) : mut(mu), locked(false) {}
// Same as constructors, but without tag types. (Requires C++17 copy elision.)
static MutexLocker Lock(Mutex *mu) ACQUIRE(mu);
static MutexLocker Adopt(Mutex *mu) REQUIRES(mu);
static MutexLocker ReaderLock(Mutex *mu) ACQUIRE_SHARED(mu);
static MutexLocker AdoptReaderLock(Mutex *mu) REQUIRES_SHARED(mu);
static MutexLocker DeferLock(Mutex *mu) EXCLUDES(mu);
// Release *this and all associated mutexes, if they are still held.
// There is no warning if the scope was already unlocked before.
~MutexLocker() RELEASE() {
if (locked)
mut->GenericUnlock();
}
// Acquire all associated mutexes exclusively.
void Lock() ACQUIRE() {
mut->Lock();
locked = true;
}
// Try to acquire all associated mutexes exclusively.
bool TryLock() TRY_ACQUIRE(true) {
return locked = mut->TryLock();
}
// Acquire all associated mutexes in shared mode.
void ReaderLock() ACQUIRE_SHARED() {
mut->ReaderLock();
locked = true;
}
// Try to acquire all associated mutexes in shared mode.
bool ReaderTryLock() TRY_ACQUIRE_SHARED(true) {
return locked = mut->ReaderTryLock();
}
// Release all associated mutexes. Warn on double unlock.
void Unlock() RELEASE() {
mut->Unlock();
locked = false;
}
// Release all associated mutexes. Warn on double unlock.
void ReaderUnlock() RELEASE() {
mut->ReaderUnlock();
locked = false;
}
};
#ifdef USE_LOCK_STYLE_THREAD_SAFETY_ATTRIBUTES
// The original version of thread safety analysis the following attribute
// definitions. These use a lock-based terminology. They are still in use
// by existing thread safety code, and will continue to be supported.
// Deprecated.
#define PT_GUARDED_VAR \
THREAD_ANNOTATION_ATTRIBUTE__(pt_guarded_var)
// Deprecated.
#define GUARDED_VAR \
THREAD_ANNOTATION_ATTRIBUTE__(guarded_var)
// Replaced by REQUIRES
#define EXCLUSIVE_LOCKS_REQUIRED(...) \
THREAD_ANNOTATION_ATTRIBUTE__(exclusive_locks_required(__VA_ARGS__))
// Replaced by REQUIRES_SHARED
#define SHARED_LOCKS_REQUIRED(...) \
THREAD_ANNOTATION_ATTRIBUTE__(shared_locks_required(__VA_ARGS__))
// Replaced by CAPABILITY
#define LOCKABLE \
THREAD_ANNOTATION_ATTRIBUTE__(lockable)
// Replaced by SCOPED_CAPABILITY
#define SCOPED_LOCKABLE \
THREAD_ANNOTATION_ATTRIBUTE__(scoped_lockable)
// Replaced by ACQUIRE
#define EXCLUSIVE_LOCK_FUNCTION(...) \
THREAD_ANNOTATION_ATTRIBUTE__(exclusive_lock_function(__VA_ARGS__))
// Replaced by ACQUIRE_SHARED
#define SHARED_LOCK_FUNCTION(...) \
THREAD_ANNOTATION_ATTRIBUTE__(shared_lock_function(__VA_ARGS__))
// Replaced by RELEASE and RELEASE_SHARED
#define UNLOCK_FUNCTION(...) \
THREAD_ANNOTATION_ATTRIBUTE__(unlock_function(__VA_ARGS__))
// Replaced by TRY_ACQUIRE
#define EXCLUSIVE_TRYLOCK_FUNCTION(...) \
THREAD_ANNOTATION_ATTRIBUTE__(exclusive_trylock_function(__VA_ARGS__))
// Replaced by TRY_ACQUIRE_SHARED
#define SHARED_TRYLOCK_FUNCTION(...) \
THREAD_ANNOTATION_ATTRIBUTE__(shared_trylock_function(__VA_ARGS__))
// Replaced by ASSERT_CAPABILITY
#define ASSERT_EXCLUSIVE_LOCK(...) \
THREAD_ANNOTATION_ATTRIBUTE__(assert_exclusive_lock(__VA_ARGS__))
// Replaced by ASSERT_SHARED_CAPABILITY
#define ASSERT_SHARED_LOCK(...) \
THREAD_ANNOTATION_ATTRIBUTE__(assert_shared_lock(__VA_ARGS__))
// Replaced by EXCLUDE_CAPABILITY.
#define LOCKS_EXCLUDED(...) \
THREAD_ANNOTATION_ATTRIBUTE__(locks_excluded(__VA_ARGS__))
// Replaced by RETURN_CAPABILITY
#define LOCK_RETURNED(x) \
THREAD_ANNOTATION_ATTRIBUTE__(lock_returned(x))
#endif // USE_LOCK_STYLE_THREAD_SAFETY_ATTRIBUTES
#endif // THREAD_SAFETY_ANALYSIS_MUTEX_H