[libcxx-commits] [libcxx] [libc++][test] Add exception tests for vector capacity operations (PR #118141)
Louis Dionne via libcxx-commits
libcxx-commits at lists.llvm.org
Mon Dec 16 07:50:31 PST 2024
================
@@ -0,0 +1,265 @@
+//===----------------------------------------------------------------------===//
+//
+// 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 <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) {
+ assert(v.size() == values.size());
+ 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 (...) {
+ }
+
+ // 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);
+
+ try {
+ v.shrink_to_fit();
+ } catch (...) {
+ }
+
+ // 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
----------------
ldionne wrote:
We always need to `return 0;` from `main()`. That's because under `-ffreestanding`, `main` is not treated as a special function so this would trigger an error that there's no `return` statement for a function that's supposed to return `int`.
https://github.com/llvm/llvm-project/pull/118141
More information about the libcxx-commits
mailing list