[libcxx-commits] [libcxx] [libc++][ThreadSafety] Add thread safety annotations for variadic std::scoped_lock (PR #204462)
via libcxx-commits
libcxx-commits at lists.llvm.org
Wed Jun 24 09:52:07 PDT 2026
https://github.com/AnthonyCalandraGeotab updated https://github.com/llvm/llvm-project/pull/204462
>From d65e8acf382c52c108a99a9ba685e33bd231d623 Mon Sep 17 00:00:00 2001
From: Anthony Calandra <anthony at anthony-calandra.com>
Date: Wed, 17 Jun 2026 17:05:52 -0400
Subject: [PATCH] [libc++][ThreadSafety] Add thread safety annotations for
variadic std::scoped_lock
The thread safety annotations on std::scoped_lock were previously only
applied to the empty and single-mutex specializations. The general
variadic specialization carried no annotations, so -Wthread-safety
considered none of the mutexes held inside a multi-mutex scoped_lock
block and reported the guarded data accessed there as unprotected.
Now that Clang supports pack expansion inside thread safety attributes
(landed for Clang 21), annotate the variadic scoped_lock with
acquire_capability/requires_capability on its constructors,
release_capability on its destructor, and scoped_lockable on the class.
Fixes #42000.
---
libcxx/docs/ReleaseNotes/23.rst | 3 +
libcxx/include/__configuration/attributes.h | 6 +-
libcxx/include/__mutex/lock_guard.h | 2 +-
libcxx/include/__mutex/mutex.h | 2 +-
libcxx/include/mutex | 18 +++--
libcxx/include/shared_mutex | 4 +-
.../thread_safety_scoped_lock.pass.cpp | 71 +++++++++++++++++++
7 files changed, 93 insertions(+), 13 deletions(-)
create mode 100644 libcxx/test/extensions/clang/thread/thread.mutex/thread_safety_scoped_lock.pass.cpp
diff --git a/libcxx/docs/ReleaseNotes/23.rst b/libcxx/docs/ReleaseNotes/23.rst
index 7823c2db39867..ffb317cdb86b9 100644
--- a/libcxx/docs/ReleaseNotes/23.rst
+++ b/libcxx/docs/ReleaseNotes/23.rst
@@ -65,6 +65,9 @@ Improvements and New Features
``"std::visit: variant is valueless"``, ``"std::get: variant is valueless"``, or
``"std::get: wrong alternative for variant"``. The standard only requires ``what()`` to return an
unspecified non-null string, so user code that does not match on the exact message remains correct.
+- ``std::scoped_lock`` now carries Clang thread safety annotations when locking multiple mutexes, so
+ ``-Wthread-safety`` no longer incorrectly reports the guarded data accessed inside the block as
+ unprotected. This requires Clang 21 or later.
Deprecations and Removals
-------------------------
diff --git a/libcxx/include/__configuration/attributes.h b/libcxx/include/__configuration/attributes.h
index cc828466482fd..5ec26934992ae 100644
--- a/libcxx/include/__configuration/attributes.h
+++ b/libcxx/include/__configuration/attributes.h
@@ -333,10 +333,10 @@
# define _LIBCPP_TRY_ACQUIRE_SHARED_CAPABILITY(...)
#endif
-#if __has_cpp_attribute(_Clang::__release_capability__)
-# define _LIBCPP_RELEASE_CAPABILITY [[_Clang::__release_capability__]]
+#if __has_attribute(__release_capability__)
+# define _LIBCPP_RELEASE_CAPABILITY(...) __attribute__((__release_capability__(__VA_ARGS__)))
#else
-# define _LIBCPP_RELEASE_CAPABILITY
+# define _LIBCPP_RELEASE_CAPABILITY(...)
#endif
#if __has_cpp_attribute(_Clang::__release_shared_capability__)
diff --git a/libcxx/include/__mutex/lock_guard.h b/libcxx/include/__mutex/lock_guard.h
index 67a25ed2f8a89..29022e8c23ece 100644
--- a/libcxx/include/__mutex/lock_guard.h
+++ b/libcxx/include/__mutex/lock_guard.h
@@ -34,7 +34,7 @@ class _LIBCPP_SCOPED_LOCKABLE lock_guard {
[[__nodiscard__]] _LIBCPP_HIDE_FROM_ABI lock_guard(mutex_type& __m, adopt_lock_t) _LIBCPP_REQUIRES_CAPABILITY(__m)
: __m_(__m) {}
- _LIBCPP_RELEASE_CAPABILITY _LIBCPP_HIDE_FROM_ABI ~lock_guard() { __m_.unlock(); }
+ _LIBCPP_RELEASE_CAPABILITY() _LIBCPP_HIDE_FROM_ABI ~lock_guard() { __m_.unlock(); }
lock_guard(lock_guard const&) = delete;
lock_guard& operator=(lock_guard const&) = delete;
diff --git a/libcxx/include/__mutex/mutex.h b/libcxx/include/__mutex/mutex.h
index d0addacc2893f..f71cba03cd4dc 100644
--- a/libcxx/include/__mutex/mutex.h
+++ b/libcxx/include/__mutex/mutex.h
@@ -39,7 +39,7 @@ class _LIBCPP_EXPORTED_FROM_ABI _LIBCPP_CAPABILITY("mutex") mutex {
_LIBCPP_ACQUIRE_CAPABILITY() void lock();
[[__nodiscard__]] _LIBCPP_TRY_ACQUIRE_CAPABILITY(true) bool try_lock() _NOEXCEPT;
- _LIBCPP_RELEASE_CAPABILITY void unlock() _NOEXCEPT;
+ _LIBCPP_RELEASE_CAPABILITY() void unlock() _NOEXCEPT;
typedef __libcpp_mutex_t* native_handle_type;
[[__nodiscard__]] _LIBCPP_HIDE_FROM_ABI native_handle_type native_handle() { return &__m_; }
diff --git a/libcxx/include/mutex b/libcxx/include/mutex
index ab505f945d63d..fc73bed0b1007 100644
--- a/libcxx/include/mutex
+++ b/libcxx/include/mutex
@@ -458,7 +458,7 @@ public:
__m_.lock();
}
- _LIBCPP_RELEASE_CAPABILITY _LIBCPP_HIDE_FROM_ABI ~scoped_lock() { __m_.unlock(); }
+ _LIBCPP_RELEASE_CAPABILITY() _LIBCPP_HIDE_FROM_ABI ~scoped_lock() { __m_.unlock(); }
[[nodiscard]]
_LIBCPP_HIDE_FROM_ABI explicit scoped_lock(adopt_lock_t, mutex_type& __m) _LIBCPP_REQUIRES_CAPABILITY(__m)
@@ -469,25 +469,31 @@ public:
};
template <class... _MArgs>
-class scoped_lock {
+class _LIBCPP_SCOPED_LOCKABLE scoped_lock {
static_assert(sizeof...(_MArgs) > 1, "At least 2 lock types required");
typedef tuple<_MArgs&...> _MutexTuple;
public:
- [[nodiscard]] _LIBCPP_HIDE_FROM_ABI explicit scoped_lock(_MArgs&... __margs) : __t_(__margs...) {
+ [[nodiscard]] _LIBCPP_HIDE_FROM_ABI explicit scoped_lock(_MArgs&... __margs) _LIBCPP_ACQUIRE_CAPABILITY(__margs...)
+ : __t_(__margs...) {
std::lock(__margs...);
}
- [[nodiscard]] _LIBCPP_HIDE_FROM_ABI scoped_lock(adopt_lock_t, _MArgs&... __margs) : __t_(__margs...) {}
+ [[nodiscard]] _LIBCPP_HIDE_FROM_ABI scoped_lock(adopt_lock_t, _MArgs&... __margs)
+ _LIBCPP_REQUIRES_CAPABILITY(__margs...)
+ : __t_(__margs...) {}
- _LIBCPP_HIDE_FROM_ABI ~scoped_lock() { __unlock_unpack(make_index_sequence<sizeof...(_MArgs)>(), __t_); }
+ _LIBCPP_RELEASE_CAPABILITY() _LIBCPP_HIDE_FROM_ABI ~scoped_lock() {
+ __unlock_unpack(make_index_sequence<sizeof...(_MArgs)>(), __t_);
+ }
scoped_lock(scoped_lock const&) = delete;
scoped_lock& operator=(scoped_lock const&) = delete;
private:
template <size_t... _Indx>
- _LIBCPP_HIDE_FROM_ABI static void __unlock_unpack(index_sequence<_Indx...>, _MutexTuple& __mt) {
+ _LIBCPP_HIDE_FROM_ABI static void __unlock_unpack(index_sequence<_Indx...>, _MutexTuple& __mt)
+ _LIBCPP_RELEASE_CAPABILITY(std::get<_Indx>(__mt)...) {
(std::get<_Indx>(__mt).unlock(), ...);
}
diff --git a/libcxx/include/shared_mutex b/libcxx/include/shared_mutex
index 9479f0aa616f8..8fc89fe1cb97c 100644
--- a/libcxx/include/shared_mutex
+++ b/libcxx/include/shared_mutex
@@ -200,7 +200,7 @@ public:
[[nodiscard]] _LIBCPP_TRY_ACQUIRE_CAPABILITY(true) _LIBCPP_HIDE_FROM_ABI bool try_lock() {
return __base_.try_lock();
}
- _LIBCPP_RELEASE_CAPABILITY _LIBCPP_HIDE_FROM_ABI void unlock() { return __base_.unlock(); }
+ _LIBCPP_RELEASE_CAPABILITY() _LIBCPP_HIDE_FROM_ABI void unlock() { return __base_.unlock(); }
// Shared ownership
_LIBCPP_ACQUIRE_SHARED_CAPABILITY _LIBCPP_HIDE_FROM_ABI void lock_shared() { return __base_.lock_shared(); }
@@ -262,7 +262,7 @@ public:
return true;
}
- _LIBCPP_RELEASE_CAPABILITY void unlock();
+ _LIBCPP_RELEASE_CAPABILITY() void unlock();
// Shared ownership
_LIBCPP_ACQUIRE_SHARED_CAPABILITY void lock_shared();
diff --git a/libcxx/test/extensions/clang/thread/thread.mutex/thread_safety_scoped_lock.pass.cpp b/libcxx/test/extensions/clang/thread/thread.mutex/thread_safety_scoped_lock.pass.cpp
new file mode 100644
index 0000000000000..898659c37d81b
--- /dev/null
+++ b/libcxx/test/extensions/clang/thread/thread.mutex/thread_safety_scoped_lock.pass.cpp
@@ -0,0 +1,71 @@
+//===----------------------------------------------------------------------===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+
+// UNSUPPORTED: no-threads, c++03, c++11, c++14
+
+// <mutex>
+
+// GCC doesn't have thread safety attributes
+// UNSUPPORTED: gcc
+
+// ADDITIONAL_COMPILE_FLAGS: -Wthread-safety
+
+#include <mutex>
+
+#include "test_macros.h"
+
+std::mutex m1;
+std::mutex m2;
+int data1 __attribute__((guarded_by(m1)));
+int data2 __attribute__((guarded_by(m2)));
+int data3;
+
+static void no_mutex() {
+ std::scoped_lock<> lock;
+ data3++;
+}
+
+static void one_mutex() {
+ std::scoped_lock<std::mutex> lock(m1);
+ data1++;
+}
+
+static void two_mutexes() {
+ std::scoped_lock<std::mutex, std::mutex> lock(m1, m2);
+ data1++;
+ data2++;
+}
+
+static void adopt_none() {
+ std::scoped_lock<> lock(std::adopt_lock);
+ data3++;
+}
+
+static void adopt_one() {
+ m1.lock();
+ std::scoped_lock<std::mutex> lock(std::adopt_lock, m1);
+ data1++;
+}
+
+static void adopt_two() {
+ m1.lock();
+ m2.lock();
+ std::scoped_lock<std::mutex, std::mutex> lock(std::adopt_lock, m1, m2);
+ data1++;
+ data2++;
+}
+
+int main(int, char**) {
+ no_mutex();
+ one_mutex();
+ two_mutexes();
+ adopt_none();
+ adopt_one();
+ adopt_two();
+ return 0;
+}
More information about the libcxx-commits
mailing list