[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
Fri Nov 29 15:11:48 PST 2024


https://github.com/winner245 updated https://github.com/llvm/llvm-project/pull/118141

>From 813d43d43e900105d47579f4fadc6dc94c312fba 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  |  46 +++++
 .../vector/vector.capacity/reserve.pass.cpp   | 180 +++++++-----------
 .../reserve_exceptions.pass.cpp               | 146 ++++++++++++++
 .../resize_exceptions.pass.cpp                |  44 +++++
 .../shrink_to_fit_exceptions.pass.cpp         |  44 +++++
 5 files changed, 345 insertions(+), 115 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_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..14671a997a6ed7 100644
--- a/libcxx/test/std/containers/sequences/vector/common.h
+++ b/libcxx/test/std/containers/sequences/vector/common.h
@@ -16,6 +16,52 @@
 
 #include "count_new.h"
 
+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;
+  }
+
+  throwing_data(throwing_data&& rhs) : data_(std::move(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=(throwing_data&& rhs) {
+    if (this == &rhs)
+      return *this;
+    data_              = std::move(rhs.data_);
+    throw_after_n_     = rhs.throw_after_n_;
+    rhs.throw_after_n_ = nullptr;
+    if (throw_after_n_ == nullptr || *throw_after_n_ == 0)
+      throw 1;
+    --*throw_after_n_;
+    return *this;
+  }
+};
+
 template <class T>
 struct throwing_allocator {
   using value_type      = T;
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..21228e04c65926
--- /dev/null
+++ b/libcxx/test/std/containers/sequences/vector/vector.capacity/reserve_exceptions.pass.cpp
@@ -0,0 +1,146 @@
+//===----------------------------------------------------------------------===//
+//
+// 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::reserve provides strong exception guarantees
+
+#include <cstddef>
+#include <memory>
+#include <stdexcept>
+#include <type_traits>
+#include <vector>
+
+#include "../common.h"
+#include "count_new.h"
+#include "min_allocator.h"
+#include "test_allocator.h"
+#include "test_iterators.h"
+
+template <typename T = int, typename Alloc = std::allocator<T> >
+void test_allocation_exception(std::vector<T, Alloc>& v) {
+  std::vector<T, Alloc> old_vector = v;
+  T* old_data                      = v.data();
+  std::size_t old_size             = v.size();
+  std::size_t old_cap              = v.capacity();
+  std::size_t new_cap              = v.max_size() + 1;
+  try {
+    v.reserve(new_cap);
+  } catch (std::length_error&) {
+    assert(v.size() == old_size);
+    assert(v.capacity() == old_cap);
+    assert(v.data() == old_data);
+    assert(v == old_vector);
+  }
+}
+
+template <typename T = int, typename Alloc = std::allocator<throwing_data<T> > >
+void test_construction_exception(std::vector<throwing_data<T>, Alloc>& v, const std::vector<T>& in) {
+  assert(v.empty() && !in.empty());
+  int throw_after = 2 * in.size() - 1;
+  v.reserve(in.size());
+  for (std::size_t i = 0; i < in.size(); ++i)
+    v.emplace_back(in[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 (int) {
+    assert(v.size() == old_size);
+    assert(v.capacity() == old_cap);
+    assert(v.data() == old_data);
+    for (std::size_t i = 0; i < in.size(); ++i)
+      assert(v[i].data_ == in[i]);
+  }
+}
+
+void test_allocation_exceptions() {
+  {
+    std::vector<int> v;
+    test_allocation_exception(v);
+  }
+  check_new_delete_called();
+
+  {
+    std::vector<int> v(10, 42);
+    test_allocation_exception(v);
+  }
+  check_new_delete_called();
+
+  {
+    std::vector<int, min_allocator<int> > v(10, 42);
+    test_allocation_exception(v);
+  }
+  check_new_delete_called();
+
+  {
+    std::vector<int, safe_allocator<int> > v(10, 42);
+    test_allocation_exception(v);
+  }
+  check_new_delete_called();
+
+  {
+    std::vector<int, test_allocator<int> > v(10, 42);
+    test_allocation_exception(v);
+  }
+  check_new_delete_called();
+
+  {
+    std::vector<int, limited_allocator<int, 100> > v(10, 42);
+    test_allocation_exception(v);
+  }
+  check_new_delete_called();
+}
+
+void test_construction_exceptions() {
+  {
+    std::vector<throwing_data<int> > v;
+    int a[] = {1, 2, 3, 4, 5};
+    std::vector<int> in(a, a + sizeof(a) / sizeof(a[0]));
+    test_construction_exception(v, in);
+  }
+  check_new_delete_called();
+
+  {
+    std::vector<throwing_data<int>, min_allocator<throwing_data<int> > > v;
+    int a[] = {1, 2, 3, 4, 5};
+    std::vector<int> in(a, a + sizeof(a) / sizeof(a[0]));
+    test_construction_exception(v, in);
+  }
+  check_new_delete_called();
+
+  {
+    std::vector<throwing_data<int>, safe_allocator<throwing_data<int> > > v;
+    std::vector<int> in(10, 42);
+    test_construction_exception(v, in);
+  }
+  check_new_delete_called();
+
+  {
+    std::vector<throwing_data<int>, test_allocator<throwing_data<int> > > v;
+    std::vector<int> in(10, 42);
+    test_construction_exception(v, in);
+  }
+  check_new_delete_called();
+
+  {
+    std::vector<throwing_data<int>, limited_allocator<throwing_data<int>, 100> > v;
+    std::vector<int> in(10, 42);
+    test_construction_exception(v, in);
+  }
+  check_new_delete_called();
+}
+
+int main(int, char**) {
+  test_allocation_exceptions();
+  test_construction_exceptions();
+}
diff --git a/libcxx/test/std/containers/sequences/vector/vector.capacity/resize_exceptions.pass.cpp b/libcxx/test/std/containers/sequences/vector/vector.capacity/resize_exceptions.pass.cpp
new file mode 100644
index 00000000000000..afedf59028e09c
--- /dev/null
+++ b/libcxx/test/std/containers/sequences/vector/vector.capacity/resize_exceptions.pass.cpp
@@ -0,0 +1,44 @@
+//===----------------------------------------------------------------------===//
+//
+// 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::resize provides strong exception guarantees
+
+#include <cstddef>
+#include <memory>
+#include <stdexcept>
+#include <type_traits>
+#include <vector>
+
+#include "../common.h"
+#include "count_new.h"
+#include "min_allocator.h"
+#include "test_allocator.h"
+#include "test_iterators.h"
+
+template <typename T = int, typename Alloc = std::allocator<T> >
+void test_allocation_exception(std::vector<T, Alloc>& v) {
+  (void)v;
+  (void)in;
+}
+
+template <typename T = int, typename Alloc = std::allocator<throwing_data<T> > >
+void test_construction_exception(std::vector<throwing_data<T>, Alloc>& v, const std::vector<T>& in) {
+  (void)v;
+  (void)in;
+}
+
+void test_allocation_exceptions() {}
+
+void test_construction_exceptions() {}
+
+int main(int, char**) {
+  test_allocation_exceptions();
+  test_construction_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..44a8211c1a5973
--- /dev/null
+++ b/libcxx/test/std/containers/sequences/vector/vector.capacity/shrink_to_fit_exceptions.pass.cpp
@@ -0,0 +1,44 @@
+//===----------------------------------------------------------------------===//
+//
+// 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::shrink_to_fit provides strong exception guarantees
+
+#include <cstddef>
+#include <memory>
+#include <stdexcept>
+#include <type_traits>
+#include <vector>
+
+#include "../common.h"
+#include "count_new.h"
+#include "min_allocator.h"
+#include "test_allocator.h"
+#include "test_iterators.h"
+
+template <typename T = int, typename Alloc = std::allocator<T> >
+void test_allocation_exception(std::vector<T, Alloc>& v) {
+  (void)v;
+  (void)in;
+}
+
+template <typename T = int, typename Alloc = std::allocator<throwing_data<T> > >
+void test_construction_exception(std::vector<throwing_data<T>, Alloc>& v, const std::vector<T>& in) {
+  (void)v;
+  (void)in;
+}
+
+void test_allocation_exceptions() {}
+
+void test_construction_exceptions() {}
+
+int main(int, char**) {
+  test_allocation_exceptions();
+  test_construction_exceptions();
+}



More information about the libcxx-commits mailing list