[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