[libcxx-commits] [libcxx] [libcxx][test-support] Improve thread_unsafe_shared_ptr (PR #195932)

Nikita Belenkiy via libcxx-commits libcxx-commits at lists.llvm.org
Wed May 6 11:46:22 PDT 2026


https://github.com/kitsnet updated https://github.com/llvm/llvm-project/pull/195932

>From 5d0eb5b3c2d0584f1e70db5bcbd6b243565d5002 Mon Sep 17 00:00:00 2001
From: Nikita Belenkiy <nikita.nb.belenkiy at partner.bmw.de>
Date: Tue, 5 May 2026 22:17:44 +0200
Subject: [PATCH] [libcxx][test-support] Improve thread_unsafe_shared_ptr

Adapting test support class thread_unsafe_shared_ptr for future use
with fancy_pointer_allocator, in particular, in constexpr contexts:

* Implementing the rule of 5;
* Annotating noexcept methods (and removing the workaround in the respective test);
* Making deallocation of its control block ignored by DisableAllocationGuard;
* Adding workaround for (expected later) module export problem (https://llvm.org/PR120108)
---
 .../string.cons/move_noexcept.pass.cpp        |  4 --
 libcxx/test/support/module.modulemap          |  5 +-
 libcxx/test/support/test_allocator.h          | 60 +++++++++++++++----
 3 files changed, 52 insertions(+), 17 deletions(-)

diff --git a/libcxx/test/std/strings/basic.string/string.cons/move_noexcept.pass.cpp b/libcxx/test/std/strings/basic.string/string.cons/move_noexcept.pass.cpp
index d807cac2c72c8..89502f6652655 100644
--- a/libcxx/test/std/strings/basic.string/string.cons/move_noexcept.pass.cpp
+++ b/libcxx/test/std/strings/basic.string/string.cons/move_noexcept.pass.cpp
@@ -32,11 +32,7 @@ int main(int, char**) {
   }
   {
     typedef std::basic_string<char, std::char_traits<char>, limited_allocator<char, 10>> C;
-#if TEST_STD_VER <= 14
-    static_assert(!std::is_nothrow_move_constructible<C>::value, "");
-#else
     static_assert(std::is_nothrow_move_constructible<C>::value, "");
-#endif
   }
 
   return 0;
diff --git a/libcxx/test/support/module.modulemap b/libcxx/test/support/module.modulemap
index 0af147d75ee5d..07acbb6b2e2fd 100644
--- a/libcxx/test/support/module.modulemap
+++ b/libcxx/test/support/module.modulemap
@@ -5,7 +5,10 @@ module test_config {
 
 module test {
   module double_move_tracker    { header "double_move_tracker.h" }
-  module test_allocator         { header "test_allocator.h" }
+  module test_allocator {
+    header "test_allocator.h"
+    export * // TODO: Workaround for https://llvm.org/PR120108
+  }
   module test_iterators         { header "test_iterators.h" }
   module type_algorithms        { header "type_algorithms.h" }
 }
diff --git a/libcxx/test/support/test_allocator.h b/libcxx/test/support/test_allocator.h
index f8b622d7f9520..1d05169e1df90 100644
--- a/libcxx/test/support/test_allocator.h
+++ b/libcxx/test/support/test_allocator.h
@@ -393,21 +393,30 @@ namespace detail {
 template <class T>
 class thread_unsafe_shared_ptr {
 public:
-  thread_unsafe_shared_ptr() = default;
-
-  TEST_CONSTEXPR_CXX14 thread_unsafe_shared_ptr(const thread_unsafe_shared_ptr& other) : block(other.block) {
+  // as it's internal and technically not nullable, we don't care about null pointer state
+  // and don't need destructive move
+  TEST_CONSTEXPR_CXX14 thread_unsafe_shared_ptr(const thread_unsafe_shared_ptr& other) TEST_NOEXCEPT
+      : block(other.block) {
     ++block->ref_count;
   }
-
-  TEST_CONSTEXPR_CXX20 ~thread_unsafe_shared_ptr() {
-    --block->ref_count;
-    if (block->ref_count != 0)
-      return;
-    typedef std::allocator_traits<std::allocator<control_block> > allocator_traits;
-    std::allocator<control_block> alloc;
-    allocator_traits::destroy(alloc, block);
-    allocator_traits::deallocate(alloc, block, 1);
+  TEST_CONSTEXPR_CXX14 thread_unsafe_shared_ptr(thread_unsafe_shared_ptr&& other) TEST_NOEXCEPT : block(other.block) {
+    ++block->ref_count;
   }
+  TEST_CONSTEXPR_CXX14 thread_unsafe_shared_ptr& operator=(const thread_unsafe_shared_ptr& other) TEST_NOEXCEPT {
+    // self-assignment safe order
+    ++other.block->ref_count;
+    detach_control_block();
+    block = other.block;
+    return *this;
+  }
+  TEST_CONSTEXPR_CXX20 thread_unsafe_shared_ptr& operator=(thread_unsafe_shared_ptr&& other) TEST_NOEXCEPT {
+    // self-assignment safe order
+    ++other.block->ref_count;
+    detach_control_block();
+    block = other.block;
+    return *this;
+  }
+  TEST_CONSTEXPR_CXX20 ~thread_unsafe_shared_ptr() TEST_NOEXCEPT { detach_control_block(); }
 
   TEST_CONSTEXPR const T& operator*() const { return block->content; }
   TEST_CONSTEXPR const T* operator->() const { return &block->content; }
@@ -424,6 +433,33 @@ class thread_unsafe_shared_ptr {
     T content;
   };
 
+  thread_unsafe_shared_ptr() = default;
+
+  TEST_CONSTEXPR_CXX20 void detach_control_block() {
+    --block->ref_count;
+    if (block->ref_count != 0)
+      return;
+    typedef std::allocator_traits<std::allocator<control_block> > allocator_traits;
+    std::allocator<control_block> alloc;
+    allocator_traits::destroy(alloc, block);
+#ifdef COUNT_NEW_H
+    // We are called from test code instrumented by counting operator new/operator delete,
+    // which might be surprised by unexpected deallocations we introduce.
+    // Instead of changing all the potentially affected tests, we will just temporarily
+    // disable that counting here.
+    bool disable_allocations = false;
+    if (!TEST_IS_CONSTANT_EVALUATED) {
+      disable_allocations                  = globalMemCounter.disable_allocations;
+      globalMemCounter.disable_allocations = false;
+    }
+#endif
+    allocator_traits::deallocate(alloc, block, 1);
+#ifdef COUNT_NEW_H
+    if (!TEST_IS_CONSTANT_EVALUATED)
+      globalMemCounter.disable_allocations = disable_allocations;
+#endif
+  }
+
   control_block* block = nullptr;
 
   template <class U, class... Args>



More information about the libcxx-commits mailing list