线程安全分析宏

概述

简介

提供基于 clang 的编译期线程安全分析(Thread Safety Analysis,TSA)注解宏。这些宏用于标注互斥锁、受保护数据、函数的前置/后置条件等,帮助编译器在编译阶段发现数据竞争和锁使用错误。在非 clang 编译器下,这些宏会被展开为空操作,以保持兼容性。

#include "thread_safety_analysis_macros.h"

涉及功能

能力(Capability)类宏

用于标注锁或可锁对象。

说明
CAPABILITY(x) 标注类/结构体为可锁能力(如互斥锁)。用法:class Foo CAPABILITY("mutex") { ... };
REENTRANT_CAPABILITY 标注可重入(允许递归加锁)的可锁类
SCOPED_CAPABILITY 标注 RAII 类(如锁守卫),在构造函数中获取能力,在析构函数中释放

数据保护宏

用于标注受锁保护的变量。

说明
GUARDED_BY(x) 标注成员变量由某互斥锁保护,访问该变量时需持有该锁
PT_GUARDED_BY(x) 类似 GUARDED_BY,用于指针变量,表示指针所指向的数据由指定互斥锁保护

锁序宏

用于声明锁的获取顺序,避免死锁。

说明
ACQUIRED_BEFORE(...) 声明该能力必须在其后列出的能力之前获取
ACQUIRED_AFTER(...) 声明该能力必须在其后列出的能力之后获取

函数前置条件宏

描述调用函数前需满足的锁状态。

说明
REQUIRES(...) 调用前必须持有指定的互斥锁(独占锁)
REQUIRES_SHARED(...) 调用前必须持有指定的共享锁(读锁)
EXCLUDES(...) 调用时不得持有指定的互斥锁,避免在持锁时调用此类函数引发死锁

函数锁操作宏

描述函数内部对锁的获取与释放。

说明
ACQUIRE(...) 函数会获取(加锁)指定的互斥锁(独占)
ACQUIRE_SHARED(...) 函数会以共享模式获取(加读锁)指定的互斥锁
RELEASE(...) 函数会释放(解锁)指定的互斥锁(此前以独占模式持有)
RELEASE_SHARED(...) 函数会释放(解锁)以共享模式持有的互斥锁
RELEASE_GENERIC(...) 函数会释放互斥锁(兼容独占或共享模式),用于通用解锁函数
TRY_ACQUIRE(...) 函数会尝试获取互斥锁,第一个参数为布尔值,表示返回 true 时表示成功
TRY_ACQUIRE_SHARED(...) 函数会尝试以共享模式获取互斥锁

断言与返回宏

用于断言和返回值标注。

说明
ASSERT_CAPABILITY(x) 断言当前线程持有指定锁
ASSERT_SHARED_CAPABILITY(x) 断言当前线程以共享模式持有指定锁
RETURN_CAPABILITY(x) 标注函数返回其持有的锁的引用或指针(常用于返回 capability 的 getter)
NO_THREAD_SAFETY_ANALYSIS 关闭对当前函数或代码块的线程安全分析,用于抑制误报或分析器无法推导的情况

使用示例

1. 定义带注解的互斥锁与 RAII 锁守卫

class CAPABILITY("mutex") Mutex {
public:
    void Lock() ACQUIRE() { /* ... */ }
    void Unlock() RELEASE() { /* ... */ }
    void ReaderLock() ACQUIRE_SHARED() { /* ... */ }
    void ReaderUnlock() RELEASE_SHARED() { /* ... */ }
    bool TryLock() TRY_ACQUIRE(true) { /* ... */ }
};

class SCOPED_CAPABILITY MutexLocker {
public:
    MutexLocker(Mutex *mu) ACQUIRE(mu) : mu_(mu) { mu->Lock(); }
    ~MutexLocker() RELEASE() { if (mu_) mu_->Unlock(); }
private:
    Mutex *mu_;
};

2. 标注受保护数据与成员函数

class Counter {
public:
    void Increment() REQUIRES(mu_) { ++value_; }
    int Get() REQUIRES_SHARED(mu_) { return value_; }
    Mutex &GetMutex() RETURN_CAPABILITY(mu_) { return mu_; }
private:
    Mutex mu_;
    int value_ GUARDED_BY(mu_);
};

3. 使用标准库锁

使用标准库std::mutex, std::lock_guard,需要增加-D_LIBCPP_ENABLE_THREAD_SAFETY_ANNOTATIONS编译选项。

#include <mutex>
#include <vector>

std::mutex mut;
std::vector<int> data{0,1} GUARDED_BY(mut);

// access with capabilities, no warning.
{
    std::lock_guard<std::mutex> lock(mut);
    int a = data.at(0);
}

// access without capabilities, warning.
{
    int b = data.at(0);
}

