[libcxx-commits] [libcxx] d0b5165 - [libc++] Fix an exception safety issue in `forward_list` and add tests.
via libcxx-commits
libcxx-commits at lists.llvm.org
Wed Jul 12 10:11:41 PDT 2023
Author: varconst
Date: 2023-07-12T10:11:26-07:00
New Revision: d0b51657c259365750b3aada3bae23f19c96fdbc
URL: https://github.com/llvm/llvm-project/commit/d0b51657c259365750b3aada3bae23f19c96fdbc
DIFF: https://github.com/llvm/llvm-project/commit/d0b51657c259365750b3aada3bae23f19c96fdbc.diff
LOG: [libc++] Fix an exception safety issue in `forward_list` and add tests.
When inserting nodes into a forward list, each new node is allocated but
not constructed. The constructor was being called explicitly on the node
`value_` but the `next_` pointer remained uninitialized rather than
being set to null. This bug is only triggered in the cleanup code if an
exception is thrown -- upon successful creation of new nodes, the last
incorrect "next" value is overwritten to a correct pointer.
This issue was found due to new tests added in
https://reviews.llvm.org/D149830.
Differential Revision: https://reviews.llvm.org/D152327
Added:
libcxx/test/libcxx/memory/allocation_guard.pass.cpp
libcxx/test/std/containers/exception_safety_helpers.h
libcxx/test/std/containers/sequences/forwardlist/exception_safety.pass.cpp
Modified:
libcxx/include/__memory/allocation_guard.h
libcxx/include/forward_list
libcxx/test/std/containers/container.adaptors/from_range_container_adaptors.h
libcxx/test/std/containers/container.adaptors/push_range_container_adaptors.h
libcxx/test/std/containers/from_range_helpers.h
libcxx/test/std/containers/sequences/from_range_sequence_containers.h
libcxx/test/std/containers/sequences/insert_range_sequence_containers.h
Removed:
################################################################################
diff --git a/libcxx/include/__memory/allocation_guard.h b/libcxx/include/__memory/allocation_guard.h
index 1481f57717ef4b..f63b17430ed1b1 100644
--- a/libcxx/include/__memory/allocation_guard.h
+++ b/libcxx/include/__memory/allocation_guard.h
@@ -11,6 +11,7 @@
#define _LIBCPP___MEMORY_ALLOCATION_GUARD_H
#include <__config>
+#include <__memory/addressof.h>
#include <__memory/allocator_traits.h>
#include <__utility/move.h>
#include <cstddef>
@@ -58,9 +59,29 @@ struct __allocation_guard {
_LIBCPP_HIDE_FROM_ABI
~__allocation_guard() _NOEXCEPT {
- if (__ptr_ != nullptr) {
- allocator_traits<_Alloc>::deallocate(__alloc_, __ptr_, __n_);
+ __destroy();
+ }
+
+ _LIBCPP_HIDE_FROM_ABI __allocation_guard(const __allocation_guard&) = delete;
+ _LIBCPP_HIDE_FROM_ABI __allocation_guard(__allocation_guard&& __other) _NOEXCEPT
+ : __alloc_(std::move(__other.__alloc_))
+ , __n_(__other.__n_)
+ , __ptr_(__other.__ptr_) {
+ __other.__ptr_ = nullptr;
+ }
+
+ _LIBCPP_HIDE_FROM_ABI __allocation_guard& operator=(const __allocation_guard& __other) = delete;
+ _LIBCPP_HIDE_FROM_ABI __allocation_guard& operator=(__allocation_guard&& __other) _NOEXCEPT {
+ if (std::addressof(__other) != this) {
+ __destroy();
+
+ __alloc_ = std::move(__other.__alloc_);
+ __n_ = __other.__n_;
+ __ptr_ = __other.__ptr_;
+ __other.__ptr_ = nullptr;
}
+
+ return *this;
}
_LIBCPP_HIDE_FROM_ABI
@@ -76,6 +97,13 @@ struct __allocation_guard {
}
private:
+ _LIBCPP_HIDE_FROM_ABI
+ void __destroy() _NOEXCEPT {
+ if (__ptr_ != nullptr) {
+ allocator_traits<_Alloc>::deallocate(__alloc_, __ptr_, __n_);
+ }
+ }
+
_Alloc __alloc_;
_Size __n_;
_Pointer __ptr_;
diff --git a/libcxx/include/forward_list b/libcxx/include/forward_list
index 41829636a2a2bd..aac9d548ac935e 100644
--- a/libcxx/include/forward_list
+++ b/libcxx/include/forward_list
@@ -195,6 +195,7 @@ template <class T, class Allocator, class Predicate>
#include <__iterator/move_iterator.h>
#include <__iterator/next.h>
#include <__memory/addressof.h>
+#include <__memory/allocation_guard.h>
#include <__memory/allocator.h>
#include <__memory/allocator_destructor.h>
#include <__memory/allocator_traits.h>
@@ -292,6 +293,7 @@ struct __forward_begin_node
pointer __next_;
_LIBCPP_INLINE_VISIBILITY __forward_begin_node() : __next_(nullptr) {}
+ _LIBCPP_INLINE_VISIBILITY explicit __forward_begin_node(pointer __n) : __next_(__n) {}
_LIBCPP_INLINE_VISIBILITY
__begin_node_pointer __next_as_begin() const {
@@ -307,8 +309,13 @@ struct _LIBCPP_STANDALONE_DEBUG __forward_list_node
: public __begin_node_of<_Tp, _VoidPtr>
{
typedef _Tp value_type;
+ typedef __begin_node_of<_Tp, _VoidPtr> _Base;
+ typedef typename _Base::pointer _NodePtr;
value_type __value_;
+
+ _LIBCPP_HIDE_FROM_ABI __forward_list_node() = default;
+ _LIBCPP_HIDE_FROM_ABI __forward_list_node(const value_type& __v, _NodePtr __next) : _Base(__next), __value_(__v) {}
};
@@ -1247,14 +1254,20 @@ typename forward_list<_Tp, _Alloc>::iterator
forward_list<_Tp, _Alloc>::insert_after(const_iterator __p, size_type __n,
const value_type& __v)
{
+ using _Guard = __allocation_guard<__node_allocator>;
+
__begin_node_pointer __r = __p.__get_begin();
if (__n > 0)
{
__node_allocator& __a = base::__alloc();
- typedef __allocator_destructor<__node_allocator> _Dp;
- unique_ptr<__node, _Dp> __h(__node_traits::allocate(__a, 1), _Dp(__a, 1));
- __node_traits::construct(__a, _VSTD::addressof(__h->__value_), __v);
- __node_pointer __first = __h.release();
+
+ __node_pointer __first = nullptr;
+ {
+ _Guard __h(__a, 1);
+ __node_traits::construct(__a, std::addressof(__h.__get()->__value_), __v);
+ __h.__get()->__next_ = nullptr;
+ __first = __h.__release_ptr();
+ }
__node_pointer __last = __first;
#ifndef _LIBCPP_HAS_NO_EXCEPTIONS
try
@@ -1262,9 +1275,10 @@ forward_list<_Tp, _Alloc>::insert_after(const_iterator __p, size_type __n,
#endif // _LIBCPP_HAS_NO_EXCEPTIONS
for (--__n; __n != 0; --__n, __last = __last->__next_)
{
- __h.reset(__node_traits::allocate(__a, 1));
- __node_traits::construct(__a, _VSTD::addressof(__h->__value_), __v);
- __last->__next_ = __h.release();
+ _Guard __h(__a, 1);
+ __node_traits::construct(__a, std::addressof(__h.__get()->__value_), __v);
+ __h.__get()->__next_ = nullptr;
+ __last->__next_ = __h.__release_ptr();
}
#ifndef _LIBCPP_HAS_NO_EXCEPTIONS
}
@@ -1293,14 +1307,19 @@ __enable_if_t<__has_input_iterator_category<_InputIterator>::value, typename for
forward_list<_Tp, _Alloc>::insert_after(const_iterator __p,
_InputIterator __f, _InputIterator __l)
{
+ using _Guard = __allocation_guard<__node_allocator>;
+
__begin_node_pointer __r = __p.__get_begin();
if (__f != __l)
{
__node_allocator& __a = base::__alloc();
- typedef __allocator_destructor<__node_allocator> _Dp;
- unique_ptr<__node, _Dp> __h(__node_traits::allocate(__a, 1), _Dp(__a, 1));
- __node_traits::construct(__a, _VSTD::addressof(__h->__value_), *__f);
- __node_pointer __first = __h.release();
+ __node_pointer __first = nullptr;
+ {
+ _Guard __h(__a, 1);
+ __node_traits::construct(__a, std::addressof(__h.__get()->__value_), *__f);
+ __h.__get()->__next_ = nullptr;
+ __first = __h.__release_ptr();
+ }
__node_pointer __last = __first;
#ifndef _LIBCPP_HAS_NO_EXCEPTIONS
try
@@ -1308,9 +1327,10 @@ forward_list<_Tp, _Alloc>::insert_after(const_iterator __p,
#endif // _LIBCPP_HAS_NO_EXCEPTIONS
for (++__f; __f != __l; ++__f, ((void)(__last = __last->__next_)))
{
- __h.reset(__node_traits::allocate(__a, 1));
- __node_traits::construct(__a, _VSTD::addressof(__h->__value_), *__f);
- __last->__next_ = __h.release();
+ _Guard __h(__a, 1);
+ __node_traits::construct(__a, std::addressof(__h.__get()->__value_), *__f);
+ __h.__get()->__next_ = nullptr;
+ __last->__next_ = __h.__release_ptr();
}
#ifndef _LIBCPP_HAS_NO_EXCEPTIONS
}
diff --git a/libcxx/test/libcxx/memory/allocation_guard.pass.cpp b/libcxx/test/libcxx/memory/allocation_guard.pass.cpp
new file mode 100644
index 00000000000000..4b2f7abe9159b7
--- /dev/null
+++ b/libcxx/test/libcxx/memory/allocation_guard.pass.cpp
@@ -0,0 +1,192 @@
+//===----------------------------------------------------------------------===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+//
+
+// <memory>
+
+// To allow checking that self-move works correctly.
+// ADDITIONAL_COMPILE_FLAGS: -Wno-self-move
+
+// template<class _Alloc>
+// struct __allocation_guard;
+
+#include <cassert>
+#include <memory>
+#include <type_traits>
+#include <utility>
+
+#include "test_allocator.h"
+
+using A = test_allocator<int>;
+
+// A trimmed-down version of `test_allocator` that is copy-assignable (in general allocators don't have to support copy
+// assignment).
+template <class T>
+struct AssignableAllocator {
+ using size_type = unsigned;
+ using
diff erence_type = int;
+ using value_type = T;
+ using pointer = value_type*;
+ using const_pointer = const value_type*;
+ using reference = typename std::add_lvalue_reference<value_type>::type;
+ using const_reference = typename std::add_lvalue_reference<const value_type>::type;
+
+ template <class U>
+ struct rebind {
+ using other = test_allocator<U>;
+ };
+
+ test_allocator_statistics* stats_ = nullptr;
+
+ explicit AssignableAllocator(test_allocator_statistics& stats) : stats_(&stats) {
+ ++stats_->count;
+ }
+
+ TEST_CONSTEXPR_CXX14 AssignableAllocator(const AssignableAllocator& rhs) TEST_NOEXCEPT
+ : stats_(rhs.stats_) {
+ if (stats_ != nullptr) {
+ ++stats_->count;
+ ++stats_->copied;
+ }
+ }
+
+ TEST_CONSTEXPR_CXX14 AssignableAllocator& operator=(const AssignableAllocator& rhs) TEST_NOEXCEPT {
+ stats_ = rhs.stats_;
+ if (stats_ != nullptr) {
+ ++stats_->count;
+ ++stats_->copied;
+ }
+
+ return *this;
+ }
+
+ TEST_CONSTEXPR_CXX14 pointer allocate(size_type n, const void* = nullptr) {
+ if (stats_ != nullptr) {
+ ++stats_->alloc_count;
+ }
+ return std::allocator<value_type>().allocate(n);
+ }
+
+ TEST_CONSTEXPR_CXX14 void deallocate(pointer p, size_type s) {
+ if (stats_ != nullptr) {
+ --stats_->alloc_count;
+ }
+ std::allocator<value_type>().deallocate(p, s);
+ }
+
+ TEST_CONSTEXPR size_type max_size() const TEST_NOEXCEPT { return UINT_MAX / sizeof(T); }
+
+ template <class U>
+ TEST_CONSTEXPR_CXX20 void construct(pointer p, U&& val) {
+ if (stats_ != nullptr)
+ ++stats_->construct_count;
+#if TEST_STD_VER > 17
+ std::construct_at(std::to_address(p), std::forward<U>(val));
+#else
+ ::new (static_cast<void*>(p)) T(std::forward<U>(val));
+#endif
+ }
+
+ TEST_CONSTEXPR_CXX14 void destroy(pointer p) {
+ if (stats_ != nullptr) {
+ ++stats_->destroy_count;
+ }
+ p->~T();
+ }
+};
+
+// Move-only.
+static_assert(!std::is_copy_constructible<std::__allocation_guard<A> >::value, "");
+static_assert(std::is_move_constructible<std::__allocation_guard<A> >::value, "");
+static_assert(!std::is_copy_assignable<std::__allocation_guard<A> >::value, "");
+static_assert(std::is_move_assignable<std::__allocation_guard<A> >::value, "");
+
+int main(int, char**) {
+ const int size = 42;
+
+ { // The constructor allocates using the given allocator.
+ test_allocator_statistics stats;
+ std::__allocation_guard<A> guard(A(&stats), size);
+ assert(stats.alloc_count == 1);
+ assert(guard.__get() != nullptr);
+ }
+
+ { // The destructor deallocates using the given allocator.
+ test_allocator_statistics stats;
+ {
+ std::__allocation_guard<A> guard(A(&stats), size);
+ assert(stats.alloc_count == 1);
+ }
+ assert(stats.alloc_count == 0);
+ }
+
+ { // `__release_ptr` prevents deallocation.
+ test_allocator_statistics stats;
+ A alloc(&stats);
+ int* ptr = nullptr;
+ {
+ std::__allocation_guard<A> guard(alloc, size);
+ assert(stats.alloc_count == 1);
+ ptr = guard.__release_ptr();
+ }
+ assert(stats.alloc_count == 1);
+ alloc.deallocate(ptr, size);
+ }
+
+ { // Using the move constructor doesn't lead to double deletion.
+ test_allocator_statistics stats;
+ {
+ std::__allocation_guard<A> guard1(A(&stats), size);
+ assert(stats.alloc_count == 1);
+ auto* ptr1 = guard1.__get();
+
+ std::__allocation_guard<A> guard2 = std::move(guard1);
+ assert(stats.alloc_count == 1);
+ assert(guard1.__get() == nullptr);
+ assert(guard2.__get() == ptr1);
+ }
+ assert(stats.alloc_count == 0);
+ }
+
+ { // Using the move assignment operator doesn't lead to double deletion.
+ using A2 = AssignableAllocator<int>;
+
+ test_allocator_statistics stats;
+ {
+ std::__allocation_guard<A2> guard1(A2(stats), size);
+ assert(stats.alloc_count == 1);
+ std::__allocation_guard<A2> guard2(A2(stats), size);
+ assert(stats.alloc_count == 2);
+ auto* ptr1 = guard1.__get();
+
+ guard2 = std::move(guard1);
+ assert(stats.alloc_count == 1);
+ assert(guard1.__get() == nullptr);
+ assert(guard2.__get() == ptr1);
+ }
+ assert(stats.alloc_count == 0);
+ }
+
+ { // Self-assignment is a no-op.
+ using A2 = AssignableAllocator<int>;
+
+ test_allocator_statistics stats;
+ {
+ std::__allocation_guard<A2> guard(A2(stats), size);
+ assert(stats.alloc_count == 1);
+ auto* ptr = guard.__get();
+
+ guard = std::move(guard);
+ assert(stats.alloc_count == 1);
+ assert(guard.__get() == ptr);
+ }
+ assert(stats.alloc_count == 0);
+ }
+
+ return 0;
+}
diff --git a/libcxx/test/std/containers/container.adaptors/from_range_container_adaptors.h b/libcxx/test/std/containers/container.adaptors/from_range_container_adaptors.h
index 0ad0b18f575c0d..8bd3ac632cfc95 100644
--- a/libcxx/test/std/containers/container.adaptors/from_range_container_adaptors.h
+++ b/libcxx/test/std/containers/container.adaptors/from_range_container_adaptors.h
@@ -17,6 +17,7 @@
#include <utility>
#include <vector>
+#include "../exception_safety_helpers.h"
#include "../from_range_helpers.h"
#include "MoveOnly.h"
#include "almost_satisfies_types.h"
@@ -192,18 +193,11 @@ constexpr void test_container_adaptor_move_only() {
template <template <class ...> class Adaptor>
void test_exception_safety_throwing_copy() {
#if !defined(TEST_HAS_NO_EXCEPTIONS)
- using T = ThrowingCopy<3>;
- T::reset();
- T in[5];
-
- try {
- Adaptor<T, std::vector<T>> c(std::from_range, in);
- assert(false); // The constructor call above should throw.
-
- } catch (int) {
- assert(T::created_by_copying == 3);
- assert(T::destroyed == 2); // No destructor call for the partially-constructed element.
- }
+ constexpr int ThrowOn = 3;
+ using T = ThrowingCopy<ThrowOn>;
+ test_exception_safety_throwing_copy<ThrowOn, /*Size=*/5>([](T* from, T* to) {
+ [[maybe_unused]] Adaptor<T, std::vector<T>> c(std::from_range, std::ranges::subrange(from, to));
+ });
#endif
}
diff --git a/libcxx/test/std/containers/container.adaptors/push_range_container_adaptors.h b/libcxx/test/std/containers/container.adaptors/push_range_container_adaptors.h
index 37b166b7df2899..19e63d23884b27 100644
--- a/libcxx/test/std/containers/container.adaptors/push_range_container_adaptors.h
+++ b/libcxx/test/std/containers/container.adaptors/push_range_container_adaptors.h
@@ -18,6 +18,7 @@
#include <type_traits>
#include <vector>
+#include "../exception_safety_helpers.h"
#include "../from_range_helpers.h"
#include "../insert_range_helpers.h"
#include "MoveOnly.h"
@@ -263,19 +264,12 @@ void test_push_range_inserter_choice(bool is_result_heapified = false) {
template <template <class ...> class Container>
void test_push_range_exception_safety_throwing_copy() {
#if !defined(TEST_HAS_NO_EXCEPTIONS)
- using T = ThrowingCopy<3>;
- T::reset();
- T in[5];
-
- try {
+ constexpr int ThrowOn = 3;
+ using T = ThrowingCopy<ThrowOn>;
+ test_exception_safety_throwing_copy<ThrowOn, /*Size=*/5>([](auto* from, auto* to) {
Container<T> c;
- c.push_range(in);
- assert(false); // The function call above should throw.
-
- } catch (int) {
- assert(T::created_by_copying == 3);
- assert(T::destroyed == 2); // No destructor call for the partially-constructed element.
- }
+ c.push_range(std::ranges::subrange(from, to));
+ });
#endif
}
diff --git a/libcxx/test/std/containers/exception_safety_helpers.h b/libcxx/test/std/containers/exception_safety_helpers.h
new file mode 100644
index 00000000000000..08884160d19c9c
--- /dev/null
+++ b/libcxx/test/std/containers/exception_safety_helpers.h
@@ -0,0 +1,106 @@
+//===----------------------------------------------------------------------===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef SUPPORT_EXCEPTION_SAFETY_HELPERS_H
+#define SUPPORT_EXCEPTION_SAFETY_HELPERS_H
+
+#include <cassert>
+#include <cstddef>
+#include <functional>
+#include <utility>
+#include "test_macros.h"
+
+#if !defined(TEST_HAS_NO_EXCEPTIONS)
+template <int N>
+struct ThrowingCopy {
+ static bool throwing_enabled;
+ static int created_by_copying;
+ static int destroyed;
+ int x = 0; // Allows distinguishing between
diff erent instances.
+
+ ThrowingCopy() = default;
+ ThrowingCopy(int value) : x(value) {}
+ ~ThrowingCopy() {
+ ++destroyed;
+ }
+
+ ThrowingCopy(const ThrowingCopy& other) : x(other.x) {
+ ++created_by_copying;
+ if (throwing_enabled && created_by_copying == N) {
+ throw -1;
+ }
+ }
+
+ // Defined to silence GCC warnings. For test purposes, only copy construction is considered `created_by_copying`.
+ ThrowingCopy& operator=(const ThrowingCopy& other) {
+ x = other.x;
+ return *this;
+ }
+
+ friend bool operator==(const ThrowingCopy& lhs, const ThrowingCopy& rhs) { return lhs.x == rhs.x; }
+ friend bool operator<(const ThrowingCopy& lhs, const ThrowingCopy& rhs) { return lhs.x < rhs.x; }
+
+ static void reset() {
+ created_by_copying = destroyed = 0;
+ }
+};
+
+template <int N>
+bool ThrowingCopy<N>::throwing_enabled = true;
+template <int N>
+int ThrowingCopy<N>::created_by_copying = 0;
+template <int N>
+int ThrowingCopy<N>::destroyed = 0;
+
+template <int N>
+struct std::hash<ThrowingCopy<N>> {
+ std::size_t operator()(const ThrowingCopy<N>& value) const {
+ return value.x;
+ }
+};
+
+template <int ThrowOn, int Size, class Func>
+void test_exception_safety_throwing_copy(Func&& func) {
+ using T = ThrowingCopy<ThrowOn>;
+ T::reset();
+ T in[Size];
+
+ try {
+ func(in, in + Size);
+ assert(false); // The function call above should throw.
+
+ } catch (int) {
+ assert(T::created_by_copying == ThrowOn);
+ assert(T::destroyed == ThrowOn - 1); // No destructor call for the partially-constructed element.
+ }
+}
+
+// Destroys the container outside the user callback to avoid destroying extra elements upon throwing (which would
+// complicate asserting that the expected number of elements was destroyed).
+template <class Container, int ThrowOn, int Size, class Func>
+void test_exception_safety_throwing_copy_container(Func&& func) {
+ using T = ThrowingCopy<ThrowOn>;
+ T::throwing_enabled = false;
+ T in[Size];
+ Container c(in, in + Size);
+ T::throwing_enabled = true;
+ T::reset();
+
+ try {
+ func(std::move(c));
+ assert(false); // The function call above should throw.
+
+ } catch (int) {
+ assert(T::created_by_copying == ThrowOn);
+ assert(T::destroyed == ThrowOn - 1); // No destructor call for the partially-constructed element.
+ }
+}
+
+#endif // !defined(TEST_HAS_NO_EXCEPTIONS)
+
+#endif // SUPPORT_EXCEPTION_SAFETY_HELPERS_H
diff --git a/libcxx/test/std/containers/from_range_helpers.h b/libcxx/test/std/containers/from_range_helpers.h
index 9bd72acfad09b3..7fff99da1e15e3 100644
--- a/libcxx/test/std/containers/from_range_helpers.h
+++ b/libcxx/test/std/containers/from_range_helpers.h
@@ -57,52 +57,6 @@ struct std::hash<KeyValue> {
};
#if !defined(TEST_HAS_NO_EXCEPTIONS)
-template <int N>
-struct ThrowingCopy {
- static bool throwing_enabled;
- static int created_by_copying;
- static int destroyed;
- int x = 0; // Allows distinguishing between
diff erent instances.
-
- ThrowingCopy() = default;
- ThrowingCopy(int value) : x(value) {}
- ~ThrowingCopy() {
- ++destroyed;
- }
-
- ThrowingCopy(const ThrowingCopy& other) : x(other.x) {
- ++created_by_copying;
- if (throwing_enabled && created_by_copying == N) {
- throw -1;
- }
- }
-
- // Defined to silence GCC warnings. For test purposes, only copy construction is considered `created_by_copying`.
- ThrowingCopy& operator=(const ThrowingCopy& other) {
- x = other.x;
- return *this;
- }
-
- friend auto operator<=>(const ThrowingCopy&, const ThrowingCopy&) = default;
-
- static void reset() {
- created_by_copying = destroyed = 0;
- }
-};
-
-template <int N>
-struct std::hash<ThrowingCopy<N>> {
- std::size_t operator()(const ThrowingCopy<N>& value) const {
- return value.x;
- }
-};
-
-template <int N>
-bool ThrowingCopy<N>::throwing_enabled = true;
-template <int N>
-int ThrowingCopy<N>::created_by_copying = 0;
-template <int N>
-int ThrowingCopy<N>::destroyed = 0;
template <class T>
struct ThrowingAllocator {
diff --git a/libcxx/test/std/containers/sequences/forwardlist/exception_safety.pass.cpp b/libcxx/test/std/containers/sequences/forwardlist/exception_safety.pass.cpp
new file mode 100644
index 00000000000000..2fe35df2cd412a
--- /dev/null
+++ b/libcxx/test/std/containers/sequences/forwardlist/exception_safety.pass.cpp
@@ -0,0 +1,153 @@
+//===----------------------------------------------------------------------===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+
+// <forward_list>
+
+// UNSUPPORTED: c++03, no-exceptions
+
+// TODO:
+// - throwing upon moving;
+// - initializer lists;
+// - throwing when constructing the element in place.
+
+// forward_list(size_type n, const value_type& v);
+// forward_list(size_type n, const value_type& v, const allocator_type& a);
+// template <class InputIterator>
+// forward_list(InputIterator first, InputIterator last);
+// template <class InputIterator>
+// forward_list(InputIterator first, InputIterator last, const allocator_type& a);
+// forward_list(const forward_list& x);
+// forward_list(const forward_list& x, const allocator_type& a);
+//
+// forward_list& operator=(const forward_list& x);
+//
+// template <class InputIterator>
+// void assign(InputIterator first, InputIterator last);
+// void assign(size_type n, const value_type& v);
+//
+// void push_front(const value_type& v);
+//
+// iterator insert_after(const_iterator p, const value_type& v);
+// iterator insert_after(const_iterator p, size_type n, const value_type& v);
+// template <class InputIterator>
+// iterator insert_after(const_iterator p,
+// InputIterator first, InputIterator last);
+//
+// void resize(size_type n, const value_type& v);
+
+#include <forward_list>
+
+#include <cassert>
+#include "../../exception_safety_helpers.h"
+
+int main(int, char**) {
+ {
+ constexpr int ThrowOn = 1;
+ constexpr int Size = 1;
+ using T = ThrowingCopy<ThrowOn>;
+
+ // void push_front(const value_type& v);
+ test_exception_safety_throwing_copy<ThrowOn, Size>([](T* from, T*){
+ std::forward_list<T> c;
+ c.push_front(*from);
+ });
+
+ // iterator insert_after(const_iterator p, const value_type& v);
+ test_exception_safety_throwing_copy</*ThrowOn=*/1, Size>([](T* from, T*){
+ std::forward_list<T> c;
+ c.insert_after(c.before_begin(), *from);
+ });
+ }
+
+ {
+ constexpr int ThrowOn = 3;
+ constexpr int Size = 5;
+ using T = ThrowingCopy<ThrowOn>;
+ using C = std::forward_list<T>;
+ using Alloc = std::allocator<T>;
+
+ // forward_list(size_type n, const value_type& v);
+ test_exception_safety_throwing_copy<ThrowOn, Size>([](T* from, T*){
+ std::forward_list<T> c(Size, *from);
+ (void)c;
+ });
+
+ // forward_list(size_type n, const value_type& v, const allocator_type& a);
+ test_exception_safety_throwing_copy<ThrowOn, Size>([](T* from, T*){
+ std::forward_list<T> c(Size, *from, Alloc());
+ (void)c;
+ });
+
+ // template <class InputIterator>
+ // forward_list(InputIterator first, InputIterator last);
+ test_exception_safety_throwing_copy<ThrowOn, Size>([](T* from, T* to){
+ std::forward_list<T> c(from, to);
+ (void)c;
+ });
+
+ // template <class InputIterator>
+ // forward_list(InputIterator first, InputIterator last, const allocator_type& a);
+ test_exception_safety_throwing_copy<ThrowOn, Size>([](T* from, T* to){
+ std::forward_list<T> c(from, to, Alloc());
+ (void)c;
+ });
+
+ // forward_list(const forward_list& x);
+ test_exception_safety_throwing_copy_container<C, ThrowOn, Size>([](C&& in) {
+ std::forward_list<T> c(in);
+ (void)c;
+ });
+
+ // forward_list(const forward_list& x, const allocator_type& a);
+ test_exception_safety_throwing_copy_container<C, ThrowOn, Size>([](C&& in) {
+ std::forward_list<T> c(in, Alloc());
+ (void)c;
+ });
+
+ // forward_list& operator=(const forward_list& x);
+ test_exception_safety_throwing_copy_container<C, ThrowOn, Size>([](C&& in) {
+ std::forward_list<T> c;
+ c = in;
+ });
+
+ // template <class InputIterator>
+ // void assign(InputIterator first, InputIterator last);
+ test_exception_safety_throwing_copy<ThrowOn, Size>([](T* from, T* to) {
+ std::forward_list<T> c;
+ c.assign(from, to);
+ });
+
+ // void assign(size_type n, const value_type& v);
+ test_exception_safety_throwing_copy<ThrowOn, Size>([](T* from, T*) {
+ std::forward_list<T> c;
+ c.assign(Size, *from);
+ });
+
+ // iterator insert_after(const_iterator p, size_type n, const value_type& v);
+ test_exception_safety_throwing_copy<ThrowOn, Size>([](T* from, T*) {
+ std::forward_list<T> c;
+ c.insert_after(c.before_begin(), Size, *from);
+ });
+
+ // template <class InputIterator>
+ // iterator insert_after(const_iterator p,
+ // InputIterator first, InputIterator last);
+ test_exception_safety_throwing_copy<ThrowOn, Size>([](T* from, T* to) {
+ std::forward_list<T> c;
+ c.insert_after(c.before_begin(), from, to);
+ });
+
+ // void resize(size_type n, const value_type& v);
+ test_exception_safety_throwing_copy<ThrowOn, Size>([](T* from, T*) {
+ std::forward_list<T> c;
+ c.resize(Size, *from);
+ });
+ }
+
+ return 0;
+}
diff --git a/libcxx/test/std/containers/sequences/from_range_sequence_containers.h b/libcxx/test/std/containers/sequences/from_range_sequence_containers.h
index 7106cb8077332a..859b9c535f7ba3 100644
--- a/libcxx/test/std/containers/sequences/from_range_sequence_containers.h
+++ b/libcxx/test/std/containers/sequences/from_range_sequence_containers.h
@@ -17,6 +17,7 @@
#include <ranges>
#include <utility>
+#include "../exception_safety_helpers.h"
#include "../from_range_helpers.h"
#include "MoveOnly.h"
#include "almost_satisfies_types.h"
@@ -125,18 +126,11 @@ constexpr void test_vector_bool(ValidateFunc validate) {
template <template <class ...> class Container>
void test_exception_safety_throwing_copy() {
#if !defined(TEST_HAS_NO_EXCEPTIONS)
- using T = ThrowingCopy<3>;
- T::reset();
- T in[5];
-
- try {
- Container<T> c(std::from_range, in);
- assert(false); // The constructor call above should throw.
-
- } catch (int) {
- assert(T::created_by_copying == 3);
- assert(T::destroyed == 2); // No destructor call for the partially-constructed element.
- }
+ constexpr int ThrowOn = 3;
+ using T = ThrowingCopy<ThrowOn>;
+ test_exception_safety_throwing_copy<ThrowOn, /*Size=*/5>([](T* from, T* to) {
+ [[maybe_unused]] Container<T> c(std::from_range, std::ranges::subrange(from, to));
+ });
#endif
}
diff --git a/libcxx/test/std/containers/sequences/insert_range_sequence_containers.h b/libcxx/test/std/containers/sequences/insert_range_sequence_containers.h
index 10fadca10eb85e..b77b2510eae230 100644
--- a/libcxx/test/std/containers/sequences/insert_range_sequence_containers.h
+++ b/libcxx/test/std/containers/sequences/insert_range_sequence_containers.h
@@ -18,6 +18,7 @@
#include <type_traits>
#include <vector>
+#include "../exception_safety_helpers.h"
#include "../from_range_helpers.h"
#include "../insert_range_helpers.h"
#include "MoveOnly.h"
@@ -675,19 +676,12 @@ constexpr void test_sequence_assign_range_move_only() {
template <template <class ...> class Container>
void test_insert_range_exception_safety_throwing_copy() {
#if !defined(TEST_HAS_NO_EXCEPTIONS)
- using T = ThrowingCopy<3>;
- T::reset();
- T in[5];
-
- try {
+ constexpr int ThrowOn = 3;
+ using T = ThrowingCopy<ThrowOn>;
+ test_exception_safety_throwing_copy<ThrowOn, /*Size=*/5>([](T* from, T* to) {
Container<T> c;
- c.insert_range(c.end(), in);
- assert(false); // The function call above should throw.
-
- } catch (int) {
- assert(T::created_by_copying == 3);
- assert(T::destroyed == 2); // No destructor call for the partially-constructed element.
- }
+ c.insert_range(c.end(), std::ranges::subrange(from, to));
+ });
#endif
}
@@ -713,19 +707,12 @@ void test_insert_range_exception_safety_throwing_allocator() {
template <template <class ...> class Container>
void test_prepend_range_exception_safety_throwing_copy() {
#if !defined(TEST_HAS_NO_EXCEPTIONS)
- using T = ThrowingCopy<3>;
- T::reset();
- T in[5];
-
- try {
+ constexpr int ThrowOn = 3;
+ using T = ThrowingCopy<ThrowOn>;
+ test_exception_safety_throwing_copy<ThrowOn, /*Size=*/5>([](T* from, T* to) {
Container<T> c;
- c.prepend_range(in);
- assert(false); // The function call above should throw.
-
- } catch (int) {
- assert(T::created_by_copying == 3);
- assert(T::destroyed == 2); // No destructor call for the partially-constructed element.
- }
+ c.prepend_range(std::ranges::subrange(from, to));
+ });
#endif
}
@@ -751,19 +738,12 @@ void test_prepend_range_exception_safety_throwing_allocator() {
template <template <class ...> class Container>
void test_append_range_exception_safety_throwing_copy() {
#if !defined(TEST_HAS_NO_EXCEPTIONS)
- using T = ThrowingCopy<3>;
- T::reset();
- T in[5];
-
- try {
+ constexpr int ThrowOn = 3;
+ using T = ThrowingCopy<ThrowOn>;
+ test_exception_safety_throwing_copy<ThrowOn, /*Size=*/5>([](T* from, T* to) {
Container<T> c;
- c.append_range(in);
- assert(false); // The function call above should throw.
-
- } catch (int) {
- assert(T::created_by_copying == 3);
- assert(T::destroyed == 2); // No destructor call for the partially-constructed element.
- }
+ c.append_range(std::ranges::subrange(from, to));
+ });
#endif
}
@@ -789,19 +769,12 @@ void test_append_range_exception_safety_throwing_allocator() {
template <template <class ...> class Container>
void test_assign_range_exception_safety_throwing_copy() {
#if !defined(TEST_HAS_NO_EXCEPTIONS)
- using T = ThrowingCopy<3>;
- T::reset();
- T in[5];
-
- try {
+ constexpr int ThrowOn = 3;
+ using T = ThrowingCopy<ThrowOn>;
+ test_exception_safety_throwing_copy<ThrowOn, /*Size=*/5>([](T* from, T* to) {
Container<T> c;
- c.assign_range(in);
- assert(false); // The function call above should throw.
-
- } catch (int) {
- assert(T::created_by_copying == 3);
- assert(T::destroyed == 2); // No destructor call for the partially-constructed element.
- }
+ c.assign_range(std::ranges::subrange(from, to));
+ });
#endif
}
More information about the libcxx-commits
mailing list