[libcxx-commits] [libcxx] [libc++][test] Fix `throwing_allocator` and improve related tests (PR #212673)

via libcxx-commits libcxx-commits at lists.llvm.org
Tue Jul 28 20:05:50 PDT 2026


llvmorg-github-actions[bot] wrote:


<!--LLVM PR SUMMARY COMMENT-->

@llvm/pr-subscribers-libcxx

Author: A. Jiang (frederick-vs-ja)

<details>
<summary>Changes</summary>

According to [allocator.requirements.general], copy and rebinding construction operations of allocators are required to be non-throwing. However, `throwing_allocator` in `libcxx/test/std/containers/sequences/vector/common.h` violated these requirements.

This patch makes corresponding constructors of `throwing_allocator` never throw any exception as required by the standard. As a result, in order to test exceptions thrown in existing test cases,
- exceptions need to be thrown from allocations, and
- for the `vector(vector&&, const Alloc&)` overload, it is necessary to make allocators not always equal to ensure another allocation to happen.

Also, `operator!=` needs to be provided in pre-C++20 modes to make `a1 != a2` valid.

This patch also adds `assert(false);` after operations throwing exceptions to ensure that exceptions are actually thrown.

---
Full diff: https://github.com/llvm/llvm-project/pull/212673.diff


3 Files Affected:

- (modified) libcxx/test/std/containers/sequences/vector.bool/ctor_exceptions.pass.cpp (+26-11) 
- (modified) libcxx/test/std/containers/sequences/vector/common.h (+22-12) 
- (modified) libcxx/test/std/containers/sequences/vector/vector.cons/exceptions.pass.cpp (+23-4) 


``````````diff
diff --git a/libcxx/test/std/containers/sequences/vector.bool/ctor_exceptions.pass.cpp b/libcxx/test/std/containers/sequences/vector.bool/ctor_exceptions.pass.cpp
index cb2b7d124edc1..c4245b74ca89b 100644
--- a/libcxx/test/std/containers/sequences/vector.bool/ctor_exceptions.pass.cpp
+++ b/libcxx/test/std/containers/sequences/vector.bool/ctor_exceptions.pass.cpp
@@ -11,6 +11,7 @@
 // (bug report: https://llvm.org/PR58392)
 // Check that vector<bool> constructors don't leak memory when an operation inside the constructor throws an exception
 
+#include <cassert>
 #include <cstddef>
 #include <memory>
 #include <type_traits>
@@ -25,22 +26,25 @@ int main(int, char**) {
 
   try { // Throw in vector() from allocator
     AllocVec vec;
+    assert(false);
   } catch (int) {
   }
   check_new_delete_called();
 
 #if TEST_STD_VER >= 14
   try { // Throw in vector(size_type, const allocator_type&) from allocator
-    throwing_allocator<bool> alloc(/*throw_on_ctor = */ false, /*throw_on_copy = */ true);
-    AllocVec get_alloc(0, alloc);
+    throwing_allocator<bool> alloc(/*throw_on_ctor = */ false, /*throw_on_allocation = */ true);
+    AllocVec get_alloc(1, alloc);
+    assert(false);
   } catch (int) {
   }
   check_new_delete_called();
 #endif // TEST_STD_VER >= 14
 
   try { // Throw in vector(size_type, const value_type&, const allocator_type&) from allocator
-    throwing_allocator<bool> alloc(/*throw_on_ctor = */ false, /*throw_on_copy = */ true);
-    AllocVec get_alloc(0, true, alloc);
+    throwing_allocator<bool> alloc(/*throw_on_ctor = */ false, /*throw_on_allocation = */ true);
+    AllocVec get_alloc(1, true, alloc);
+    assert(false);
   } catch (int) {
   }
   check_new_delete_called();
@@ -48,6 +52,7 @@ int main(int, char**) {
   try { // Throw in vector(InputIterator, InputIterator) from input iterator
     std::vector<bool> vec(
         throwing_iterator<bool, std::input_iterator_tag>(), throwing_iterator<bool, std::input_iterator_tag>(2));
+    assert(false);
   } catch (int) {
   }
   check_new_delete_called();
@@ -55,6 +60,7 @@ int main(int, char**) {
   try { // Throw in vector(InputIterator, InputIterator) from forward iterator
     std::vector<bool> vec(
         throwing_iterator<bool, std::forward_iterator_tag>(), throwing_iterator<bool, std::forward_iterator_tag>(2));
+    assert(false);
   } catch (int) {
   }
   check_new_delete_called();
@@ -62,6 +68,7 @@ int main(int, char**) {
   try { // Throw in vector(InputIterator, InputIterator) from allocator
     bool a[] = {true, true};
     AllocVec vec(cpp17_input_iterator<bool*>(a), cpp17_input_iterator<bool*>(a + 2));
+    assert(false);
   } catch (int) {
   }
   check_new_delete_called();
@@ -70,6 +77,7 @@ int main(int, char**) {
     std::allocator<bool> alloc;
     std::vector<bool> vec(
         throwing_iterator<bool, std::input_iterator_tag>(), throwing_iterator<bool, std::input_iterator_tag>(2), alloc);
+    assert(false);
   } catch (int) {
   }
   check_new_delete_called();
@@ -79,50 +87,57 @@ int main(int, char**) {
     std::vector<bool> vec(throwing_iterator<bool, std::forward_iterator_tag>(),
                           throwing_iterator<bool, std::forward_iterator_tag>(2),
                           alloc);
+    assert(false);
   } catch (int) {
   }
   check_new_delete_called();
 
   try { // Throw in vector(InputIterator, InputIterator, const allocator_type&) from allocator
     bool a[] = {true, false};
-    throwing_allocator<bool> alloc(/*throw_on_ctor = */ false, /*throw_on_copy = */ true);
+    throwing_allocator<bool> alloc(/*throw_on_ctor = */ false, /*throw_on_allocation = */ true);
     AllocVec vec(cpp17_input_iterator<bool*>(a), cpp17_input_iterator<bool*>(a + 2), alloc);
+    assert(false);
   } catch (int) {
   }
   check_new_delete_called();
 
   try { // Throw in vector(InputIterator, InputIterator, const allocator_type&) from allocator
     bool a[] = {true, false};
-    throwing_allocator<bool> alloc(/*throw_on_ctor = */ false, /*throw_on_copy = */ true);
+    throwing_allocator<bool> alloc(/*throw_on_ctor = */ false, /*throw_on_allocation = */ true);
     AllocVec vec(forward_iterator<bool*>(a), forward_iterator<bool*>(a + 2), alloc);
+    assert(false);
   } catch (int) {
   }
   check_new_delete_called();
 
 #if TEST_STD_VER >= 11
   try { // Throw in vector(const vector&, const allocator_type&) from allocator
-    throwing_allocator<bool> alloc(/*throw_on_ctor = */ false, /*throw_on_copy = */ false);
+    throwing_allocator<bool> alloc(/*throw_on_ctor = */ false, /*throw_on_allocation = */ false);
     AllocVec vec(alloc);
     vec.push_back(true);
-    alloc.throw_on_copy_ = true;
+    alloc.throw_on_allocation_ = true;
     AllocVec vec2(vec, alloc);
+    assert(false);
   } catch (int) {
   }
   check_new_delete_called();
 
   try { // Throw in vector(vector&&, const allocator_type&) from allocator
-    throwing_allocator<bool> alloc(/*throw_on_ctor = */ false, /*throw_on_copy = */ false);
+    throwing_allocator<bool> alloc(/*throw_on_ctor = */ false, /*throw_on_allocation = */ false);
     AllocVec vec(alloc);
     vec.push_back(true);
-    alloc.throw_on_copy_ = true;
+    alloc.throw_on_allocation_ = true;
+    alloc.payload_             = 42; // makes allocators not equal and enforces reallocation
     AllocVec vec2(std::move(vec), alloc);
+    assert(false);
   } catch (int) {
   }
   check_new_delete_called();
 
   try { // Throw in vector(initializer_list<value_type>, const allocator_type&) constructor from allocator
-    throwing_allocator<bool> alloc(/*throw_on_ctor = */ false, /*throw_on_copy = */ true);
+    throwing_allocator<bool> alloc(/*throw_on_ctor = */ false, /*throw_on_allocation = */ true);
     AllocVec vec({true, true}, alloc);
+    assert(false);
   } catch (int) {
   }
   check_new_delete_called();
diff --git a/libcxx/test/std/containers/sequences/vector/common.h b/libcxx/test/std/containers/sequences/vector/common.h
index 34453f8889b73..b0a39761cc357 100644
--- a/libcxx/test/std/containers/sequences/vector/common.h
+++ b/libcxx/test/std/containers/sequences/vector/common.h
@@ -147,36 +147,46 @@ template <class T>
 struct throwing_allocator {
   using value_type = T;
 
-  bool throw_on_copy_ = false;
+  bool throw_on_allocation_ = false;
+  int payload_              = 0;
 
   explicit throwing_allocator(bool throw_on_ctor = true) {
     if (throw_on_ctor)
       throw 0;
   }
 
-  explicit throwing_allocator(bool throw_on_ctor, bool throw_on_copy) : throw_on_copy_(throw_on_copy) {
+  explicit throwing_allocator(bool throw_on_ctor, bool throw_on_allocation)
+      : throw_on_allocation_(throw_on_allocation) {
     if (throw_on_ctor)
       throw 0;
   }
 
-  throwing_allocator(const throwing_allocator& rhs) : throw_on_copy_(rhs.throw_on_copy_) {
-    if (throw_on_copy_)
-      throw 0;
-  }
+  throwing_allocator(const throwing_allocator& rhs) TEST_NOEXCEPT
+      : throw_on_allocation_(rhs.throw_on_allocation_),
+        payload_(rhs.payload_) {}
 
   template <class U>
-  throwing_allocator(const throwing_allocator<U>& rhs) : throw_on_copy_(rhs.throw_on_copy_) {
-    if (throw_on_copy_)
+  throwing_allocator(const throwing_allocator<U>& rhs) TEST_NOEXCEPT
+      : throw_on_allocation_(rhs.throw_on_allocation_),
+        payload_(rhs.payload_) {}
+
+  T* allocate(std::size_t n) {
+    if (throw_on_allocation_)
       throw 0;
+    return std::allocator<T>().allocate(n);
   }
-
-  T* allocate(std::size_t n) { return std::allocator<T>().allocate(n); }
   void deallocate(T* ptr, std::size_t n) { std::allocator<T>().deallocate(ptr, n); }
 
   template <class U>
-  friend bool operator==(const throwing_allocator&, const throwing_allocator<U>&) {
-    return true;
+  friend bool operator==(const throwing_allocator& lhs, const throwing_allocator<U>& rhs) {
+    return lhs.payload_ == rhs.payload_;
   }
+#if TEST_STD_VER < 20
+  template <class U>
+  friend bool operator!=(const throwing_allocator& lhs, const throwing_allocator<U>& rhs) {
+    return !(lhs == rhs);
+  }
+#endif
 };
 
 template <class T, class IterCat>
diff --git a/libcxx/test/std/containers/sequences/vector/vector.cons/exceptions.pass.cpp b/libcxx/test/std/containers/sequences/vector/vector.cons/exceptions.pass.cpp
index 679eec2413793..000b95f74402b 100644
--- a/libcxx/test/std/containers/sequences/vector/vector.cons/exceptions.pass.cpp
+++ b/libcxx/test/std/containers/sequences/vector/vector.cons/exceptions.pass.cpp
@@ -11,6 +11,7 @@
 // (bug report: https://llvm.org/PR58392)
 // Check that vector constructors don't leak memory when an operation inside the constructor throws an exception
 
+#include <cassert>
 #include <cstddef>
 #include <memory>
 #include <type_traits>
@@ -25,12 +26,14 @@ int main(int, char**) {
   using AllocVec = std::vector<int, throwing_allocator<int> >;
   try { // vector()
     AllocVec vec;
+    assert(false);
   } catch (int) {
   }
   check_new_delete_called();
 
   try { // Throw in vector(size_type) from type
     std::vector<throwing_t> get_alloc(1);
+    assert(false);
   } catch (int) {
   }
   check_new_delete_called();
@@ -40,19 +43,22 @@ int main(int, char**) {
     int throw_after = 1;
     throwing_t v(throw_after);
     std::vector<throwing_t> get_alloc(1, v);
+    assert(false);
   } catch (int) {
   }
   check_new_delete_called();
 
   try { // Throw in vector(size_type, const allocator_type&) from allocator
-    throwing_allocator<int> alloc(/*throw_on_ctor = */ false, /*throw_on_copy = */ true);
-    AllocVec get_alloc(0, alloc);
+    throwing_allocator<int> alloc(/*throw_on_ctor = */ false, /*throw_on_allocation = */ true);
+    AllocVec get_alloc(1, alloc);
+    assert(false);
   } catch (int) {
   }
   check_new_delete_called();
 
   try { // Throw in vector(size_type, const allocator_type&) from the type
     std::vector<throwing_t> vec(1, std::allocator<throwing_t>());
+    assert(false);
   } catch (int) {
   }
   check_new_delete_called();
@@ -62,6 +68,7 @@ int main(int, char**) {
     int throw_after = 1;
     throwing_t v(throw_after);
     std::vector<throwing_t> vec(1, v, std::allocator<throwing_t>());
+    assert(false);
   } catch (int) {
   }
   check_new_delete_called();
@@ -69,6 +76,7 @@ int main(int, char**) {
   try { // Throw in vector(InputIterator, InputIterator) from input iterator
     std::vector<int> vec(
         (throwing_iterator<int, std::input_iterator_tag>()), throwing_iterator<int, std::input_iterator_tag>(2));
+    assert(false);
   } catch (int) {
   }
   check_new_delete_called();
@@ -76,6 +84,7 @@ int main(int, char**) {
   try { // Throw in vector(InputIterator, InputIterator) from forward iterator
     std::vector<int> vec(
         (throwing_iterator<int, std::forward_iterator_tag>()), throwing_iterator<int, std::forward_iterator_tag>(2));
+    assert(false);
   } catch (int) {
   }
   check_new_delete_called();
@@ -83,6 +92,7 @@ int main(int, char**) {
   try { // Throw in vector(InputIterator, InputIterator) from allocator
     int a[] = {1, 2};
     AllocVec vec(cpp17_input_iterator<int*>(a), cpp17_input_iterator<int*>(a + 2));
+    assert(false);
   } catch (int) {
   }
   check_new_delete_called();
@@ -91,6 +101,7 @@ int main(int, char**) {
     std::allocator<int> alloc;
     std::vector<int> vec(
         throwing_iterator<int, std::input_iterator_tag>(), throwing_iterator<int, std::input_iterator_tag>(2), alloc);
+    assert(false);
   } catch (int) {
   }
   check_new_delete_called();
@@ -100,22 +111,25 @@ int main(int, char**) {
     std::vector<int> vec(throwing_iterator<int, std::forward_iterator_tag>(),
                          throwing_iterator<int, std::forward_iterator_tag>(2),
                          alloc);
+    assert(false);
   } catch (int) {
   }
   check_new_delete_called();
 
   try { // Throw in vector(InputIterator, InputIterator, const allocator_type&) from allocator
     int a[] = {1, 2};
-    throwing_allocator<int> alloc(/*throw_on_ctor = */ false, /*throw_on_copy = */ true);
+    throwing_allocator<int> alloc(/*throw_on_ctor = */ false, /*throw_on_allocation = */ true);
     AllocVec vec(cpp17_input_iterator<int*>(a), cpp17_input_iterator<int*>(a + 2), alloc);
+    assert(false);
   } catch (int) {
   }
   check_new_delete_called();
 
   try { // Throw in vector(InputIterator, InputIterator, const allocator_type&) from allocator
     int a[] = {1, 2};
-    throwing_allocator<int> alloc(/*throw_on_ctor = */ false, /*throw_on_copy = */ true);
+    throwing_allocator<int> alloc(/*throw_on_ctor = */ false, /*throw_on_allocation = */ true);
     AllocVec vec(forward_iterator<int*>(a), forward_iterator<int*>(a + 2), alloc);
+    assert(false);
   } catch (int) {
   }
   check_new_delete_called();
@@ -125,6 +139,7 @@ int main(int, char**) {
     int throw_after = 1;
     vec.emplace_back(throw_after);
     auto vec2 = vec;
+    assert(false);
   } catch (int) {
   }
   check_new_delete_called();
@@ -134,6 +149,7 @@ int main(int, char**) {
     int throw_after = 1;
     vec.emplace_back(throw_after);
     std::vector<throwing_t> vec2(vec, std::allocator<int>());
+    assert(false);
   } catch (int) {
   }
   check_new_delete_called();
@@ -144,6 +160,7 @@ int main(int, char**) {
     throwing_t v(throw_after);
     vec.insert(vec.end(), 6, v);
     std::vector<throwing_t, test_allocator<throwing_t> > vec2(std::move(vec), test_allocator<throwing_t>(2));
+    assert(false);
   } catch (int) {
   }
   check_new_delete_called();
@@ -152,6 +169,7 @@ int main(int, char**) {
   try { // Throw in vector(initializer_list<value_type>) from type
     int throw_after = 1;
     std::vector<throwing_t> vec({throwing_t(throw_after)});
+    assert(false);
   } catch (int) {
   }
   check_new_delete_called();
@@ -159,6 +177,7 @@ int main(int, char**) {
   try { // Throw in vector(initializer_list<value_type>, const allocator_type&) constructor from type
     int throw_after = 1;
     std::vector<throwing_t> vec({throwing_t(throw_after)}, std::allocator<throwing_t>());
+    assert(false);
   } catch (int) {
   }
   check_new_delete_called();

``````````

</details>


https://github.com/llvm/llvm-project/pull/212673


More information about the libcxx-commits mailing list