编译:clang++ test.cpp -Wthread-safety -stdlib=libc++ -D_LIBCPP_ENABLE_THREAD_SAFETY_ANNOTATIONS -I base/include/ -o a.out。注:-I base/include为示例路径,实际路径需要根据项目调整,OH构建系统中包含TSA头文件需要在组件BUILD.gn中增加:

external_deps += [ c_utils:utils ]

4. 测试用例

测试用例编写简介:

  1. 定义具有capability的互斥锁类:
// Use CAPABILITY macro to asign capability
class CAPABILITY("mutex") Mutex {
public:
    // Declare methods a mutuable exclusive lock should have.
    void Lock() ACQUIRE();

    // Acquire/lock this mutex for read operations.
    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 this mutex is currently held for read operations.
    void AssertReaderHeld() ASSERT_SHARED_CAPABILITY(this) {}

    // For negative capabilities.
    const Mutex& operator!() const;

    bool IsLocked() const;

    bool IsSharedLocked() const;

private:
    bool locked_;
    bool sharedLocked_;
};
  1. 使用HWTEST_F测试框架写具体用例:
HWTEST_F(UtilsThreadSafetyAnalysisTest, MutexBasicOperations, TestSize.Level0)
{
    Mutex mu;

    EXPECT_FALSE(mu.IsLocked());

    // Exclusive lock / unlock.
    mu.Lock();
    EXPECT_TRUE(mu.IsLocked());
    EXPECT_FALSE(mu.IsSharedLocked());

    mu.Unlock();
    EXPECT_FALSE(mu.IsLocked());

    // Shared lock / unlock.
    mu.ReaderLock();
    EXPECT_TRUE(mu.IsLocked());
    EXPECT_TRUE(mu.IsSharedLocked());

    mu.ReaderUnlock();
    EXPECT_FALSE(mu.IsLocked());

    // TryLock should fail when the mutex is already locked.
    bool locked = mu.TryLock();
    EXPECT_TRUE(locked);
    EXPECT_TRUE(mu.IsLocked());

    // The second TryLock is expected to fail; in that case we cannot assume
    // the lock is held again, so we must not call GenericUnlock for it.
    bool lockedAgain = mu.TryLock();
    EXPECT_FALSE(lockedAgain);

    if (locked) {
        mu.GenericUnlock();
    }
    EXPECT_FALSE(mu.IsLocked());

    // Try shared lock when currently unlocked.
    bool sharedLocked = mu.ReaderTryLock();
    EXPECT_TRUE(sharedLocked);
    EXPECT_TRUE(mu.IsLocked());
    EXPECT_TRUE(mu.IsSharedLocked());
    if (sharedLocked) {
        mu.GenericUnlock();
    }
    EXPECT_FALSE(mu.IsLocked());
}

TSA检测宏比较特殊:上述测试用例实质上测试了Mutex类的持锁、解锁功能,而TSA宏在编译期发挥作用,即用例编译器成功即发挥检测作用且代码正确使用TSA宏。

5. 编译与运行

  • 使用 clang 编译时,需启用 -Wthread-safety 才能进行线程安全分析,详见如何启用分析

常见问题

详细信息参考LLVM官方文档

  1. 应用于std::mutex, std::lock_guard:需要应用于标准库互斥锁,需要添加编译选项宏-D_LIBCPP_ENABLE_THREAD_SAFETY_ANNOTATIONS,且使用libc++而非libstdc++。但注意:该编译选项宏会使能clang编译器对所有使用标注库互斥锁、lock_guard代码进行线程安全性检测,可能引入新的告警。

  2. 编译器支持:线程安全分析仅在 clang 下生效。在 GCC、MSVC 等编译器下,这些宏会展开为空,不会报错,但也不会进行静态检查。

  3. 如何启用分析:使用 clang 时需添加编译选项 -Wthread-safety(包含了 -Wthread-safety-analysis, -Wthread-safety-attributes, -Wthread-safety-precise, -Wthread-safety-reference,也可单独开启此四个安全检测宏)或 -Wthread-safety-pointer(检测受保护变量的指针、指向受保护数据的指针),否则注解不会触发诊断(OH构建系统已默认开启)。OH构建系统默认提升thread-safety warning为error,可以使用-Wno-error=thread-safety降级为warning。

  4. NO_THREAD_SAFETY_ANALYSIS 的用法:当分析器产生误报,或某些复杂的锁逻辑无法被正确推导时,可对相应函数使用 NO_THREAD_SAFETY_ANALYSIS 跳过分析。应谨慎使用,避免掩盖真实问题。

  5. SCOPED_CAPABILITY 与 RAII:用于锁守卫等 RAII 类型时,构造函数应使用 ACQUIREACQUIRE_SHARED 标注加锁操作,析构函数使用 RELEASERELEASE_GENERIC 标注解锁操作。

  6. REENTRANT_CAPABILITY支持情况:当前OpenHarmony构建工具链的clang版本还不支持REENTRANT_CAPABILITY,请等待升级。