diff --git a/libcxx/include/forward_list b/libcxx/include/forward_list --- a/libcxx/include/forward_list +++ b/libcxx/include/forward_list @@ -1252,6 +1252,7 @@ __node_allocator& __a = base::__alloc(); typedef __allocator_destructor<__node_allocator> _Dp; unique_ptr<__node, _Dp> __h(__node_traits::allocate(__a, 1), _Dp(__a, 1)); + __h->__next_ = nullptr; __node_traits::construct(__a, _VSTD::addressof(__h->__value_), __v); __node_pointer __first = __h.release(); __node_pointer __last = __first; @@ -1262,6 +1263,7 @@ for (--__n; __n != 0; --__n, __last = __last->__next_) { __h.reset(__node_traits::allocate(__a, 1)); + __h->__next_ = nullptr; __node_traits::construct(__a, _VSTD::addressof(__h->__value_), __v); __last->__next_ = __h.release(); } @@ -1298,6 +1300,7 @@ __node_allocator& __a = base::__alloc(); typedef __allocator_destructor<__node_allocator> _Dp; unique_ptr<__node, _Dp> __h(__node_traits::allocate(__a, 1), _Dp(__a, 1)); + __h->__next_ = nullptr; __node_traits::construct(__a, _VSTD::addressof(__h->__value_), *__f); __node_pointer __first = __h.release(); __node_pointer __last = __first; @@ -1308,6 +1311,7 @@ for (++__f; __f != __l; ++__f, ((void)(__last = __last->__next_))) { __h.reset(__node_traits::allocate(__a, 1)); + __h->__next_ = nullptr; __node_traits::construct(__a, _VSTD::addressof(__h->__value_), *__f); __last->__next_ = __h.release(); } 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 --- 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 #include +#include "../exception_safety_helpers.h" #include "../from_range_helpers.h" #include "MoveOnly.h" #include "almost_satisfies_types.h" @@ -193,17 +194,9 @@ void test_exception_safety_throwing_copy() { #if !defined(TEST_HAS_NO_EXCEPTIONS) using T = ThrowingCopy<3>; - T::reset(); - T in[5]; - - try { - Adaptor> 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. - } + test_exception_safety_throwing_copy_range([](auto&& in) { + [[maybe_unused]] Adaptor> c(std::from_range, in); + }); #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 --- 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 #include +#include "../exception_safety_helpers.h" #include "../from_range_helpers.h" #include "../insert_range_helpers.h" #include "MoveOnly.h" @@ -264,18 +265,10 @@ void test_push_range_exception_safety_throwing_copy() { #if !defined(TEST_HAS_NO_EXCEPTIONS) using T = ThrowingCopy<3>; - T::reset(); - T in[5]; - - try { + test_exception_safety_throwing_copy_range([](auto&& in) { Container 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. - } + }); #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 --- /dev/null +++ b/libcxx/test/std/containers/exception_safety_helpers.h @@ -0,0 +1,132 @@ +//===----------------------------------------------------------------------===// +// +// 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 +#include + +#if !defined(TEST_HAS_NO_EXCEPTIONS) +template +struct ThrowingCopy { + static bool throwing_enabled; + static int created_by_copying; + static int destroyed; + int x = 0; // Allows distinguishing between different 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 +bool ThrowingCopy::throwing_enabled = true; +template +int ThrowingCopy::created_by_copying = 0; +template +int ThrowingCopy::destroyed = 0; + +template +struct std::hash> { + std::size_t operator()(const ThrowingCopy& value) const { + return value.x; + } +}; + +template +void test_exception_safety_throwing_copy_single_value(Func&& func) { + using T = ThrowingCopy<1>; + T::reset(); + T input; + + try { + func(std::move(input)); + assert(false); // The function call above should throw. + + } catch (int) { + assert(T::created_by_copying == 1); + assert(T::destroyed == 0); + } +} + +template +void test_exception_safety_throwing_copy_range(Func&& func) { + using T = ThrowingCopy<3>; + T::reset(); + T in[5]; + + try { + func(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. + } +} + +template +void test_exception_safety_throwing_copy_iterator_pair(Func&& func) { + using T = ThrowingCopy<3>; + T::reset(); + T in[5]; + + try { + func(in, in + 5); + 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. + } +} + +template +void test_exception_safety_throwing_copy_container(Func&& func) { + using T = ThrowingCopy<3>; + T::throwing_enabled = false; + T in[5]; + Container c(in, in + 5); + 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 == 3); + assert(T::destroyed == 2); // 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 --- a/libcxx/test/std/containers/from_range_helpers.h +++ b/libcxx/test/std/containers/from_range_helpers.h @@ -57,52 +57,6 @@ }; #if !defined(TEST_HAS_NO_EXCEPTIONS) -template -struct ThrowingCopy { - static bool throwing_enabled; - static int created_by_copying; - static int destroyed; - int x = 0; // Allows distinguishing between different 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 -struct std::hash> { - std::size_t operator()(const ThrowingCopy& value) const { - return value.x; - } -}; - -template -bool ThrowingCopy::throwing_enabled = true; -template -int ThrowingCopy::created_by_copying = 0; -template -int ThrowingCopy::destroyed = 0; template struct ThrowingAllocator { diff --git a/libcxx/test/std/containers/sequences/forwardlist/robust_against_exceptions.pass.cpp b/libcxx/test/std/containers/sequences/forwardlist/robust_against_exceptions.pass.cpp new file mode 100644 --- /dev/null +++ b/libcxx/test/std/containers/sequences/forwardlist/robust_against_exceptions.pass.cpp @@ -0,0 +1,148 @@ +//===----------------------------------------------------------------------===// +// +// 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 +// +//===----------------------------------------------------------------------===// + +// + +// 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 +// forward_list(InputIterator first, InputIterator last); +// template +// 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 +// 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 +// iterator insert_after(const_iterator p, +// InputIterator first, InputIterator last); +// +// void resize(size_type n, const value_type& v); + +#include +#include + +#include "../../exception_safety_helpers.h" +#include "min_allocator.h" + +int main(int, char**) { + { + using T = ThrowingCopy<1>; + + // void push_front(const value_type& v); + test_exception_safety_throwing_copy_single_value([](T&& in){ + std::forward_list c; + c.push_front(in); + }); + + // iterator insert_after(const_iterator p, const value_type& v); + test_exception_safety_throwing_copy_single_value([](T&& in){ + std::forward_list c; + c.insert_after(c.before_begin(), in); + }); + } + + { + using T = ThrowingCopy<3>; + using C = std::forward_list; + using Alloc = std::allocator; + + // forward_list(size_type n, const value_type& v); + test_exception_safety_throwing_copy_iterator_pair([](T* from, T*){ + std::forward_list c(5, *from); + (void)c; + }); + + // forward_list(size_type n, const value_type& v, const allocator_type& a); + test_exception_safety_throwing_copy_iterator_pair([](T* from, T*){ + std::forward_list c(5, *from, Alloc()); + (void)c; + }); + + // template + // forward_list(InputIterator first, InputIterator last); + test_exception_safety_throwing_copy_iterator_pair([](T* from, T* to){ + std::forward_list c(from, to); + (void)c; + }); + + // template + // forward_list(InputIterator first, InputIterator last, const allocator_type& a); + test_exception_safety_throwing_copy_iterator_pair([](T* from, T* to){ + std::forward_list c(from, to, Alloc()); + (void)c; + }); + + // forward_list(const forward_list& x); + test_exception_safety_throwing_copy_container([](C&& in) { + std::forward_list c(in); + (void)c; + }); + + // forward_list(const forward_list& x, const allocator_type& a); + test_exception_safety_throwing_copy_container([](C&& in) { + std::forward_list c(in, Alloc()); + (void)c; + }); + + // forward_list& operator=(const forward_list& x); + test_exception_safety_throwing_copy_container([](C&& in) { + std::forward_list c; + c = in; + }); + + // template + // void assign(InputIterator first, InputIterator last); + test_exception_safety_throwing_copy_iterator_pair([](T* from, T* to) { + std::forward_list c; + c.assign(from, to); + }); + + // void assign(size_type n, const value_type& v); + test_exception_safety_throwing_copy_iterator_pair([](T* from, T*) { + std::forward_list c; + c.assign(5, *from); + }); + + // iterator insert_after(const_iterator p, size_type n, const value_type& v); + test_exception_safety_throwing_copy_iterator_pair([](T* from, T*) { + std::forward_list c; + c.insert_after(c.before_begin(), 5, *from); + }); + + // template + // iterator insert_after(const_iterator p, + // InputIterator first, InputIterator last); + test_exception_safety_throwing_copy_iterator_pair([](T* from, T* to) { + std::forward_list c; + c.insert_after(c.before_begin(), from, to); + }); + + // void resize(size_type n, const value_type& v); + test_exception_safety_throwing_copy_iterator_pair([](T* from, T*) { + std::forward_list c; + c.resize(5, *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 --- 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 #include +#include "../exception_safety_helpers.h" #include "../from_range_helpers.h" #include "MoveOnly.h" #include "almost_satisfies_types.h" @@ -126,17 +127,9 @@ void test_exception_safety_throwing_copy() { #if !defined(TEST_HAS_NO_EXCEPTIONS) using T = ThrowingCopy<3>; - T::reset(); - T in[5]; - - try { - Container 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. - } + test_exception_safety_throwing_copy_range([](auto&& in) { + [[maybe_unused]] Container c(std::from_range, in); + }); #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 --- 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 #include +#include "../exception_safety_helpers.h" #include "../from_range_helpers.h" #include "../insert_range_helpers.h" #include "MoveOnly.h" @@ -676,18 +677,10 @@ void test_insert_range_exception_safety_throwing_copy() { #if !defined(TEST_HAS_NO_EXCEPTIONS) using T = ThrowingCopy<3>; - T::reset(); - T in[5]; - - try { + test_exception_safety_throwing_copy_range([](auto&& in) { Container 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. - } + }); #endif } @@ -714,18 +707,10 @@ void test_prepend_range_exception_safety_throwing_copy() { #if !defined(TEST_HAS_NO_EXCEPTIONS) using T = ThrowingCopy<3>; - T::reset(); - T in[5]; - - try { + test_exception_safety_throwing_copy_range([](auto&& in) { Container 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. - } + }); #endif } @@ -752,18 +737,10 @@ void test_append_range_exception_safety_throwing_copy() { #if !defined(TEST_HAS_NO_EXCEPTIONS) using T = ThrowingCopy<3>; - T::reset(); - T in[5]; - - try { + test_exception_safety_throwing_copy_range([](auto&& in) { Container 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. - } + }); #endif } @@ -790,18 +767,10 @@ void test_assign_range_exception_safety_throwing_copy() { #if !defined(TEST_HAS_NO_EXCEPTIONS) using T = ThrowingCopy<3>; - T::reset(); - T in[5]; - - try { + test_exception_safety_throwing_copy_range([](auto&& in) { Container 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. - } + }); #endif }