//===----------------------------------------------------------------------===// // // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. // See https://llvm.org/LICENSE.txt for license information. // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception // //===----------------------------------------------------------------------===// #include "lldb/Utility/Locked.h" #include "gtest/gtest.h" #include #include #include #include #include using namespace lldb_private; namespace { struct Widget { int value = 0; }; } // namespace // Default mutex types match the LLDB conventions: recursive_mutex for write // access, llvm::sys::RWMutex for read access. static_assert( std::is_same_v::mutex_type, std::recursive_mutex>); static_assert( std::is_same_v::mutex_type, std::recursive_mutex>); static_assert( std::is_same_v::mutex_type, std::recursive_mutex>); static_assert( std::is_same_v::mutex_type, llvm::sys::RWMutex>); static_assert( std::is_same_v::mutex_type, llvm::sys::RWMutex>); static_assert( std::is_same_v::mutex_type, llvm::sys::RWMutex>); // Force compile-time validation of every (pointer-flavor x mutex) combination // the templates are intended to support. template class lldb_private::Locked; template class lldb_private::Locked; template class lldb_private::Locked; template class lldb_private::Locked, std::recursive_mutex>; template class lldb_private::Locked, std::recursive_mutex>; template class lldb_private::SharedLocked; template class lldb_private::SharedLocked; template class lldb_private::SharedLocked, llvm::sys::RWMutex>; template class lldb_private::SharedLocked, llvm::sys::RWMutex>; // Locked is move-only; SharedLocked is copyable. static_assert(!std::is_copy_constructible_v>); static_assert(!std::is_copy_assignable_v>); static_assert(std::is_move_constructible_v>); static_assert(std::is_move_assignable_v>); static_assert(std::is_copy_constructible_v>); static_assert(std::is_copy_assignable_v>); static_assert(std::is_move_constructible_v>); static_assert(std::is_move_assignable_v>); TEST(LockedTest, DefaultConstructed) { LockedPtr handle; EXPECT_FALSE(handle); EXPECT_EQ(handle.get(), nullptr); SharedLockedPtr shared; EXPECT_FALSE(shared); EXPECT_EQ(shared.get(), nullptr); } TEST(LockedTest, ExclusivePtrAccess) { std::recursive_mutex mutex; Widget widget; LockedPtr handle(mutex, &widget); ASSERT_TRUE(handle); EXPECT_EQ(handle.get(), &widget); // A different thread cannot acquire the mutex while the handle holds it. std::thread other([&] { EXPECT_FALSE(mutex.try_lock()); }); other.join(); handle->value = 7; EXPECT_EQ((*handle).value, 7); } TEST(LockedTest, ExclusiveReleasesOnDestruction) { std::mutex mutex; Widget widget; { LockedPtr handle(mutex, &widget); EXPECT_FALSE(mutex.try_lock()); } if (mutex.try_lock()) mutex.unlock(); else ADD_FAILURE() << "Mutex was not released"; } TEST(LockedTest, MoveTransfersLock) { std::mutex mutex; Widget widget; LockedPtr first(mutex, &widget); EXPECT_FALSE(mutex.try_lock()); LockedPtr second = std::move(first); EXPECT_FALSE(mutex.try_lock()); EXPECT_TRUE(second); EXPECT_EQ(second.get(), &widget); // The moved-from handle no longer points anywhere — the raw pointer must // be nulled out so operator bool can't claim ownership of a lock the // handle no longer holds. EXPECT_FALSE(first); EXPECT_EQ(first.get(), nullptr); } TEST(LockedTest, AcceptsExternallyAcquiredLock) { std::mutex mutex; Widget widget; std::unique_lock lock(mutex); LockedPtr handle(std::move(lock), &widget); ASSERT_TRUE(handle); EXPECT_FALSE(mutex.try_lock()); } TEST(LockedTest, SharedPtrFlavor) { std::recursive_mutex mutex; auto widget_sp = std::make_shared(); widget_sp->value = 42; LockedSP handle(mutex, widget_sp); ASSERT_TRUE(handle); EXPECT_EQ(handle.get(), widget_sp.get()); EXPECT_EQ(handle->value, 42); } TEST(LockedTest, UniquePtrFlavor) { std::recursive_mutex mutex; auto widget_up = std::make_unique(); widget_up->value = 99; Widget *raw = widget_up.get(); LockedUP handle(mutex, std::move(widget_up)); ASSERT_TRUE(handle); EXPECT_EQ(handle.get(), raw); EXPECT_EQ(handle->value, 99); } TEST(LockedTest, SharedAccessOnRWMutex) { llvm::sys::RWMutex mutex; Widget widget; widget.value = 5; SharedLockedPtr reader(mutex, &widget); ASSERT_TRUE(reader); EXPECT_EQ(reader->value, 5); static_assert(std::is_same_v, "shared access borrows a const-qualified pointer"); } // Copies of a SharedLocked share the same reader lock; the lock is released // only when the last copy goes away. Verified by polling try_lock() — it // must keep failing while any copy is alive. TEST(LockedTest, SharedAccessIsRefCounted) { llvm::sys::RWMutex mutex; Widget widget; std::optional> first; first.emplace(mutex, &widget); EXPECT_FALSE(mutex.try_lock()); std::optional> second = first; ASSERT_TRUE(second); EXPECT_FALSE(mutex.try_lock()); first.reset(); // Second copy still holds the reader lock. EXPECT_FALSE(mutex.try_lock()); second.reset(); // All copies gone — the writer lock is now obtainable. EXPECT_TRUE(mutex.try_lock()); mutex.unlock(); } TEST(LockedTest, SharedAcceptsExternallyAcquiredLock) { llvm::sys::RWMutex mutex; Widget widget; std::shared_lock lock(mutex); SharedLockedPtr handle(std::move(lock), &widget); ASSERT_TRUE(handle); EXPECT_FALSE(mutex.try_lock()); } TEST(LockedTest, SharedMoveNullsSource) { llvm::sys::RWMutex mutex; Widget widget; SharedLockedPtr first(mutex, &widget); SharedLockedPtr second = std::move(first); EXPECT_TRUE(second); EXPECT_EQ(second.get(), &widget); EXPECT_FALSE(first); EXPECT_EQ(first.get(), nullptr); } TEST(LockedTest, ExclusiveAccessOnRWMutex) { llvm::sys::RWMutex mutex; Widget widget; LockedPtr writer(mutex, &widget); ASSERT_TRUE(writer); writer->value = 11; EXPECT_EQ(widget.value, 11); }