[Lldb-commits] [lldb] [lldb] Add Locked<T> handle and locking accessors on Module (PR #198941)

Jonas Devlieghere via lldb-commits lldb-commits at lists.llvm.org
Thu May 21 09:18:29 PDT 2026


https://github.com/JDevlieghere updated https://github.com/llvm/llvm-project/pull/198941

>From 2e723106069b563bb2e769161352bf0bdb6ce227 Mon Sep 17 00:00:00 2001
From: Jonas Devlieghere <jonas at devlieghere.com>
Date: Thu, 21 May 2026 09:16:54 -0700
Subject: [PATCH] [lldb] Add Locked<T> and SharedLocked<T> RAII handles in
 Utility
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Introduce two RAII handles that pair a pointer-like value with a lock on
a caller-supplied mutex.

  - Locked<PtrT, Mutex>: holds std::unique_lock<Mutex>. Move-only;
    enforces exclusive access. Defaults to std::recursive_mutex to match
    LLDB's existing synchronization style.

  - SharedLocked<PtrT, Mutex>: holds a reference-counted
    std::shared_lock<Mutex>. Copyable — copies share the same reader
    lock and the lock releases when the last copy goes away. Defaults
    to llvm::sys::RWMutex, the LLDB convention.

PtrT may be a raw pointer, std::shared_ptr, or std::unique_ptr;
convenience aliases LockedPtr/LockedSP/LockedUP and
SharedLockedPtr/SharedLockedSP/SharedLockedUP cover the common
combinations. SharedLocked's borrowed pointer is const-qualified so
readers can't mutate the pointee.
---
 lldb/include/lldb/Utility/Locked.h    | 142 ++++++++++++++++++
 lldb/unittests/Utility/CMakeLists.txt |   1 +
 lldb/unittests/Utility/LockedTest.cpp | 208 ++++++++++++++++++++++++++
 3 files changed, 351 insertions(+)
 create mode 100644 lldb/include/lldb/Utility/Locked.h
 create mode 100644 lldb/unittests/Utility/LockedTest.cpp

diff --git a/lldb/include/lldb/Utility/Locked.h b/lldb/include/lldb/Utility/Locked.h
new file mode 100644
index 0000000000000..2e8488df3b0e7
--- /dev/null
+++ b/lldb/include/lldb/Utility/Locked.h
@@ -0,0 +1,142 @@
+//===-- Locked.h ------------------------------------------------*- C++ -*-===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLDB_UTILITY_LOCKED_H
+#define LLDB_UTILITY_LOCKED_H
+
+#include "llvm/Support/RWMutex.h"
+
+#include <memory>
+#include <mutex>
+#include <shared_mutex>
+#include <type_traits>
+#include <utility>
+
+namespace lldb_private {
+
+namespace detail {
+/// Common pointer-like accessors shared by `Locked` and `SharedLocked`.
+template <typename Derived, typename PtrT> class LockedAccessors {
+public:
+  auto operator->() const { return Raw(); }
+  decltype(auto) operator*() const { return *Self()->m_ptr; }
+  auto get() const { return Raw(); }
+  explicit operator bool() const { return Raw() != nullptr; }
+
+private:
+  const Derived *Self() const { return static_cast<const Derived *>(this); }
+
+  auto Raw() const {
+    if constexpr (std::is_pointer_v<PtrT>)
+      return Self()->m_ptr;
+    else
+      return Self()->m_ptr.get();
+  }
+};
+} // namespace detail
+
+/// A move-only RAII handle that pairs a pointer-like value with an exclusive
+/// lock on a caller-supplied mutex. While the handle is alive the borrowed
+/// pointer is serialized against other threads that go through the same
+/// mutex.
+///
+/// `PtrT` is the pointer-like value: a raw pointer (`T*`),
+/// `std::shared_ptr<T>`, or `std::unique_ptr<T>`. `Mutex` may be any type
+/// that satisfies `Lockable` — `std::mutex`, `std::recursive_mutex`,
+/// `std::shared_mutex`, or `llvm::sys::RWMutex` all work. Use the
+/// `LockedPtr`, `LockedSP`, `LockedUP` aliases for the common combinations.
+template <typename PtrT, typename Mutex = std::recursive_mutex>
+class Locked : public detail::LockedAccessors<Locked<PtrT, Mutex>, PtrT> {
+  friend class detail::LockedAccessors<Locked<PtrT, Mutex>, PtrT>;
+
+public:
+  using mutex_type = Mutex;
+  using lock_type = std::unique_lock<Mutex>;
+
+  Locked() = default;
+  Locked(mutex_type &m, PtrT p) : m_lock(m), m_ptr(std::move(p)) {}
+  Locked(lock_type lock, PtrT p)
+      : m_lock(std::move(lock)), m_ptr(std::move(p)) {}
+
+  Locked(Locked &&) noexcept = default;
+  Locked &operator=(Locked &&) noexcept = default;
+  Locked(const Locked &) = delete;
+  Locked &operator=(const Locked &) = delete;
+
+private:
+  lock_type m_lock;
+  PtrT m_ptr{};
+};
+
+/// A copyable RAII handle that pairs a pointer-like value with a shared
+/// (reader) lock on a caller-supplied mutex. Copies share the same
+/// underlying reader lock through reference counting; the lock is released
+/// when the last copy is destroyed. This makes a `SharedLocked` cheap to
+/// pass through code paths that branch or fan out without each leaf having
+/// to re-acquire.
+///
+/// The borrowed pointer is `const`-qualified so callers cannot mutate the
+/// pointee while holding only a reader's lock. `Mutex` must satisfy
+/// `SharedLockable` — `llvm::sys::RWMutex` (the LLDB convention) or
+/// `std::shared_mutex`. Use the `SharedLockedPtr`, `SharedLockedSP`,
+/// `SharedLockedUP` aliases for the common combinations.
+template <typename PtrT, typename Mutex = llvm::sys::RWMutex>
+class SharedLocked
+    : public detail::LockedAccessors<SharedLocked<PtrT, Mutex>, PtrT> {
+  friend class detail::LockedAccessors<SharedLocked<PtrT, Mutex>, PtrT>;
+
+public:
+  using mutex_type = Mutex;
+  using lock_type = std::shared_lock<Mutex>;
+
+  SharedLocked() = default;
+  SharedLocked(mutex_type &m, PtrT p)
+      : m_lock(std::make_shared<lock_type>(m)), m_ptr(std::move(p)) {}
+  SharedLocked(lock_type lock, PtrT p)
+      : m_lock(std::make_shared<lock_type>(std::move(lock))),
+        m_ptr(std::move(p)) {}
+
+  SharedLocked(const SharedLocked &) = default;
+  SharedLocked &operator=(const SharedLocked &) = default;
+  SharedLocked(SharedLocked &&) noexcept = default;
+  SharedLocked &operator=(SharedLocked &&) noexcept = default;
+
+private:
+  std::shared_ptr<lock_type> m_lock;
+  PtrT m_ptr{};
+};
+
+/// Exclusive (write) access aliases. The default mutex is
+/// `std::recursive_mutex` to match the existing LLDB synchronization style.
+/// @{
+template <typename T, typename Mutex = std::recursive_mutex>
+using LockedPtr = Locked<T *, Mutex>;
+
+template <typename T, typename Mutex = std::recursive_mutex>
+using LockedSP = Locked<std::shared_ptr<T>, Mutex>;
+
+template <typename T, typename Mutex = std::recursive_mutex>
+using LockedUP = Locked<std::unique_ptr<T>, Mutex>;
+/// @}
+
+/// Shared (read) access aliases. The default mutex is `llvm::sys::RWMutex`,
+/// the LLDB convention for read/write locks.
+/// @{
+template <typename T, typename Mutex = llvm::sys::RWMutex>
+using SharedLockedPtr = SharedLocked<const T *, Mutex>;
+
+template <typename T, typename Mutex = llvm::sys::RWMutex>
+using SharedLockedSP = SharedLocked<std::shared_ptr<const T>, Mutex>;
+
+template <typename T, typename Mutex = llvm::sys::RWMutex>
+using SharedLockedUP = SharedLocked<std::unique_ptr<const T>, Mutex>;
+/// @}
+
+} // namespace lldb_private
+
+#endif // LLDB_UTILITY_LOCKED_H
diff --git a/lldb/unittests/Utility/CMakeLists.txt b/lldb/unittests/Utility/CMakeLists.txt
index 9970c2f36e440..56b3c86fa7060 100644
--- a/lldb/unittests/Utility/CMakeLists.txt
+++ b/lldb/unittests/Utility/CMakeLists.txt
@@ -16,6 +16,7 @@ add_lldb_unittest(UtilityTests
   FileSpecTest.cpp
   FlagsTest.cpp
   ListenerTest.cpp
+  LockedTest.cpp
   LogTest.cpp
   NameMatchesTest.cpp
   NonNullSharedPtrTest.cpp
diff --git a/lldb/unittests/Utility/LockedTest.cpp b/lldb/unittests/Utility/LockedTest.cpp
new file mode 100644
index 0000000000000..ebbbf2de3d2ea
--- /dev/null
+++ b/lldb/unittests/Utility/LockedTest.cpp
@@ -0,0 +1,208 @@
+//===-- LockedTest.cpp ----------------------------------------------------===//
+//
+// 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 <memory>
+#include <mutex>
+#include <shared_mutex>
+#include <type_traits>
+
+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<LockedPtr<Widget>::mutex_type,
+                             std::recursive_mutex>);
+static_assert(std::is_same_v<LockedSP<Widget>::mutex_type,
+                             std::recursive_mutex>);
+static_assert(std::is_same_v<LockedUP<Widget>::mutex_type,
+                             std::recursive_mutex>);
+static_assert(std::is_same_v<SharedLockedPtr<Widget>::mutex_type,
+                             llvm::sys::RWMutex>);
+static_assert(std::is_same_v<SharedLockedSP<Widget>::mutex_type,
+                             llvm::sys::RWMutex>);
+static_assert(std::is_same_v<SharedLockedUP<Widget>::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<Widget *, std::mutex>;
+template class lldb_private::Locked<Widget *, std::recursive_mutex>;
+template class lldb_private::Locked<Widget *, llvm::sys::RWMutex>;
+template class lldb_private::Locked<std::shared_ptr<Widget>,
+                                    std::recursive_mutex>;
+template class lldb_private::Locked<std::unique_ptr<Widget>,
+                                    std::recursive_mutex>;
+template class lldb_private::SharedLocked<const Widget *, std::shared_mutex>;
+template class lldb_private::SharedLocked<const Widget *, llvm::sys::RWMutex>;
+template class lldb_private::SharedLocked<std::shared_ptr<const Widget>,
+                                          llvm::sys::RWMutex>;
+template class lldb_private::SharedLocked<std::unique_ptr<const Widget>,
+                                          llvm::sys::RWMutex>;
+
+// Locked is move-only; SharedLocked is copyable.
+static_assert(!std::is_copy_constructible_v<LockedPtr<Widget>>);
+static_assert(!std::is_copy_assignable_v<LockedPtr<Widget>>);
+static_assert(std::is_move_constructible_v<LockedPtr<Widget>>);
+static_assert(std::is_move_assignable_v<LockedPtr<Widget>>);
+
+static_assert(std::is_copy_constructible_v<SharedLockedPtr<Widget>>);
+static_assert(std::is_copy_assignable_v<SharedLockedPtr<Widget>>);
+static_assert(std::is_move_constructible_v<SharedLockedPtr<Widget>>);
+static_assert(std::is_move_assignable_v<SharedLockedPtr<Widget>>);
+
+TEST(LockedTest, DefaultConstructed) {
+  LockedPtr<Widget> handle;
+  EXPECT_FALSE(handle);
+  EXPECT_EQ(handle.get(), nullptr);
+
+  SharedLockedPtr<Widget> shared;
+  EXPECT_FALSE(shared);
+  EXPECT_EQ(shared.get(), nullptr);
+}
+
+TEST(LockedTest, ExclusivePtrAccess) {
+  std::recursive_mutex mutex;
+  Widget widget;
+
+  LockedPtr<Widget> handle(mutex, &widget);
+  ASSERT_TRUE(handle);
+  EXPECT_EQ(handle.get(), &widget);
+
+  // Recursive mutex lets us reacquire on the same thread, proving the lock
+  // really is held.
+  EXPECT_TRUE(mutex.try_lock());
+  mutex.unlock();
+
+  handle->value = 7;
+  EXPECT_EQ((*handle).value, 7);
+}
+
+TEST(LockedTest, ExclusiveReleasesOnDestruction) {
+  std::mutex mutex;
+  Widget widget;
+  {
+    LockedPtr<Widget, std::mutex> handle(mutex, &widget);
+    EXPECT_FALSE(mutex.try_lock());
+  }
+  EXPECT_TRUE(mutex.try_lock());
+  mutex.unlock();
+}
+
+TEST(LockedTest, MoveTransfersLock) {
+  std::mutex mutex;
+  Widget widget;
+
+  LockedPtr<Widget, std::mutex> first(mutex, &widget);
+  EXPECT_FALSE(mutex.try_lock());
+
+  LockedPtr<Widget, std::mutex> second = std::move(first);
+  EXPECT_FALSE(mutex.try_lock());
+  EXPECT_TRUE(second);
+  EXPECT_EQ(second.get(), &widget);
+}
+
+TEST(LockedTest, AcceptsExternallyAcquiredLock) {
+  std::mutex mutex;
+  Widget widget;
+
+  std::unique_lock<std::mutex> lock(mutex);
+  LockedPtr<Widget, std::mutex> 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>();
+  widget_sp->value = 42;
+
+  LockedSP<Widget> 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>();
+  widget_up->value = 99;
+  Widget *raw = widget_up.get();
+
+  LockedUP<Widget> 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<Widget> reader(mutex, &widget);
+  ASSERT_TRUE(reader);
+  EXPECT_EQ(reader->value, 5);
+
+  static_assert(
+      std::is_same_v<decltype(reader.get()), const Widget *>,
+      "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<SharedLockedPtr<Widget>> first;
+  first.emplace(mutex, &widget);
+  EXPECT_FALSE(mutex.try_lock());
+
+  std::optional<SharedLockedPtr<Widget>> 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<llvm::sys::RWMutex> lock(mutex);
+  SharedLockedPtr<Widget> handle(std::move(lock), &widget);
+  ASSERT_TRUE(handle);
+  EXPECT_FALSE(mutex.try_lock());
+}
+
+TEST(LockedTest, ExclusiveAccessOnRWMutex) {
+  llvm::sys::RWMutex mutex;
+  Widget widget;
+  LockedPtr<Widget, llvm::sys::RWMutex> writer(mutex, &widget);
+  ASSERT_TRUE(writer);
+  writer->value = 11;
+  EXPECT_EQ(widget.value, 11);
+}



More information about the lldb-commits mailing list