mirror of
https://github.com/capstone-engine/llvm-capstone.git
synced 2024-11-23 22:00:10 +00:00
[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
This commit is contained in:
parent
345f8699c7
commit
d0b51657c2
@ -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_;
|
||||
|
@ -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
|
||||
}
|
||||
|
192
libcxx/test/libcxx/memory/allocation_guard.pass.cpp
Normal file
192
libcxx/test/libcxx/memory/allocation_guard.pass.cpp
Normal file
@ -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 difference_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;
|
||||
}
|
@ -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
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
}
|
||||
|
||||
|
106
libcxx/test/std/containers/exception_safety_helpers.h
Normal file
106
libcxx/test/std/containers/exception_safety_helpers.h
Normal file
@ -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 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 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
|
@ -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 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 <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 {
|
||||
|
@ -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;
|
||||
}
|
@ -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
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user