blob: 8b5542bb17cb6c0f8264ed14c6a31542d24094e3 [file] [log] [blame]
// Copyright 2019 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <lib/zircon-internal/thread_annotations.h>
#include <lockdep/lockdep.h>
#include <zxtest/zxtest.h>
#include "lockdep/lock_traits.h"
// If enabled, introduce locking errors into the tests that we expect Clang
// to detect.
#define TEST_CLANG_DETECTS_LOCK_MISUSE 0
namespace {
using lockdep::AssertHeld;
using lockdep::Guard;
// A custom Mutex implementation.
struct __TA_CAPABILITY("mutex") FakeMutex {
FakeMutex() = default;
// No copy or move.
FakeMutex(FakeMutex&&) = delete;
FakeMutex(const FakeMutex&) = delete;
FakeMutex& operator=(FakeMutex&&) = delete;
FakeMutex& operator=(const FakeMutex&) = delete;
// Locking operations.
void Acquire() __TA_ACQUIRE() { acquired = true; }
void Release() __TA_RELEASE() { acquired = false; }
void AssertHeld() const __TA_ASSERT() { assert_held_called = true; }
bool acquired = false;
mutable bool assert_held_called = false;
};
LOCK_DEP_TRAITS(FakeMutex, lockdep::LockFlagsNone);
// Take/release locks in a way that Clang's lock analysis can't see.
template <typename Lock>
void SecretlyTakeLock(Lock* lock) __TA_NO_THREAD_SAFETY_ANALYSIS {
lock->lock().Acquire();
}
template <typename Lock>
void SecretlyReleaseLock(Lock* lock) __TA_NO_THREAD_SAFETY_ANALYSIS {
lock->lock().Release();
}
LOCK_DEP_SINGLETON_LOCK(SingletonLock, FakeMutex);
TEST(LockDep, SingletonLockGuard) {
static int guarded_var TA_GUARDED(SingletonLock::Get()) = 0;
EXPECT_FALSE(SingletonLock::Get()->lock().acquired);
// Take the lock, and ensure it was actually acquired.
Guard<FakeMutex> guard{SingletonLock::Get()};
EXPECT_TRUE(SingletonLock::Get()->lock().acquired);
// Access the locked variable. Clang should not complain.
guarded_var++;
// Release the lock.
guard.Release();
EXPECT_FALSE(SingletonLock::Get()->lock().acquired);
// Access the locked variable. Clang should fail compilation.
#if 0 || TEST_CLANG_DETECTS_LOCK_MISUSE
guarded_var++;
#endif
}
TEST(LockDep, SingletonLockAssertHeld) {
static int guarded_var TA_GUARDED(SingletonLock::Get()) = 0;
EXPECT_FALSE(SingletonLock::Get()->lock().acquired);
// Take the lock in a way the Clang can't detect.
SecretlyTakeLock(SingletonLock::Get());
EXPECT_TRUE(SingletonLock::Get()->lock().acquired);
// Call AssertHeld() on the lock. Clang should be satisifed we have
// the lock, and let us modify the guarded field.
SingletonLock::Get()->lock().assert_held_called = false;
AssertHeld(*SingletonLock::Get());
EXPECT_TRUE(SingletonLock::Get()->lock().assert_held_called);
guarded_var++;
// Release the lock.
SecretlyReleaseLock(SingletonLock::Get());
}
// A wrapped external lock.
FakeMutex global_lock;
LOCK_DEP_SINGLETON_LOCK_WRAPPER(WrappedGlobalLock, global_lock);
TEST(LockDep, WrappedGlobalLockGuard) {
static int guarded_var TA_GUARDED(WrappedGlobalLock::Get()) = 0;
static int guarded_raw_var TA_GUARDED(global_lock) = 0;
EXPECT_FALSE(WrappedGlobalLock::Get()->lock().acquired);
// Take the lock, and ensure it was actually acquired.
Guard<FakeMutex> guard{WrappedGlobalLock::Get()};
EXPECT_TRUE(WrappedGlobalLock::Get()->lock().acquired);
// Access the locked variable. Clang should not complain.
guarded_var++;
guarded_raw_var++;
// Release the lock.
guard.Release();
EXPECT_FALSE(WrappedGlobalLock::Get()->lock().acquired);
// Access the locked variable. Clang should fail compilation.
#if 0 || TEST_CLANG_DETECTS_LOCK_MISUSE
guarded_var++;
guarded_raw_var++;
#endif
}
TEST(LockDep, WrappedGlobalLockAssertHeld) {
static int guarded_var TA_GUARDED(WrappedGlobalLock::Get()) = 0;
static int guarded_raw_var TA_GUARDED(global_lock) = 0;
EXPECT_FALSE(WrappedGlobalLock::Get()->lock().acquired);
// Take the lock in a way the Clang can't detect.
SecretlyTakeLock(WrappedGlobalLock::Get());
EXPECT_TRUE(WrappedGlobalLock::Get()->lock().acquired);
// Call AssertHeld() on the lock. Clang should be satisifed we have
// the lock, and let us modify the guarded field.
WrappedGlobalLock::Get()->lock().assert_held_called = false;
AssertHeld(*WrappedGlobalLock::Get());
EXPECT_TRUE(WrappedGlobalLock::Get()->lock().assert_held_called);
guarded_var++;
guarded_raw_var++;
// Release the lock.
SecretlyReleaseLock(WrappedGlobalLock::Get());
}
// An object using FakeMutex.
struct FakeLockable {
int guarded_field __TA_GUARDED(lock) = 0;
LOCK_DEP_INSTRUMENT(FakeLockable, FakeMutex) lock;
};
TEST(LockDep, LockableObjectLockGuard) {
FakeLockable lockable;
EXPECT_FALSE(lockable.lock.lock().acquired);
// Take the lock, and ensure it was actually acquired.
Guard<FakeMutex> guard{&lockable.lock};
EXPECT_TRUE(lockable.lock.lock().acquired);
// Access the locked variable. Clang should not complain.
lockable.guarded_field++;
// Release the lock.
guard.Release();
EXPECT_FALSE(lockable.lock.lock().acquired);
// Access the locked variable. Clang should fail compilation.
#if 0 || TEST_CLANG_DETECTS_LOCK_MISUSE
lockable.guarded_field++;
#endif
}
TEST(LockDep, LockableObjectLockAssertHeld) {
FakeLockable lockable;
EXPECT_FALSE(lockable.lock.lock().acquired);
// Take the lock in a way the Clang can't detect.
SecretlyTakeLock(&lockable.lock);
EXPECT_TRUE(lockable.lock.lock().acquired);
// Call AssertHeld() on the lock. Clang should be satisifed we have
// the lock, and let us modify the guarded field.
lockable.lock.lock().assert_held_called = false;
AssertHeld(lockable.lock);
EXPECT_TRUE(lockable.lock.lock().assert_held_called);
lockable.guarded_field++;
// Release the lock.
SecretlyReleaseLock(&lockable.lock);
}
} // namespace