[libcxx-commits] [libcxx] [libc++][test] Add exception tests for vector capacity operations (PR #118141)
Peng Liu via libcxx-commits
libcxx-commits at lists.llvm.org
Thu Dec 5 13:55:21 PST 2024
https://github.com/winner245 updated https://github.com/llvm/llvm-project/pull/118141
>From e95bcd35e5f368f61bb0cea19ca43a4036119bdf Mon Sep 17 00:00:00 2001
From: Peng Liu <winner245 at hotmail.com>
Date: Fri, 29 Nov 2024 17:38:31 -0500
Subject: [PATCH] Add exception tests for vector capacity operations
---
.../std/containers/sequences/vector/common.h | 133 ++++++
.../vector/vector.capacity/reserve.pass.cpp | 180 +++-----
.../reserve_exceptions.pass.cpp | 323 ++++++++++++++
.../resize_size_exceptions.pass.cpp | 411 ++++++++++++++++++
.../resize_size_value_exceptions.pass.cpp | 232 ++++++++++
.../shrink_to_fit_exceptions.pass.cpp | 273 ++++++++++++
libcxx/test/support/test_allocator.h | 84 ++--
7 files changed, 1484 insertions(+), 152 deletions(-)
create mode 100644 libcxx/test/std/containers/sequences/vector/vector.capacity/reserve_exceptions.pass.cpp
create mode 100644 libcxx/test/std/containers/sequences/vector/vector.capacity/resize_size_exceptions.pass.cpp
create mode 100644 libcxx/test/std/containers/sequences/vector/vector.capacity/resize_size_value_exceptions.pass.cpp
create mode 100644 libcxx/test/std/containers/sequences/vector/vector.capacity/shrink_to_fit_exceptions.pass.cpp
diff --git a/libcxx/test/std/containers/sequences/vector/common.h b/libcxx/test/std/containers/sequences/vector/common.h
index e793ab2a21c1d0..f82f8bac111721 100644
--- a/libcxx/test/std/containers/sequences/vector/common.h
+++ b/libcxx/test/std/containers/sequences/vector/common.h
@@ -11,10 +11,104 @@
#include <cassert>
#include <cstddef>
+#include <cstdlib>
#include <memory>
+#include <string>
#include <type_traits>
+#include <utility>
+#include <vector>
#include "count_new.h"
+#include "test_macros.h"
+
+#if TEST_STD_VER >= 11
+
+template <typename T>
+struct move_only_throwing_t {
+ T data_;
+ int* throw_after_n_ = nullptr;
+ bool moved_from_ = false;
+
+ move_only_throwing_t() = default;
+
+ move_only_throwing_t(const T& data, int& throw_after_n) : data_(data), throw_after_n_(&throw_after_n) {
+ if (throw_after_n == 0)
+ throw 1;
+ --throw_after_n;
+ }
+
+ move_only_throwing_t(T&& data, int& throw_after_n) : data_(std::move(data)), throw_after_n_(&throw_after_n) {
+ if (throw_after_n == 0)
+ throw 1;
+ --throw_after_n;
+ }
+
+ move_only_throwing_t(const move_only_throwing_t&) = delete;
+ move_only_throwing_t& operator=(const move_only_throwing_t&) = delete;
+
+ move_only_throwing_t(move_only_throwing_t&& rhs) : data_(std::move(rhs.data_)), throw_after_n_(rhs.throw_after_n_) {
+ rhs.throw_after_n_ = nullptr;
+ rhs.moved_from_ = true;
+ if (throw_after_n_ == nullptr || *throw_after_n_ == 0)
+ throw 1;
+ --*throw_after_n_;
+ }
+
+ move_only_throwing_t& operator=(move_only_throwing_t&& rhs) {
+ if (this == &rhs)
+ return *this;
+ data_ = std::move(rhs.data_);
+ throw_after_n_ = rhs.throw_after_n_;
+ rhs.moved_from_ = true;
+ rhs.throw_after_n_ = nullptr;
+ if (throw_after_n_ == nullptr || *throw_after_n_ == 0)
+ throw 1;
+ --*throw_after_n_;
+ return *this;
+ }
+
+ friend bool operator==(const move_only_throwing_t& lhs, const move_only_throwing_t& rhs) {
+ return lhs.data_ == rhs.data_;
+ }
+ friend bool operator!=(const move_only_throwing_t& lhs, const move_only_throwing_t& rhs) {
+ return lhs.data_ != rhs.data_;
+ }
+};
+
+#endif
+
+template <typename T>
+struct throwing_data {
+ T data_;
+ int* throw_after_n_ = nullptr;
+ throwing_data() { throw 0; }
+
+ throwing_data(const T& data, int& throw_after_n) : data_(data), throw_after_n_(&throw_after_n) {
+ if (throw_after_n == 0)
+ throw 0;
+ --throw_after_n;
+ }
+
+ throwing_data(const throwing_data& rhs) : data_(rhs.data_), throw_after_n_(rhs.throw_after_n_) {
+ if (throw_after_n_ == nullptr || *throw_after_n_ == 0)
+ throw 1;
+ --*throw_after_n_;
+ }
+
+ throwing_data& operator=(const throwing_data& rhs) {
+ data_ = rhs.data_;
+ throw_after_n_ = rhs.throw_after_n_;
+ if (throw_after_n_ == nullptr || *throw_after_n_ == 0)
+ throw 1;
+ --*throw_after_n_;
+ return *this;
+ }
+
+ friend bool operator==(const throwing_data& lhs, const throwing_data& rhs) {
+ return lhs.data_ == rhs.data_ && lhs.throw_after_n_ == rhs.throw_after_n_;
+ }
+ friend bool operator!=(const throwing_data& lhs, const throwing_data& rhs) { return !(lhs == rhs); }
+};
template <class T>
struct throwing_allocator {
@@ -94,4 +188,43 @@ inline void check_new_delete_called() {
assert(globalMemCounter.aligned_new_array_called == globalMemCounter.aligned_delete_array_called);
}
+class Rnd {
+public:
+ static void initializeSeed(unsigned int seed = 12345) { std::srand(seed); }
+
+ static std::vector<int> getRandomIntegerInputs(std::size_t N) {
+ std::vector<int> v;
+ v.reserve(N);
+ for (std::size_t i = 0; i < N; ++i)
+ v.push_back(std::rand());
+ return v;
+ }
+
+ static std::vector<std::string> getRandomStringInputsWithLength(std::size_t N, std::size_t len) {
+ std::vector<std::string> v;
+ v.reserve(N);
+ for (std::size_t i = 0; i < N; ++i)
+ v.push_back(getRandomString(len));
+ return v;
+ }
+
+private:
+ static const char Letters[];
+ static const std::size_t LettersSize;
+
+ static std::string getRandomString(std::size_t len) {
+ std::string s;
+ s.reserve(len);
+ for (std::size_t i = 0; i < len; ++i)
+ s += Letters[std::rand() % LettersSize];
+ return s;
+ }
+};
+
+const char Rnd::Letters[] = {
+ '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K',
+ 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f',
+ 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z'};
+const std::size_t Rnd::LettersSize = sizeof(Rnd::Letters) / sizeof(Rnd::Letters[0]);
+
#endif // TEST_STD_CONTAINERS_SEQUENCES_VECTOR_COMMON_H
diff --git a/libcxx/test/std/containers/sequences/vector/vector.capacity/reserve.pass.cpp b/libcxx/test/std/containers/sequences/vector/vector.capacity/reserve.pass.cpp
index b8548ad72d4376..38e969335e0adb 100644
--- a/libcxx/test/std/containers/sequences/vector/vector.capacity/reserve.pass.cpp
+++ b/libcxx/test/std/containers/sequences/vector/vector.capacity/reserve.pass.cpp
@@ -19,126 +19,76 @@
#include "asan_testing.h"
TEST_CONSTEXPR_CXX20 bool tests() {
- {
- std::vector<int> v;
- v.reserve(10);
- assert(v.capacity() >= 10);
- assert(is_contiguous_container_asan_correct(v));
- }
- {
- std::vector<int> v(100);
- assert(v.capacity() == 100);
- v.reserve(50);
- assert(v.size() == 100);
- assert(v.capacity() == 100);
- v.reserve(150);
- assert(v.size() == 100);
- assert(v.capacity() == 150);
- assert(is_contiguous_container_asan_correct(v));
- }
- {
- // Add 1 for implementations that dynamically allocate a container proxy.
- std::vector<int, limited_allocator<int, 250 + 1> > v(100);
- assert(v.capacity() == 100);
- v.reserve(50);
- assert(v.size() == 100);
- assert(v.capacity() == 100);
- v.reserve(150);
- assert(v.size() == 100);
- assert(v.capacity() == 150);
- assert(is_contiguous_container_asan_correct(v));
- }
-#ifndef TEST_HAS_NO_EXCEPTIONS
- if (!TEST_IS_CONSTANT_EVALUATED) {
- std::vector<int> v;
- std::size_t sz = v.max_size() + 1;
-
- try {
- v.reserve(sz);
- assert(false);
- } catch (const std::length_error&) {
- assert(v.size() == 0);
- assert(v.capacity() == 0);
- }
- }
- if (!TEST_IS_CONSTANT_EVALUATED) {
- std::vector<int> v(10, 42);
- int* previous_data = v.data();
- std::size_t previous_capacity = v.capacity();
- std::size_t sz = v.max_size() + 1;
-
- try {
- v.reserve(sz);
- assert(false);
- } catch (std::length_error&) {
- assert(v.size() == 10);
- assert(v.capacity() == previous_capacity);
- assert(v.data() == previous_data);
-
- for (int i = 0; i < 10; ++i) {
- assert(v[i] == 42);
- }
- }
- }
-#endif
+ {
+ std::vector<int> v;
+ v.reserve(10);
+ assert(v.capacity() >= 10);
+ assert(is_contiguous_container_asan_correct(v));
+ }
+ {
+ std::vector<int> v(100);
+ assert(v.capacity() == 100);
+ v.reserve(50);
+ assert(v.size() == 100);
+ assert(v.capacity() == 100);
+ v.reserve(150);
+ assert(v.size() == 100);
+ assert(v.capacity() == 150);
+ assert(is_contiguous_container_asan_correct(v));
+ }
+ {
+ // Add 1 for implementations that dynamically allocate a container proxy.
+ std::vector<int, limited_allocator<int, 250 + 1> > v(100);
+ assert(v.capacity() == 100);
+ v.reserve(50);
+ assert(v.size() == 100);
+ assert(v.capacity() == 100);
+ v.reserve(150);
+ assert(v.size() == 100);
+ assert(v.capacity() == 150);
+ assert(is_contiguous_container_asan_correct(v));
+ }
#if TEST_STD_VER >= 11
- {
- std::vector<int, min_allocator<int>> v;
- v.reserve(10);
- assert(v.capacity() >= 10);
- assert(is_contiguous_container_asan_correct(v));
- }
- {
- std::vector<int, min_allocator<int>> v(100);
- assert(v.capacity() == 100);
- v.reserve(50);
- assert(v.size() == 100);
- assert(v.capacity() == 100);
- v.reserve(150);
- assert(v.size() == 100);
- assert(v.capacity() == 150);
- assert(is_contiguous_container_asan_correct(v));
- }
- {
- std::vector<int, safe_allocator<int>> v;
- v.reserve(10);
- assert(v.capacity() >= 10);
- assert(is_contiguous_container_asan_correct(v));
- }
- {
- std::vector<int, safe_allocator<int>> v(100);
- assert(v.capacity() == 100);
- v.reserve(50);
- assert(v.size() == 100);
- assert(v.capacity() == 100);
- v.reserve(150);
- assert(v.size() == 100);
- assert(v.capacity() == 150);
- assert(is_contiguous_container_asan_correct(v));
- }
-#endif
-#ifndef TEST_HAS_NO_EXCEPTIONS
- if (!TEST_IS_CONSTANT_EVALUATED) {
- std::vector<int, limited_allocator<int, 100> > v;
- v.reserve(50);
- assert(v.capacity() == 50);
- assert(is_contiguous_container_asan_correct(v));
- try {
- v.reserve(101);
- assert(false);
- } catch (const std::length_error&) {
- // no-op
- }
- assert(v.capacity() == 50);
- assert(is_contiguous_container_asan_correct(v));
- }
+ {
+ std::vector<int, min_allocator<int>> v;
+ v.reserve(10);
+ assert(v.capacity() >= 10);
+ assert(is_contiguous_container_asan_correct(v));
+ }
+ {
+ std::vector<int, min_allocator<int>> v(100);
+ assert(v.capacity() == 100);
+ v.reserve(50);
+ assert(v.size() == 100);
+ assert(v.capacity() == 100);
+ v.reserve(150);
+ assert(v.size() == 100);
+ assert(v.capacity() == 150);
+ assert(is_contiguous_container_asan_correct(v));
+ }
+ {
+ std::vector<int, safe_allocator<int>> v;
+ v.reserve(10);
+ assert(v.capacity() >= 10);
+ assert(is_contiguous_container_asan_correct(v));
+ }
+ {
+ std::vector<int, safe_allocator<int>> v(100);
+ assert(v.capacity() == 100);
+ v.reserve(50);
+ assert(v.size() == 100);
+ assert(v.capacity() == 100);
+ v.reserve(150);
+ assert(v.size() == 100);
+ assert(v.capacity() == 150);
+ assert(is_contiguous_container_asan_correct(v));
+ }
#endif
- return true;
+ return true;
}
-int main(int, char**)
-{
+int main(int, char**) {
tests();
#if TEST_STD_VER > 17
diff --git a/libcxx/test/std/containers/sequences/vector/vector.capacity/reserve_exceptions.pass.cpp b/libcxx/test/std/containers/sequences/vector/vector.capacity/reserve_exceptions.pass.cpp
new file mode 100644
index 00000000000000..63f19f945e8b48
--- /dev/null
+++ b/libcxx/test/std/containers/sequences/vector/vector.capacity/reserve_exceptions.pass.cpp
@@ -0,0 +1,323 @@
+//===----------------------------------------------------------------------===//
+//
+// 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-exceptions
+
+// This test file validates that std::vector<T>::reserve provides a strong exception guarantee if T is
+// Cpp17MoveInsertible and no exception is thrown by the move constructor of T during the reserve call.
+// It also checks that if T's move constructor is not noexcept, reserve provides only a basic exception
+// guarantee.
+
+#include <cstddef>
+#include <memory>
+#include <stdexcept>
+#include <type_traits>
+#include <vector>
+
+#include "../common.h"
+#include "MoveOnly.h"
+#include "count_new.h"
+#include "increasing_allocator.h"
+#include "min_allocator.h"
+#include "test_allocator.h"
+#include "test_iterators.h"
+#include "test_macros.h"
+
+template <typename T, typename Alloc>
+void test_allocation_exception_for_strong_guarantee(
+ std::vector<T, Alloc>& v, const std::vector<T>& values, std::size_t new_cap) {
+ T* old_data = v.data();
+ std::size_t old_size = v.size();
+ std::size_t old_cap = v.capacity();
+
+ try {
+ v.reserve(new_cap);
+ } catch (std::exception&) {
+ assert(v.data() == old_data);
+ assert(v.size() == old_size);
+ assert(v.capacity() == old_cap);
+ for (std::size_t i = 0; i < v.size(); ++i)
+ assert(v[i] == values[i]);
+ }
+}
+
+template <typename T, typename Alloc>
+void test_copy_ctor_exception_for_strong_guarantee(std::vector<throwing_data<T>, Alloc>& v,
+ const std::vector<T>& values) {
+ assert(v.empty() && !values.empty());
+ int throw_after = values.size() + values.size() / 2; // Trigger an exception halfway through reallocation
+ v.reserve(values.size());
+ for (std::size_t i = 0; i < values.size(); ++i)
+ v.emplace_back(values[i], throw_after);
+
+ throwing_data<T>* old_data = v.data();
+ std::size_t old_size = v.size();
+ std::size_t old_cap = v.capacity();
+ std::size_t new_cap = 2 * old_cap;
+
+ try {
+ v.reserve(new_cap);
+ } catch (...) {
+ assert(v.data() == old_data);
+ assert(v.size() == old_size);
+ assert(v.capacity() == old_cap);
+ for (std::size_t i = 0; i < v.size(); ++i)
+ assert(v[i].data_ == values[i]);
+ }
+}
+
+#if TEST_STD_VER >= 11
+
+template <typename T, typename Alloc>
+void test_move_ctor_exception_for_basic_guarantee(std::vector<move_only_throwing_t<T>, Alloc>& v,
+ const std::vector<T>& values) {
+ assert(v.empty() && !values.empty());
+ int throw_after = values.size() + values.size() / 2; // Trigger an exception halfway through reallocation
+ v.reserve(values.size());
+ for (std::size_t i = 0; i < values.size(); ++i)
+ v.emplace_back(values[i], throw_after);
+
+ move_only_throwing_t<T>* old_data = v.data();
+ std::size_t old_size = v.size();
+ std::size_t old_cap = v.capacity();
+ std::size_t new_cap = 2 * old_cap;
+
+ try {
+ v.reserve(new_cap);
+ } catch (...) {
+ assert(v.data() == old_data);
+ assert(v.size() == old_size);
+ assert(v.capacity() == old_cap);
+
+ // After a failure during element-wise move, the vector elements are left in a valid but unspecified state.
+ for (std::size_t i = 0; i < v.size(); ++i) {
+ assert(((void)v[i],
+ (void)v[i].data_,
+ (void)v[i].throw_after_n_,
+ (void)(v[i] == v[i]),
+ v[i].moved_from_ || v[i].data_ == values[i]));
+ }
+ }
+}
+
+#endif
+
+// Check the strong exception guarantee during reallocation failures
+void test_allocation_exceptions() {
+ //
+ // Tests for std::length_error during reallocation failures
+ //
+ {
+ std::vector<int> v;
+ test_allocation_exception_for_strong_guarantee(v, std::vector<int>(), v.max_size() + 1);
+ }
+ check_new_delete_called();
+
+ {
+ int a[] = {1, 2, 3, 4, 5};
+ std::vector<int> in(a, a + sizeof(a) / sizeof(a[0]));
+ std::vector<int> v(in.begin(), in.end());
+ test_allocation_exception_for_strong_guarantee(v, in, v.max_size() + 1);
+ }
+ check_new_delete_called();
+
+ {
+ int a[] = {1, 2, 3, 4, 5};
+ std::vector<int> in(a, a + sizeof(a) / sizeof(a[0]));
+ std::vector<int, min_allocator<int> > v(in.begin(), in.end());
+ test_allocation_exception_for_strong_guarantee(v, in, v.max_size() + 1);
+ }
+ check_new_delete_called();
+
+ {
+ int a[] = {1, 2, 3, 4, 5};
+ std::vector<int> in(a, a + sizeof(a) / sizeof(a[0]));
+ std::vector<int, safe_allocator<int> > v(in.begin(), in.end());
+ test_allocation_exception_for_strong_guarantee(v, in, v.max_size() + 1);
+ }
+ check_new_delete_called();
+
+ {
+ int a[] = {1, 2, 3, 4, 5};
+ std::vector<int> in(a, a + sizeof(a) / sizeof(a[0]));
+ std::vector<int, test_allocator<int> > v(in.begin(), in.end());
+ test_allocation_exception_for_strong_guarantee(v, in, v.max_size() + 1);
+ }
+ check_new_delete_called();
+
+ {
+ std::vector<int> in(10, 42);
+ std::vector<int, limited_allocator<int, 100> > v(in.begin(), in.end());
+ test_allocation_exception_for_strong_guarantee(v, in, v.max_size() + 1);
+ }
+ check_new_delete_called();
+
+#if TEST_STD_VER >= 23
+ {
+ std::vector<int> in{0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
+ std::vector<int, increasing_allocator<int>> v(in.begin(), in.end());
+ test_allocation_exception_for_strong_guarantee(v, in, v.max_size() + 1);
+ }
+ check_new_delete_called();
+#endif
+
+ //
+ // Tests for std::bad_alloc during reallocation failures
+ //
+ {
+ std::vector<int> in(10, 42);
+ std::vector<int, limited_allocator<int, 100> > v(in.begin(), in.end());
+ test_allocation_exception_for_strong_guarantee(v, in, 91);
+ }
+ check_new_delete_called();
+
+ {
+ std::vector<int> in(10, 42);
+ std::vector<int, limited_allocator<int, 100> > v(in.begin(), in.end());
+ v.reserve(30);
+ test_allocation_exception_for_strong_guarantee(v, in, 61);
+ }
+ check_new_delete_called();
+
+#if TEST_STD_VER >= 11
+ {
+ std::vector<MoveOnly> in(10);
+ std::vector<MoveOnly, limited_allocator<MoveOnly, 100> > v(10);
+ test_allocation_exception_for_strong_guarantee(v, in, 91);
+ }
+ check_new_delete_called();
+
+ {
+ std::vector<MoveOnly> in(10);
+ in.insert(in.cbegin() + 5, MoveOnly(42));
+ std::vector<MoveOnly, limited_allocator<MoveOnly, 100> > v(10);
+ v.reserve(30);
+ v.insert(v.cbegin() + 5, MoveOnly(42));
+ test_allocation_exception_for_strong_guarantee(v, in, 61);
+ }
+ check_new_delete_called();
+#endif
+
+ { // Practical example: Testing with 100 randomly generated integers.
+ auto in = Rnd::getRandomIntegerInputs(100);
+ std::vector<int, limited_allocator<int, 299> > v(in.begin(), in.end());
+ test_allocation_exception_for_strong_guarantee(v, in, 200);
+ }
+ check_new_delete_called();
+
+ { // Practical example: Testing with 100 randomly generated strings, each 256 characters long.
+ std::vector<std::string> in = Rnd::getRandomStringInputsWithLength(100, 256);
+ std::vector<std::string, limited_allocator<std::string, 299> > v(in.begin(), in.end());
+ test_allocation_exception_for_strong_guarantee(v, in, 200);
+ }
+ check_new_delete_called();
+}
+
+// Check the strong exception guarantee during copy-constructor failures
+void test_copy_ctor_exceptions() {
+ {
+ int a[] = {1, 2, 3, 4, 5};
+ std::vector<int> in(a, a + sizeof(a) / sizeof(a[0]));
+ std::vector<throwing_data<int> > v;
+ test_copy_ctor_exception_for_strong_guarantee(v, in);
+ }
+ check_new_delete_called();
+
+ {
+ int a[] = {1, 2, 3, 4, 5};
+ std::vector<int> in(a, a + sizeof(a) / sizeof(a[0]));
+ std::vector<throwing_data<int>, min_allocator<throwing_data<int> > > v;
+ test_copy_ctor_exception_for_strong_guarantee(v, in);
+ }
+ check_new_delete_called();
+
+ {
+ std::vector<int> in(10, 42);
+ std::vector<throwing_data<int>, safe_allocator<throwing_data<int> > > v;
+ test_copy_ctor_exception_for_strong_guarantee(v, in);
+ }
+ check_new_delete_called();
+
+ {
+ std::vector<int> in(10, 42);
+ std::vector<throwing_data<int>, test_allocator<throwing_data<int> > > v;
+ test_copy_ctor_exception_for_strong_guarantee(v, in);
+ }
+ check_new_delete_called();
+
+ {
+ std::vector<int> in(10, 42);
+ std::vector<throwing_data<int>, limited_allocator<throwing_data<int>, 100> > v;
+ test_copy_ctor_exception_for_strong_guarantee(v, in);
+ }
+ check_new_delete_called();
+
+#if TEST_STD_VER >= 23
+ {
+ std::vector<int> in{0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
+ std::vector<throwing_data<int>, increasing_allocator<throwing_data<int>>> v;
+ test_copy_ctor_exception_for_strong_guarantee(v, in);
+ }
+ check_new_delete_called();
+#endif
+
+ { // Practical example: Testing with 100 randomly generated integers.
+ auto in = Rnd::getRandomIntegerInputs(100);
+ std::vector<throwing_data<int> > v;
+ test_copy_ctor_exception_for_strong_guarantee(v, in);
+ }
+ check_new_delete_called();
+
+ { // Practical example: Testing with 100 randomly generated strings, each 256 characters long.
+ std::vector<std::string> in = Rnd::getRandomStringInputsWithLength(100, 256);
+ std::vector<throwing_data<std::string> > v;
+ test_copy_ctor_exception_for_strong_guarantee(v, in);
+ }
+ check_new_delete_called();
+}
+
+#if TEST_STD_VER >= 11
+
+// Check that if T is Cpp17MoveInsertible && !Cpp17CopyInsertible, and T's move-ctor is not noexcept, then
+// std::vector::reserve only provides basic guarantee.
+void test_move_ctor_exceptions() {
+ {
+ std::vector<int> in{0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
+ std::vector<move_only_throwing_t<int>> v;
+ test_move_ctor_exception_for_basic_guarantee(v, in);
+ }
+ check_new_delete_called();
+
+# if TEST_STD_VER >= 23
+ {
+ std::vector<int> in{0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
+ std::vector<move_only_throwing_t<int>, increasing_allocator<move_only_throwing_t<int>>> v;
+ test_move_ctor_exception_for_basic_guarantee(v, in);
+ }
+ check_new_delete_called();
+# endif
+
+ {
+ // Practical example: Testing with 100 randomly generated strings, each 256 characters long.
+ std::vector<std::string> in = Rnd::getRandomStringInputsWithLength(100, 256);
+ std::vector<move_only_throwing_t<std::string> > v;
+ test_move_ctor_exception_for_basic_guarantee(v, in);
+ }
+ check_new_delete_called();
+}
+
+#endif
+
+int main(int, char**) {
+ Rnd::initializeSeed();
+ test_allocation_exceptions();
+ test_copy_ctor_exceptions();
+#if TEST_STD_VER >= 11
+ test_move_ctor_exceptions();
+#endif
+}
diff --git a/libcxx/test/std/containers/sequences/vector/vector.capacity/resize_size_exceptions.pass.cpp b/libcxx/test/std/containers/sequences/vector/vector.capacity/resize_size_exceptions.pass.cpp
new file mode 100644
index 00000000000000..9a6c5deea44fe9
--- /dev/null
+++ b/libcxx/test/std/containers/sequences/vector/vector.capacity/resize_size_exceptions.pass.cpp
@@ -0,0 +1,411 @@
+//===----------------------------------------------------------------------===//
+//
+// 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-exceptions
+
+// This test file validates that std::vector<T>::resize(size_type) provides a strong exception guarantee
+// if no exception is thrown by the move constructor of T during the resize call. It also checks that if
+// T's move constructor is not noexcept, resize provides only a basic exception guarantee.
+
+#include <cstddef>
+#include <memory>
+#include <stdexcept>
+#include <type_traits>
+#include <vector>
+
+#include "../common.h"
+#include "MoveOnly.h"
+#include "count_new.h"
+#include "increasing_allocator.h"
+#include "min_allocator.h"
+#include "test_allocator.h"
+#include "test_iterators.h"
+#include "test_macros.h"
+
+template <typename T, typename Alloc>
+void test_allocation_exception_for_strong_guarantee(
+ std::vector<T, Alloc>& v, const std::vector<T>& values, std::size_t new_size) {
+ T* old_data = v.data();
+ std::size_t old_size = v.size();
+ std::size_t old_cap = v.capacity();
+
+ try {
+ v.resize(new_size);
+ } catch (std::exception&) {
+ assert(v.data() == old_data);
+ assert(v.size() == old_size);
+ assert(v.capacity() == old_cap);
+ for (std::size_t i = 0; i < v.size(); ++i)
+ assert(v[i] == values[i]);
+ }
+}
+
+template <typename T, typename Alloc>
+void test_default_ctor_exception_for_strong_guarantee(
+ std::vector<throwing_data<T>, Alloc>& v, const std::vector<T>& values) {
+ assert(v.empty() && !values.empty());
+ int throw_after = values.size() + 10;
+ v.reserve(values.size());
+ for (std::size_t i = 0; i < values.size(); ++i)
+ v.emplace_back(values[i], throw_after);
+
+ throwing_data<T>* old_data = v.data();
+ std::size_t old_size = v.size();
+ std::size_t old_cap = v.capacity();
+ std::size_t new_size = old_size + 1;
+
+ try {
+ v.resize(new_size);
+ } catch (...) {
+ assert(v.data() == old_data);
+ assert(v.size() == old_size);
+ assert(v.capacity() == old_cap);
+ for (std::size_t i = 0; i < v.size(); ++i)
+ assert(v[i].data_ == values[i]);
+ }
+}
+
+template <typename T, typename Alloc>
+void test_copy_ctor_exception_for_strong_guarantee(std::vector<throwing_data<T>, Alloc>& v,
+ const std::vector<T>& values) {
+ assert(v.empty() && !values.empty());
+ int throw_after = values.size() + values.size() / 2; // Trigger an exception halfway through reallocation
+ v.reserve(values.size());
+ for (std::size_t i = 0; i < values.size(); ++i)
+ v.emplace_back(values[i], throw_after);
+
+ throwing_data<T>* old_data = v.data();
+ std::size_t old_size = v.size();
+ std::size_t old_cap = v.capacity();
+ std::size_t new_size = 2 * old_cap;
+
+ try {
+ v.resize(new_size);
+ } catch (...) {
+ assert(v.data() == old_data);
+ assert(v.size() == old_size);
+ assert(v.capacity() == old_cap);
+ for (std::size_t i = 0; i < v.size(); ++i)
+ assert(v[i].data_ == values[i]);
+ }
+}
+
+#if TEST_STD_VER >= 11
+
+template <typename T, typename Alloc>
+void test_move_ctor_exception_for_basic_guarantee(std::vector<move_only_throwing_t<T>, Alloc>& v,
+ const std::vector<T>& values) {
+ assert(v.empty() && !values.empty());
+ int throw_after = values.size() + values.size() / 2; // Trigger an exception halfway through reallocation
+ v.reserve(values.size());
+ for (std::size_t i = 0; i < values.size(); ++i)
+ v.emplace_back(values[i], throw_after);
+
+ move_only_throwing_t<T>* old_data = v.data();
+ std::size_t old_size = v.size();
+ std::size_t old_cap = v.capacity();
+ std::size_t new_size = 2 * old_cap;
+
+ try {
+ v.resize(new_size);
+ } catch (...) {
+ assert(v.data() == old_data);
+ assert(v.size() == old_size);
+ assert(v.capacity() == old_cap);
+
+ // After a failure during element-wise move, the vector elements are left in a valid but unspecified state.
+ for (std::size_t i = 0; i < v.size(); ++i) {
+ assert(((void)v[i],
+ (void)v[i].data_,
+ (void)v[i].throw_after_n_,
+ (void)(v[i] == v[i]),
+ v[i].moved_from_ || v[i].data_ == values[i]));
+ }
+ }
+}
+
+#endif
+
+// Check the strong exception guarantee during reallocation failures
+void test_allocation_exceptions() {
+ //
+ // Tests for std::length_error during reallocation failures
+ //
+ {
+ std::vector<int> v;
+ test_allocation_exception_for_strong_guarantee(v, std::vector<int>(), v.max_size() + 1);
+ }
+ check_new_delete_called();
+
+ {
+ int a[] = {1, 2, 3, 4, 5};
+ std::vector<int> in(a, a + sizeof(a) / sizeof(a[0]));
+ std::vector<int> v(in.begin(), in.end());
+ test_allocation_exception_for_strong_guarantee(v, in, v.max_size() + 1);
+ }
+ check_new_delete_called();
+
+ {
+ int a[] = {1, 2, 3, 4, 5};
+ std::vector<int> in(a, a + sizeof(a) / sizeof(a[0]));
+ std::vector<int, min_allocator<int> > v(in.begin(), in.end());
+ test_allocation_exception_for_strong_guarantee(v, in, v.max_size() + 1);
+ }
+ check_new_delete_called();
+
+ {
+ int a[] = {1, 2, 3, 4, 5};
+ std::vector<int> in(a, a + sizeof(a) / sizeof(a[0]));
+ std::vector<int, safe_allocator<int> > v(in.begin(), in.end());
+ test_allocation_exception_for_strong_guarantee(v, in, v.max_size() + 1);
+ }
+ check_new_delete_called();
+
+ {
+ int a[] = {1, 2, 3, 4, 5};
+ std::vector<int> in(a, a + sizeof(a) / sizeof(a[0]));
+ std::vector<int, test_allocator<int> > v(in.begin(), in.end());
+ test_allocation_exception_for_strong_guarantee(v, in, v.max_size() + 1);
+ }
+ check_new_delete_called();
+
+ {
+ std::vector<int> in(10, 42);
+ std::vector<int, limited_allocator<int, 100> > v(in.begin(), in.end());
+ test_allocation_exception_for_strong_guarantee(v, in, v.max_size() + 1);
+ }
+ check_new_delete_called();
+
+#if TEST_STD_VER >= 23
+ {
+ std::vector<int> in{0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
+ std::vector<int, increasing_allocator<int>> v(in.begin(), in.end());
+ test_allocation_exception_for_strong_guarantee(v, in, v.max_size() + 1);
+ }
+ check_new_delete_called();
+#endif
+
+ //
+ // Tests for std::bad_alloc during reallocation failures
+ //
+ {
+ std::vector<int> in(10, 42);
+ std::vector<int, limited_allocator<int, 100> > v(in.begin(), in.end());
+ test_allocation_exception_for_strong_guarantee(v, in, 91);
+ }
+ check_new_delete_called();
+
+ {
+ std::vector<int> in(10, 42);
+ std::vector<int, limited_allocator<int, 100> > v(in.begin(), in.end());
+ v.reserve(30);
+ test_allocation_exception_for_strong_guarantee(v, in, 61);
+ }
+ check_new_delete_called();
+
+#if TEST_STD_VER >= 11
+ {
+ std::vector<MoveOnly> in(10);
+ std::vector<MoveOnly, limited_allocator<MoveOnly, 100> > v(10);
+ test_allocation_exception_for_strong_guarantee(v, in, 91);
+ }
+ check_new_delete_called();
+
+ {
+ std::vector<MoveOnly> in(10);
+ in.insert(in.cbegin() + 5, MoveOnly(42));
+ std::vector<MoveOnly, limited_allocator<MoveOnly, 100> > v(10);
+ v.reserve(30);
+ v.insert(v.cbegin() + 5, MoveOnly(42));
+ test_allocation_exception_for_strong_guarantee(v, in, 61);
+ }
+ check_new_delete_called();
+#endif
+
+ { // Practical example: Testing with 100 randomly generated integers.
+ auto in = Rnd::getRandomIntegerInputs(100);
+ std::vector<int, limited_allocator<int, 299> > v(in.begin(), in.end());
+ test_allocation_exception_for_strong_guarantee(v, in, 200);
+ }
+ check_new_delete_called();
+
+ { // Practical example: Testing with 100 randomly generated strings, each 256 characters long.
+ std::vector<std::string> in = Rnd::getRandomStringInputsWithLength(100, 256);
+ std::vector<std::string, limited_allocator<std::string, 299> > v(in.begin(), in.end());
+ test_allocation_exception_for_strong_guarantee(v, in, 200);
+ }
+ check_new_delete_called();
+}
+
+// Check the strong exception guarantee during default-constructor failures
+void test_default_ctor_exceptions() {
+ {
+ int a[] = {1, 2, 3, 4, 5};
+ std::vector<int> in(a, a + sizeof(a) / sizeof(a[0]));
+ std::vector<throwing_data<int> > v;
+ test_default_ctor_exception_for_strong_guarantee(v, in);
+ }
+ check_new_delete_called();
+
+ {
+ int a[] = {1, 2, 3, 4, 5};
+ std::vector<int> in(a, a + sizeof(a) / sizeof(a[0]));
+ std::vector<throwing_data<int>, min_allocator<throwing_data<int> > > v;
+ test_default_ctor_exception_for_strong_guarantee(v, in);
+ }
+ check_new_delete_called();
+
+ {
+ std::vector<int> in(10, 42);
+ std::vector<throwing_data<int>, safe_allocator<throwing_data<int> > > v;
+ test_default_ctor_exception_for_strong_guarantee(v, in);
+ }
+ check_new_delete_called();
+
+ {
+ std::vector<int> in(10, 42);
+ std::vector<throwing_data<int>, test_allocator<throwing_data<int> > > v;
+ test_default_ctor_exception_for_strong_guarantee(v, in);
+ }
+ check_new_delete_called();
+
+ {
+ std::vector<int> in(10, 42);
+ std::vector<throwing_data<int>, limited_allocator<throwing_data<int>, 100> > v;
+ test_default_ctor_exception_for_strong_guarantee(v, in);
+ }
+ check_new_delete_called();
+
+#if TEST_STD_VER >= 23
+ {
+ std::vector<int> in{0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
+ std::vector<throwing_data<int>, increasing_allocator<throwing_data<int>>> v;
+ test_default_ctor_exception_for_strong_guarantee(v, in);
+ }
+ check_new_delete_called();
+#endif
+
+ { // Practical example: Testing with 100 randomly generated integers.
+ auto in = Rnd::getRandomIntegerInputs(100);
+ std::vector<throwing_data<int> > v;
+ test_default_ctor_exception_for_strong_guarantee(v, in);
+ }
+ check_new_delete_called();
+
+ { // Practical example: Testing with 100 randomly generated strings, each 256 characters long.
+ std::vector<std::string> in = Rnd::getRandomStringInputsWithLength(100, 256);
+ std::vector<throwing_data<std::string> > v;
+ test_default_ctor_exception_for_strong_guarantee(v, in);
+ }
+ check_new_delete_called();
+}
+
+// Check the strong exception guarantee during copy-constructor failures
+void test_copy_ctor_exceptions() {
+ {
+ int a[] = {1, 2, 3, 4, 5};
+ std::vector<int> in(a, a + sizeof(a) / sizeof(a[0]));
+ std::vector<throwing_data<int> > v;
+ test_copy_ctor_exception_for_strong_guarantee(v, in);
+ }
+ check_new_delete_called();
+
+ {
+ int a[] = {1, 2, 3, 4, 5};
+ std::vector<int> in(a, a + sizeof(a) / sizeof(a[0]));
+ std::vector<throwing_data<int>, min_allocator<throwing_data<int> > > v;
+ test_copy_ctor_exception_for_strong_guarantee(v, in);
+ }
+ check_new_delete_called();
+
+ {
+ std::vector<int> in(10, 42);
+ std::vector<throwing_data<int>, safe_allocator<throwing_data<int> > > v;
+ test_copy_ctor_exception_for_strong_guarantee(v, in);
+ }
+ check_new_delete_called();
+
+ {
+ std::vector<int> in(10, 42);
+ std::vector<throwing_data<int>, test_allocator<throwing_data<int> > > v;
+ test_copy_ctor_exception_for_strong_guarantee(v, in);
+ }
+ check_new_delete_called();
+
+ {
+ std::vector<int> in(10, 42);
+ std::vector<throwing_data<int>, limited_allocator<throwing_data<int>, 100> > v;
+ test_copy_ctor_exception_for_strong_guarantee(v, in);
+ }
+ check_new_delete_called();
+
+#if TEST_STD_VER >= 23
+ {
+ std::vector<int> in{0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
+ std::vector<throwing_data<int>, increasing_allocator<throwing_data<int>>> v;
+ test_copy_ctor_exception_for_strong_guarantee(v, in);
+ }
+ check_new_delete_called();
+#endif
+
+ { // Practical example: Testing with 100 randomly generated integers.
+ auto in = Rnd::getRandomIntegerInputs(100);
+ std::vector<throwing_data<int> > v;
+ test_copy_ctor_exception_for_strong_guarantee(v, in);
+ }
+ check_new_delete_called();
+
+ { // Practical example: Testing with 100 randomly generated strings, each 256 characters long.
+ std::vector<std::string> in = Rnd::getRandomStringInputsWithLength(100, 256);
+ std::vector<throwing_data<std::string> > v;
+ test_copy_ctor_exception_for_strong_guarantee(v, in);
+ }
+ check_new_delete_called();
+}
+
+#if TEST_STD_VER >= 11
+
+// Check that if T is Cpp17MoveInsertible && !Cpp17CopyInsertible, and T's move-ctor is not noexcept, then
+// std::vector::reserve only provides basic guarantee.
+void test_move_ctor_exceptions() {
+ {
+ std::vector<int> in{0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
+ std::vector<move_only_throwing_t<int>> v;
+ test_move_ctor_exception_for_basic_guarantee(v, in);
+ }
+ check_new_delete_called();
+
+# if TEST_STD_VER >= 23
+ {
+ std::vector<int> in{0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
+ std::vector<move_only_throwing_t<int>, increasing_allocator<move_only_throwing_t<int>>> v;
+ test_move_ctor_exception_for_basic_guarantee(v, in);
+ }
+ check_new_delete_called();
+# endif
+
+ {
+ // Practical example: Testing with 100 randomly generated strings, each 256 characters long.
+ std::vector<std::string> in = Rnd::getRandomStringInputsWithLength(100, 256);
+ std::vector<move_only_throwing_t<std::string> > v;
+ test_move_ctor_exception_for_basic_guarantee(v, in);
+ }
+ check_new_delete_called();
+}
+
+#endif
+
+int main(int, char**) {
+ Rnd::initializeSeed();
+ test_allocation_exceptions();
+ test_default_ctor_exceptions();
+ test_copy_ctor_exceptions();
+#if TEST_STD_VER >= 11
+ test_move_ctor_exceptions();
+#endif
+}
diff --git a/libcxx/test/std/containers/sequences/vector/vector.capacity/resize_size_value_exceptions.pass.cpp b/libcxx/test/std/containers/sequences/vector/vector.capacity/resize_size_value_exceptions.pass.cpp
new file mode 100644
index 00000000000000..a7c118ce6885b0
--- /dev/null
+++ b/libcxx/test/std/containers/sequences/vector/vector.capacity/resize_size_value_exceptions.pass.cpp
@@ -0,0 +1,232 @@
+//===----------------------------------------------------------------------===//
+//
+// 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-exceptions
+
+// Check that std::vector<T>::resize(size_type sz, const value_type& x) provides a strong exception guarantee
+// if T is Cpp17CopyInsertible.
+
+#include <cstddef>
+#include <memory>
+#include <stdexcept>
+#include <type_traits>
+#include <vector>
+
+#include "../common.h"
+#include "count_new.h"
+#include "increasing_allocator.h"
+#include "min_allocator.h"
+#include "test_allocator.h"
+#include "test_iterators.h"
+#include "test_macros.h"
+
+template <typename T, typename Alloc>
+void test_allocation_exception_for_strong_guarantee(
+ std::vector<T, Alloc>& v, const std::vector<T>& values, std::size_t new_size) {
+ T* old_data = v.data();
+ std::size_t old_size = v.size();
+ std::size_t old_cap = v.capacity();
+
+ try {
+ v.resize(new_size, values[0]);
+ } catch (std::exception&) {
+ assert(v.data() == old_data);
+ assert(v.size() == old_size);
+ assert(v.capacity() == old_cap);
+ for (std::size_t i = 0; i < v.size(); ++i)
+ assert(v[i] == values[i]);
+ }
+}
+
+template <typename T, typename Alloc>
+void test_copy_ctor_exception_for_strong_guarantee(std::vector<throwing_data<T>, Alloc>& v,
+ const std::vector<T>& values) {
+ assert(v.empty() && !values.empty());
+ int throw_after = values.size() + values.size() / 2; // Trigger an exception halfway through reallocation
+ v.reserve(values.size());
+ for (std::size_t i = 0; i < values.size(); ++i)
+ v.emplace_back(values[i], throw_after);
+
+ throwing_data<T>* old_data = v.data();
+ std::size_t old_size = v.size();
+ std::size_t old_cap = v.capacity();
+ std::size_t new_size = 2 * old_cap;
+
+ try {
+ int n = new_size - old_size + 1;
+ throwing_data<T> t(T(), n);
+ v.resize(new_size, t);
+ } catch (...) {
+ assert(v.data() == old_data);
+ assert(v.size() == old_size);
+ assert(v.capacity() == old_cap);
+ for (std::size_t i = 0; i < v.size(); ++i)
+ assert(v[i].data_ == values[i]);
+ }
+}
+
+// Check the strong exception guarantee during reallocation failures
+void test_allocation_exceptions() {
+ //
+ // Tests for std::length_error during reallocation failures
+ //
+ {
+ std::vector<int> v;
+ test_allocation_exception_for_strong_guarantee(v, std::vector<int>(), v.max_size() + 1);
+ }
+ check_new_delete_called();
+
+ {
+ int a[] = {1, 2, 3, 4, 5};
+ std::vector<int> in(a, a + sizeof(a) / sizeof(a[0]));
+ std::vector<int> v(in.begin(), in.end());
+ test_allocation_exception_for_strong_guarantee(v, in, v.max_size() + 1);
+ }
+ check_new_delete_called();
+
+ {
+ int a[] = {1, 2, 3, 4, 5};
+ std::vector<int> in(a, a + sizeof(a) / sizeof(a[0]));
+ std::vector<int, min_allocator<int> > v(in.begin(), in.end());
+ test_allocation_exception_for_strong_guarantee(v, in, v.max_size() + 1);
+ }
+ check_new_delete_called();
+
+ {
+ int a[] = {1, 2, 3, 4, 5};
+ std::vector<int> in(a, a + sizeof(a) / sizeof(a[0]));
+ std::vector<int, safe_allocator<int> > v(in.begin(), in.end());
+ test_allocation_exception_for_strong_guarantee(v, in, v.max_size() + 1);
+ }
+ check_new_delete_called();
+
+ {
+ int a[] = {1, 2, 3, 4, 5};
+ std::vector<int> in(a, a + sizeof(a) / sizeof(a[0]));
+ std::vector<int, test_allocator<int> > v(in.begin(), in.end());
+ test_allocation_exception_for_strong_guarantee(v, in, v.max_size() + 1);
+ }
+ check_new_delete_called();
+
+ {
+ std::vector<int> in(10, 42);
+ std::vector<int, limited_allocator<int, 100> > v(in.begin(), in.end());
+ test_allocation_exception_for_strong_guarantee(v, in, v.max_size() + 1);
+ }
+ check_new_delete_called();
+
+#if TEST_STD_VER >= 23
+ {
+ std::vector<int> in{0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
+ std::vector<int, increasing_allocator<int>> v(in.begin(), in.end());
+ test_allocation_exception_for_strong_guarantee(v, in, v.max_size() + 1);
+ }
+ check_new_delete_called();
+#endif
+
+ //
+ // Tests for std::bad_alloc during reallocation failures
+ //
+ {
+ std::vector<int> in(10, 42);
+ std::vector<int, limited_allocator<int, 100> > v(in.begin(), in.end());
+ test_allocation_exception_for_strong_guarantee(v, in, 91);
+ }
+ check_new_delete_called();
+
+ {
+ std::vector<int> in(10, 42);
+ std::vector<int, limited_allocator<int, 100> > v(in.begin(), in.end());
+ v.reserve(30);
+ test_allocation_exception_for_strong_guarantee(v, in, 61);
+ }
+ check_new_delete_called();
+
+ { // Practical example: Testing with 100 randomly generated integers.
+ auto in = Rnd::getRandomIntegerInputs(100);
+ std::vector<int, limited_allocator<int, 299> > v(in.begin(), in.end());
+ test_allocation_exception_for_strong_guarantee(v, in, 200);
+ }
+ check_new_delete_called();
+
+ { // Practical example: Testing with 100 randomly generated strings, each 256 characters long.
+ std::vector<std::string> in = Rnd::getRandomStringInputsWithLength(100, 256);
+ std::vector<std::string, limited_allocator<std::string, 299> > v(in.begin(), in.end());
+ test_allocation_exception_for_strong_guarantee(v, in, 200);
+ }
+ check_new_delete_called();
+}
+
+// Check the strong exception guarantee during copy-constructor failures
+void test_copy_ctor_exceptions() {
+ {
+ int a[] = {1, 2, 3, 4, 5};
+ std::vector<int> in(a, a + sizeof(a) / sizeof(a[0]));
+ std::vector<throwing_data<int> > v;
+ test_copy_ctor_exception_for_strong_guarantee(v, in);
+ }
+ check_new_delete_called();
+
+ {
+ int a[] = {1, 2, 3, 4, 5};
+ std::vector<int> in(a, a + sizeof(a) / sizeof(a[0]));
+ std::vector<throwing_data<int>, min_allocator<throwing_data<int> > > v;
+ test_copy_ctor_exception_for_strong_guarantee(v, in);
+ }
+ check_new_delete_called();
+
+ {
+ std::vector<int> in(10, 42);
+ std::vector<throwing_data<int>, safe_allocator<throwing_data<int> > > v;
+ test_copy_ctor_exception_for_strong_guarantee(v, in);
+ }
+ check_new_delete_called();
+
+ {
+ std::vector<int> in(10, 42);
+ std::vector<throwing_data<int>, test_allocator<throwing_data<int> > > v;
+ test_copy_ctor_exception_for_strong_guarantee(v, in);
+ }
+ check_new_delete_called();
+
+ {
+ std::vector<int> in(10, 42);
+ std::vector<throwing_data<int>, limited_allocator<throwing_data<int>, 100> > v;
+ test_copy_ctor_exception_for_strong_guarantee(v, in);
+ }
+ check_new_delete_called();
+
+#if TEST_STD_VER >= 23
+ {
+ std::vector<int> in{0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
+ std::vector<throwing_data<int>, increasing_allocator<throwing_data<int>>> v;
+ test_copy_ctor_exception_for_strong_guarantee(v, in);
+ }
+ check_new_delete_called();
+#endif
+
+ { // Practical example: Testing with 100 randomly generated integers.
+ auto in = Rnd::getRandomIntegerInputs(100);
+ std::vector<throwing_data<int> > v;
+ test_copy_ctor_exception_for_strong_guarantee(v, in);
+ }
+ check_new_delete_called();
+
+ { // Practical example: Testing with 100 randomly generated strings, each 256 characters long.
+ std::vector<std::string> in = Rnd::getRandomStringInputsWithLength(100, 256);
+ std::vector<throwing_data<std::string> > v;
+ test_copy_ctor_exception_for_strong_guarantee(v, in);
+ }
+ check_new_delete_called();
+}
+
+int main(int, char**) {
+ Rnd::initializeSeed();
+ test_allocation_exceptions();
+ test_copy_ctor_exceptions();
+}
diff --git a/libcxx/test/std/containers/sequences/vector/vector.capacity/shrink_to_fit_exceptions.pass.cpp b/libcxx/test/std/containers/sequences/vector/vector.capacity/shrink_to_fit_exceptions.pass.cpp
new file mode 100644
index 00000000000000..dfdfd4b5739b9d
--- /dev/null
+++ b/libcxx/test/std/containers/sequences/vector/vector.capacity/shrink_to_fit_exceptions.pass.cpp
@@ -0,0 +1,273 @@
+//===----------------------------------------------------------------------===//
+//
+// 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-exceptions
+
+// This test file validates that std::vector<T>::shrink_to_fit provides a strong exception guarantee when
+// T is Cpp17MoveInsertible and its move constructor does not throw exceptions during the shrink_to_fit
+// call. Additionally, it checks that for move-only types where T's move constructor is not noexcept, only
+// the basic exception guarantee is ensured.
+
+#include <cstddef>
+#include <memory>
+#include <stdexcept>
+#include <type_traits>
+#include <vector>
+
+#include "../common.h"
+#include "MoveOnly.h"
+#include "count_new.h"
+#include "increasing_allocator.h"
+#include "min_allocator.h"
+#include "test_allocator.h"
+#include "test_iterators.h"
+#include "test_macros.h"
+
+template <typename T, typename Alloc>
+void test_allocation_exception_for_strong_guarantee(std::vector<T, Alloc>& v, const std::vector<T>& values) {
+ T* old_data = v.data();
+ std::size_t old_size = v.size();
+ std::size_t old_cap = v.capacity();
+
+ try {
+ v.shrink_to_fit();
+ } catch (std::exception&) {
+ }
+
+ // As shrink_to_fit may swallow any exceptions, we place the checks outisde the catch block.
+ assert(v.data() == old_data);
+ assert(v.size() == old_size);
+ assert(v.capacity() == old_cap);
+ for (std::size_t i = 0; i < v.size(); ++i)
+ assert(v[i] == values[i]);
+}
+
+template <typename T, typename Alloc>
+void test_copy_ctor_exception_for_strong_guarantee(std::vector<throwing_data<T>, Alloc>& v,
+ const std::vector<T>& values) {
+ assert(v.empty() && !values.empty());
+ v.reserve(values.size() * 2);
+ int throw_after = values.size() + values.size() / 2; // Trigger an exception halfway through reallocation
+ for (std::size_t i = 0; i < values.size(); ++i)
+ v.emplace_back(values[i], throw_after);
+
+ throwing_data<T>* old_data = v.data();
+ std::size_t old_size = v.size();
+ std::size_t old_cap = v.capacity();
+
+ try {
+ v.shrink_to_fit();
+ } catch (...) {
+ }
+
+ assert(v.data() == old_data);
+ assert(v.size() == old_size);
+ assert(v.capacity() == old_cap);
+ for (std::size_t i = 0; i < v.size(); ++i)
+ assert(v[i].data_ == values[i]);
+}
+
+#if TEST_STD_VER >= 11
+
+template <typename T, typename Alloc>
+void test_move_ctor_exception_for_basic_guarantee(std::vector<move_only_throwing_t<T>, Alloc>& v,
+ const std::vector<T>& values) {
+ assert(v.empty() && !values.empty());
+ v.reserve(values.size() * 2);
+ int throw_after = values.size() + values.size() / 2; // Trigger an exception halfway through reallocation
+ for (std::size_t i = 0; i < values.size(); ++i)
+ v.emplace_back(values[i], throw_after);
+
+ // move_only_throwing_t<T>* old_data = v.data();
+ // std::size_t old_size = v.size();
+ // std::size_t old_cap = v.capacity();
+
+ try {
+ v.shrink_to_fit();
+ } catch (int) {
+ }
+
+ // assert(v.data() == old_data); // assertion failed on macos!!!
+ // assert(v.size() == old_size);
+ // assert(v.capacity() == old_cap);
+
+ // After a failure during element-wise move, the vector elements are left in a valid but unspecified state.
+ for (std::size_t i = 0; i < v.size(); ++i) {
+ assert(((void)v[i],
+ (void)v[i].data_,
+ (void)v[i].throw_after_n_,
+ (void)(v[i] == v[i]),
+ v[i].moved_from_ || v[i].data_ == values[i]));
+ }
+}
+
+#endif
+
+// Check the strong exception guarantee during reallocation failures
+void test_allocation_exceptions() {
+ {
+ int a[] = {1, 2, 3, 4, 5};
+ std::vector<int> in(a, a + sizeof(a) / sizeof(a[0]));
+ std::vector<int, limited_allocator<int, 100> > v;
+ v.reserve(100);
+ for (std::size_t i = 0; i < in.size(); ++i)
+ v.push_back(in[i]);
+ test_allocation_exception_for_strong_guarantee(v, in);
+ }
+ check_new_delete_called();
+
+ {
+ std::vector<int> in(50, 42);
+ std::vector<int, limited_allocator<int, 100> > v;
+ v.reserve(100);
+ for (std::size_t i = 0; i < in.size(); ++i)
+ v.push_back(in[i]);
+ test_allocation_exception_for_strong_guarantee(v, in);
+ }
+ check_new_delete_called();
+
+ {
+ std::vector<int> in(10, 42);
+ std::vector<int, limited_allocator<int, 100> > v(in.begin(), in.end());
+ v.reserve(90);
+ test_allocation_exception_for_strong_guarantee(v, in);
+ }
+ check_new_delete_called();
+
+#if TEST_STD_VER >= 11
+ {
+ std::vector<MoveOnly> in(10);
+ std::vector<MoveOnly, limited_allocator<MoveOnly, 100> > v(10);
+ v.reserve(90);
+ test_allocation_exception_for_strong_guarantee(v, in);
+ }
+ check_new_delete_called();
+
+ {
+ std::vector<MoveOnly> in(10);
+ std::vector<MoveOnly, limited_allocator<MoveOnly, 100> > v(10);
+ v.reserve(90);
+ in.insert(in.cbegin() + 5, MoveOnly(42));
+ v.insert(v.cbegin() + 5, MoveOnly(42));
+ test_allocation_exception_for_strong_guarantee(v, in);
+ }
+ check_new_delete_called();
+#endif
+
+ { // Practical example: Testing with 100 randomly generated integers.
+ auto in = Rnd::getRandomIntegerInputs(100);
+ std::vector<int, limited_allocator<int, 100> > v(in.begin(), in.end());
+ in.erase(in.end() - 10, in.end());
+ v.erase(v.end() - 10, v.end());
+ test_allocation_exception_for_strong_guarantee(v, in);
+ }
+ check_new_delete_called();
+
+ { // Practical example: Testing with 100 randomly generated strings, each 256 characters long.
+ std::vector<std::string> in = Rnd::getRandomStringInputsWithLength(100, 256);
+ std::vector<std::string, limited_allocator<std::string, 300> > v(in.begin(), in.end());
+ v.reserve(200);
+ test_allocation_exception_for_strong_guarantee(v, in);
+ }
+ check_new_delete_called();
+}
+
+// Check the strong exception guarantee during copy-constructor failures
+void test_copy_ctor_exceptions() {
+ {
+ int a[] = {1, 2, 3, 4, 5};
+ std::vector<int> in(a, a + sizeof(a) / sizeof(a[0]));
+ std::vector<throwing_data<int> > v;
+ test_copy_ctor_exception_for_strong_guarantee(v, in);
+ }
+ check_new_delete_called();
+
+ {
+ int a[] = {1, 2, 3, 4, 5};
+ std::vector<int> in(a, a + sizeof(a) / sizeof(a[0]));
+ std::vector<throwing_data<int>, min_allocator<throwing_data<int> > > v;
+ test_copy_ctor_exception_for_strong_guarantee(v, in);
+ }
+ check_new_delete_called();
+
+ {
+ std::vector<int> in(10, 42);
+ std::vector<throwing_data<int>, safe_allocator<throwing_data<int> > > v;
+ test_copy_ctor_exception_for_strong_guarantee(v, in);
+ }
+ check_new_delete_called();
+
+ {
+ std::vector<int> in(10, 42);
+ std::vector<throwing_data<int>, test_allocator<throwing_data<int> > > v;
+ test_copy_ctor_exception_for_strong_guarantee(v, in);
+ }
+ check_new_delete_called();
+
+ {
+ std::vector<int> in(10, 42);
+ std::vector<throwing_data<int>, limited_allocator<throwing_data<int>, 100> > v;
+ test_copy_ctor_exception_for_strong_guarantee(v, in);
+ }
+ check_new_delete_called();
+
+ { // Practical example: Testing with 100 randomly generated integers.
+ auto in = Rnd::getRandomIntegerInputs(100);
+ std::vector<throwing_data<int> > v;
+ test_copy_ctor_exception_for_strong_guarantee(v, in);
+ }
+ check_new_delete_called();
+
+ { // Practical example: Testing with 100 randomly generated strings, each 256 characters long.
+ std::vector<std::string> in = Rnd::getRandomStringInputsWithLength(100, 256);
+ std::vector<throwing_data<std::string> > v;
+ test_copy_ctor_exception_for_strong_guarantee(v, in);
+ }
+ check_new_delete_called();
+}
+
+#if TEST_STD_VER >= 11
+
+// Check that if T is Cpp17MoveInsertible && !Cpp17CopyInsertible, and T's move-ctor is not noexcept, then
+// std::vector::shrink_to_fit only provides basic guarantee.
+void test_move_ctor_exceptions() {
+ {
+ std::vector<int> in{0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
+ std::vector<move_only_throwing_t<int>> v;
+ test_move_ctor_exception_for_basic_guarantee(v, in);
+ }
+ check_new_delete_called();
+
+# if TEST_STD_VER >= 23
+ {
+ std::vector<int> in{0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
+ std::vector<move_only_throwing_t<int>, increasing_allocator<move_only_throwing_t<int>>> v;
+ test_move_ctor_exception_for_basic_guarantee(v, in);
+ }
+ check_new_delete_called();
+# endif
+
+ {
+ // Practical example: Testing with 100 randomly generated strings, each 256 characters long.
+ std::vector<std::string> in = Rnd::getRandomStringInputsWithLength(100, 256);
+ std::vector<move_only_throwing_t<std::string> > v;
+ test_move_ctor_exception_for_basic_guarantee(v, in);
+ }
+ check_new_delete_called();
+}
+
+#endif
+
+int main(int, char**) {
+ Rnd::initializeSeed();
+ test_allocation_exceptions();
+ test_copy_ctor_exceptions();
+#if TEST_STD_VER >= 11
+ test_move_ctor_exceptions();
+#endif
+}
diff --git a/libcxx/test/support/test_allocator.h b/libcxx/test/support/test_allocator.h
index dcd15332ca304f..cf6952fcd788e6 100644
--- a/libcxx/test/support/test_allocator.h
+++ b/libcxx/test/support/test_allocator.h
@@ -27,45 +27,45 @@ TEST_CONSTEXPR_CXX20 inline typename std::allocator_traits<Alloc>::size_type all
}
struct test_allocator_statistics {
- int time_to_throw = 0;
- int throw_after = INT_MAX;
+ int time_to_throw = 0;
+ int throw_after = INT_MAX;
int count = 0; // the number of active instances
int alloc_count = 0; // the number of allocations not deallocating
int allocated_size = 0; // the size of allocated elements
int construct_count = 0; // the number of times that ::construct was called
- int destroy_count = 0; // the number of times that ::destroy was called
- int copied = 0;
- int moved = 0;
- int converted = 0;
+ int destroy_count = 0; // the number of times that ::destroy was called
+ int copied = 0;
+ int moved = 0;
+ int converted = 0;
TEST_CONSTEXPR_CXX14 void clear() {
assert(count == 0 && "clearing leaking allocator data?");
- count = 0;
- time_to_throw = 0;
- alloc_count = 0;
+ count = 0;
+ time_to_throw = 0;
+ alloc_count = 0;
allocated_size = 0;
construct_count = 0;
- destroy_count = 0;
- throw_after = INT_MAX;
+ destroy_count = 0;
+ throw_after = INT_MAX;
clear_ctor_counters();
}
TEST_CONSTEXPR_CXX14 void clear_ctor_counters() {
- copied = 0;
- moved = 0;
+ copied = 0;
+ moved = 0;
converted = 0;
}
};
struct test_alloc_base {
TEST_CONSTEXPR static const int destructed_value = -1;
- TEST_CONSTEXPR static const int moved_value = INT_MAX;
+ TEST_CONSTEXPR static const int moved_value = INT_MAX;
};
template <class T>
class test_allocator {
- int data_ = 0; // participates in equality
- int id_ = 0; // unique identifier, doesn't participate in equality
+ int data_ = 0; // participates in equality
+ int id_ = 0; // unique identifier, doesn't participate in equality
test_allocator_statistics* stats_ = nullptr;
template <class U>
@@ -95,7 +95,8 @@ class test_allocator {
TEST_CONSTEXPR explicit test_allocator(int data) TEST_NOEXCEPT : data_(data) {}
TEST_CONSTEXPR_CXX14 explicit test_allocator(int data, test_allocator_statistics* stats) TEST_NOEXCEPT
- : data_(data), stats_(stats) {
+ : data_(data),
+ stats_(stats) {
if (stats != nullptr)
++stats_->count;
}
@@ -103,13 +104,17 @@ class test_allocator {
TEST_CONSTEXPR explicit test_allocator(int data, int id) TEST_NOEXCEPT : data_(data), id_(id) {}
TEST_CONSTEXPR_CXX14 explicit test_allocator(int data, int id, test_allocator_statistics* stats) TEST_NOEXCEPT
- : data_(data), id_(id), stats_(stats) {
+ : data_(data),
+ id_(id),
+ stats_(stats) {
if (stats_ != nullptr)
++stats_->count;
}
TEST_CONSTEXPR_CXX14 test_allocator(const test_allocator& a) TEST_NOEXCEPT
- : data_(a.data_), id_(a.id_), stats_(a.stats_) {
+ : data_(a.data_),
+ id_(a.id_),
+ stats_(a.stats_) {
assert(a.data_ != test_alloc_base::destructed_value && a.id_ != test_alloc_base::destructed_value &&
"copying from destroyed allocator");
if (stats_ != nullptr) {
@@ -130,7 +135,9 @@ class test_allocator {
template <class U>
TEST_CONSTEXPR_CXX14 test_allocator(const test_allocator<U>& a) TEST_NOEXCEPT
- : data_(a.data_), id_(a.id_), stats_(a.stats_) {
+ : data_(a.data_),
+ id_(a.id_),
+ stats_(a.stats_) {
if (stats_ != nullptr) {
++stats_->count;
++stats_->converted;
@@ -143,7 +150,7 @@ class test_allocator {
if (stats_ != nullptr)
--stats_->count;
data_ = test_alloc_base::destructed_value;
- id_ = test_alloc_base::destructed_value;
+ id_ = test_alloc_base::destructed_value;
}
TEST_CONSTEXPR pointer address(reference x) const { return &x; }
@@ -197,8 +204,8 @@ class test_allocator {
template <>
class test_allocator<void> {
- int data_ = 0;
- int id_ = 0;
+ int data_ = 0;
+ int id_ = 0;
test_allocator_statistics* stats_ = nullptr;
template <class U>
@@ -223,27 +230,30 @@ class test_allocator<void> {
TEST_CONSTEXPR explicit test_allocator(int data) TEST_NOEXCEPT : data_(data) {}
TEST_CONSTEXPR explicit test_allocator(int data, test_allocator_statistics* stats) TEST_NOEXCEPT
- : data_(data), stats_(stats)
- {}
+ : data_(data),
+ stats_(stats) {}
TEST_CONSTEXPR explicit test_allocator(int data, int id) : data_(data), id_(id) {}
TEST_CONSTEXPR_CXX14 explicit test_allocator(int data, int id, test_allocator_statistics* stats) TEST_NOEXCEPT
- : data_(data), id_(id), stats_(stats)
- {}
+ : data_(data),
+ id_(id),
+ stats_(stats) {}
TEST_CONSTEXPR_CXX14 explicit test_allocator(const test_allocator& a) TEST_NOEXCEPT
- : data_(a.data_), id_(a.id_), stats_(a.stats_)
- {}
+ : data_(a.data_),
+ id_(a.id_),
+ stats_(a.stats_) {}
template <class U>
TEST_CONSTEXPR_CXX14 test_allocator(const test_allocator<U>& a) TEST_NOEXCEPT
- : data_(a.data_), id_(a.id_), stats_(a.stats_)
- {}
+ : data_(a.data_),
+ id_(a.id_),
+ stats_(a.stats_) {}
TEST_CONSTEXPR_CXX20 ~test_allocator() TEST_NOEXCEPT {
data_ = test_alloc_base::destructed_value;
- id_ = test_alloc_base::destructed_value;
+ id_ = test_alloc_base::destructed_value;
}
TEST_CONSTEXPR int get_id() const { return id_; }
@@ -310,9 +320,9 @@ struct Tag_X {
TEST_CONSTEXPR Tag_X(Ctor_Tag, Args&&...) {}
// not DefaultConstructible, CopyConstructible or MoveConstructible.
- Tag_X() = delete;
+ Tag_X() = delete;
Tag_X(const Tag_X&) = delete;
- Tag_X(Tag_X&&) = delete;
+ Tag_X(Tag_X&&) = delete;
// CopyAssignable.
TEST_CONSTEXPR_CXX14 Tag_X& operator=(const Tag_X&) { return *this; };
@@ -329,7 +339,7 @@ struct Tag_X {
template <typename T>
class TaggingAllocator {
public:
- using value_type = T;
+ using value_type = T;
TaggingAllocator() = default;
template <typename U>
@@ -356,13 +366,13 @@ class TaggingAllocator {
template <std::size_t MaxAllocs>
struct limited_alloc_handle {
std::size_t outstanding_ = 0;
- void* last_alloc_ = nullptr;
+ void* last_alloc_ = nullptr;
template <class T>
TEST_CONSTEXPR_CXX20 T* allocate(std::size_t N) {
if (N + outstanding_ > MaxAllocs)
TEST_THROW(std::bad_alloc());
- auto alloc = std::allocator<T>().allocate(N);
+ auto alloc = std::allocator<T>().allocate(N);
last_alloc_ = alloc;
outstanding_ += N;
return alloc;
More information about the libcxx-commits
mailing list