[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