diff --git a/libcxx/include/__vector/vector.h b/libcxx/include/__vector/vector.h index ae3ea1de61de0..ed3697990f387 100644 --- a/libcxx/include/__vector/vector.h +++ b/libcxx/include/__vector/vector.h @@ -1025,9 +1025,14 @@ vector<_Tp, _Allocator>::__assign_with_size(_ForwardIterator __first, _Sentinel this->__destruct_at_end(__m); } } else { + __split_buffer __v(__recommend(__new_size), 0, __alloc_); + __v.__construct_at_end_with_size(__first, __new_size); __vdeallocate(); - __vallocate(__recommend(__new_size)); - __construct_at_end(__first, __last, __new_size); + this->__begin_ = __v.__begin_; + this->__end_ = __v.__end_; + this->__cap_ = __v.__cap_; + __v.__first_ = __v.__begin_ = __v.__end_ = __v.__cap_ = nullptr; + __annotate_new(__new_size); } } @@ -1041,9 +1046,14 @@ _LIBCPP_CONSTEXPR_SINCE_CXX20 void vector<_Tp, _Allocator>::assign(size_type __n else this->__destruct_at_end(this->__begin_ + __n); } else { + __split_buffer __v(__recommend(__n), 0, __alloc_); + __v.__construct_at_end(__n, __u); __vdeallocate(); - __vallocate(__recommend(static_cast(__n))); - __construct_at_end(__n, __u); + this->__begin_ = __v.__begin_; + this->__end_ = __v.__end_; + this->__cap_ = __v.__cap_; + __v.__first_ = __v.__begin_ = __v.__end_ = __v.__cap_ = nullptr; + __annotate_new(__n); } } diff --git a/libcxx/test/std/containers/sequences/vector/vector.cons/assign_exceptions.pass.cpp b/libcxx/test/std/containers/sequences/vector/vector.cons/assign_exceptions.pass.cpp new file mode 100644 index 0000000000000..275c2df5852ed --- /dev/null +++ b/libcxx/test/std/containers/sequences/vector/vector.cons/assign_exceptions.pass.cpp @@ -0,0 +1,209 @@ +//===----------------------------------------------------------------------===// +// +// 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 vector assignments do not alter the RHS vector when an exception is thrown during reallocations triggered by assignments. + +#include +#include +#include + +#include "allocators.h" +#include "exception_test_helpers.h" +#include "test_allocator.h" +#include "test_macros.h" + +void test_allocation_exception() { +#if TEST_STD_VER >= 14 + { + limited_alloc_wrapper alloc1 = limited_allocator(); + limited_alloc_wrapper alloc2 = limited_allocator(); + std::vector > v(100, alloc1); + std::vector > in(200, alloc2); + try { // Throw in copy-assignment operator during allocation + v = in; + } catch (const std::exception&) { + } + assert(v.size() == 100); + } + + { + limited_alloc_wrapper alloc1 = limited_allocator(); + limited_alloc_wrapper alloc2 = limited_allocator(); + std::vector > v(100, alloc1); + std::vector > in(200, alloc2); + try { // Throw in move-assignment operator during allocation + v = std::move(in); + } catch (const std::exception&) { + } + assert(v.size() == 100); + } +#endif + +#if TEST_STD_VER >= 11 + { + std::vector > v(5); + std::initializer_list in{1, 2, 3, 4, 5, 6}; + try { // Throw in operator=(initializer_list) during allocation + v = in; + } catch (const std::exception&) { + } + assert(v.size() == 5); + } + + { + std::vector > v(5); + std::initializer_list in{1, 2, 3, 4, 5, 6}; + try { // Throw in assign(initializer_list) during allocation + v.assign(in); + } catch (const std::exception&) { + } + assert(v.size() == 5); + } +#endif + + { + std::vector > v(100); + std::vector in(101, 1); + try { // Throw in assign(_ForwardIterator, _ForwardIterator) during allocation + v.assign(in.begin(), in.end()); + } catch (const std::exception&) { + } + assert(v.size() == 100); + } + + { + std::vector > v(100); + try { // Throw in assign(size_type, const_reference) during allocation + v.assign(101, 1); + } catch (const std::exception&) { + } + assert(v.size() == 100); + } + +#if TEST_STD_VER >= 23 + { + std::vector > v(100); + std::vector in(101, 1); + try { // Throw in assign(_ForwardIterator, _ForwardIterator) during allocation + v.assign_range(in); + } catch (const std::exception&) { + } + assert(v.size() == 100); + } +#endif +} + +void test_construction_exception() { + { + int throw_after = 10; + throwing_t t = throw_after; + std::vector in(6, t); + std::vector v(3, t); + try { // Throw in copy-assignment operator from element type during construction + v = in; + } catch (int) { + } + assert(v.size() == 3); + } + +#if TEST_STD_VER >= 11 + { + int throw_after = 10; + throwing_t t = throw_after; + NONPOCMAAllocator alloc1(1); + NONPOCMAAllocator alloc2(2); + std::vector > in(6, t, alloc1); + std::vector > v(3, t, alloc2); + try { // Throw in move-assignment operator from element type during construction + v = std::move(in); + } catch (int) { + } + assert(v.size() == 3); + } + + { + int throw_after = 10; + throwing_t t = throw_after; + std::initializer_list in{t, t, t, t, t, t}; + std::vector v(3, t); + try { // Throw in operator=(initializer_list) from element type during construction + v = in; + } catch (int) { + } + assert(v.size() == 3); + } + + { + int throw_after = 10; + throwing_t t = throw_after; + std::initializer_list in{t, t, t, t, t, t}; + std::vector v(3, t); + try { // Throw in assign(initializer_list) from element type during construction + v.assign(in); + } catch (int) { + } + assert(v.size() == 3); + } +#endif + + { + std::vector v(3); + try { // Throw in assign(_ForwardIterator, _ForwardIterator) from forward iterator during construction + v.assign(throwing_iterator(), + throwing_iterator(6)); + } catch (int) { + } + assert(v.size() == 3); + } + + { + int throw_after = 10; + throwing_t t = throw_after; + std::vector in(6, t); + std::vector v(3, t); + try { // Throw in assign(_ForwardIterator, _ForwardIterator) from element type during construction + v.assign(in.begin(), in.end()); + } catch (int) { + } + assert(v.size() == 3); + } + +#if TEST_STD_VER >= 23 + { + int throw_after = 10; + throwing_t t = throw_after; + std::vector in(6, t); + std::vector v(3, t); + try { // Throw in assign_range(_Range&&) from element type during construction + v.assign_range(in); + } catch (int) { + } + assert(v.size() == 3); + } +#endif + + { + int throw_after = 4; + throwing_t t = throw_after; + std::vector v(3, t); + try { // Throw in assign(size_type, const_reference) from element type during construction + v.assign(6, t); + } catch (int) { + } + assert(v.size() == 3); + } +} + +int main() { + test_allocation_exception(); + test_construction_exception(); +} diff --git a/libcxx/test/support/allocators.h b/libcxx/test/support/allocators.h index 02436fd9c35ef..44f0b9a947362 100644 --- a/libcxx/test/support/allocators.h +++ b/libcxx/test/support/allocators.h @@ -251,6 +251,50 @@ using POCCAAllocator = MaybePOCCAAllocator; template using NonPOCCAAllocator = MaybePOCCAAllocator; +template +class MaybePOCMAAllocator { + template + friend class MaybePOCMAAllocator; + +public: + using propagate_on_container_move_assignment = std::integral_constant; + using value_type = T; + + template + struct rebind { + using other = MaybePOCMAAllocator; + }; + + TEST_CONSTEXPR MaybePOCMAAllocator(int id) : id_(id) {} + + template + TEST_CONSTEXPR MaybePOCMAAllocator(const MaybePOCMAAllocator& other) : id_(other.id_) {} + + TEST_CONSTEXPR_CXX20 T* allocate(std::size_t n) { return std::allocator().allocate(n); } + + TEST_CONSTEXPR_CXX20 void deallocate(T* p, std::size_t n) { std::allocator().deallocate(p, n); } + + TEST_CONSTEXPR int id() const { return id_; } + + template + TEST_CONSTEXPR friend bool operator==(const MaybePOCMAAllocator& lhs, const MaybePOCMAAllocator& rhs) { + return lhs.id() == rhs.id(); + } + + template + TEST_CONSTEXPR friend bool operator!=(const MaybePOCMAAllocator& lhs, const MaybePOCMAAllocator& rhs) { + return !(lhs == rhs); + } + +private: + int id_; +}; + +template +using POCMAAllocator = MaybePOCMAAllocator; +template +using NONPOCMAAllocator = MaybePOCMAAllocator; + #endif // TEST_STD_VER >= 11 #endif // ALLOCATORS_H diff --git a/libcxx/test/support/exception_test_helpers.h b/libcxx/test/support/exception_test_helpers.h new file mode 100644 index 0000000000000..a61f690c7fc6f --- /dev/null +++ b/libcxx/test/support/exception_test_helpers.h @@ -0,0 +1,114 @@ +//===----------------------------------------------------------------------===// +// +// 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 EXCEPTION_TEST_HELPERS_H +#define EXCEPTION_TEST_HELPERS_H + +#include "count_new.h" + +struct throwing_t { + int* throw_after_n_ = nullptr; + throwing_t() { throw 0; } + + throwing_t(int& throw_after_n) : throw_after_n_(&throw_after_n) { + if (throw_after_n == 0) + throw 0; + --throw_after_n; + } + + throwing_t(const throwing_t& rhs) : throw_after_n_(rhs.throw_after_n_) { + if (throw_after_n_ == nullptr || *throw_after_n_ == 0) + throw 1; + --*throw_after_n_; + } + + throwing_t& operator=(const throwing_t& rhs) { + throw_after_n_ = rhs.throw_after_n_; + if (throw_after_n_ == nullptr || *throw_after_n_ == 0) + throw 1; + --*throw_after_n_; + return *this; + } + + friend bool operator==(const throwing_t& lhs, const throwing_t& rhs) { + return lhs.throw_after_n_ == rhs.throw_after_n_; + } + friend bool operator!=(const throwing_t& lhs, const throwing_t& rhs) { + return lhs.throw_after_n_ != rhs.throw_after_n_; + } +}; + +template +struct throwing_allocator { + using value_type = T; + using is_always_equal = std::false_type; + + bool throw_on_copy_ = false; + + throwing_allocator(bool throw_on_ctor = true, bool throw_on_copy = false) : throw_on_copy_(throw_on_copy) { + if (throw_on_ctor) + throw 0; + } + + template + throwing_allocator(const throwing_allocator& rhs) : throw_on_copy_(rhs.throw_on_copy_) { + if (throw_on_copy_) + throw 0; + } + + T* allocate(std::size_t n) { return std::allocator().allocate(n); } + void deallocate(T* ptr, std::size_t n) { std::allocator().deallocate(ptr, n); } + + template + friend bool operator==(const throwing_allocator&, const throwing_allocator&) { + return true; + } +}; + +template +struct throwing_iterator { + using iterator_category = IterCat; + using difference_type = std::ptrdiff_t; + using value_type = T; + using reference = T&; + using pointer = T*; + + int i_; + T v_; + + throwing_iterator(int i = 0, const T& v = T()) : i_(i), v_(v) {} + + reference operator*() { + if (i_ == 1) + throw 1; + return v_; + } + + friend bool operator==(const throwing_iterator& lhs, const throwing_iterator& rhs) { return lhs.i_ == rhs.i_; } + friend bool operator!=(const throwing_iterator& lhs, const throwing_iterator& rhs) { return lhs.i_ != rhs.i_; } + + throwing_iterator& operator++() { + ++i_; + return *this; + } + + throwing_iterator operator++(int) { + auto tmp = *this; + ++i_; + return tmp; + } +}; + +inline void check_new_delete_called() { + assert(globalMemCounter.new_called == globalMemCounter.delete_called); + assert(globalMemCounter.new_array_called == globalMemCounter.delete_array_called); + assert(globalMemCounter.aligned_new_called == globalMemCounter.aligned_delete_called); + assert(globalMemCounter.aligned_new_array_called == globalMemCounter.aligned_delete_array_called); +} + +#endif // EXCEPTION_TEST_HELPERS_H \ No newline at end of file diff --git a/libcxx/test/support/test_allocator.h b/libcxx/test/support/test_allocator.h index dcd15332ca304..07e7bc26804c8 100644 --- a/libcxx/test/support/test_allocator.h +++ b/libcxx/test/support/test_allocator.h @@ -27,45 +27,45 @@ TEST_CONSTEXPR_CXX20 inline typename std::allocator_traits::size_type all } struct test_allocator_statistics { - int time_to_throw = 0; - int throw_after = INT_MAX; + int time_to_throw = 0; + int throw_after = INT_MAX; int count = 0; // the number of active instances int alloc_count = 0; // the number of allocations not deallocating int allocated_size = 0; // the size of allocated elements int construct_count = 0; // the number of times that ::construct was called - int destroy_count = 0; // the number of times that ::destroy was called - int copied = 0; - int moved = 0; - int converted = 0; + int destroy_count = 0; // the number of times that ::destroy was called + int copied = 0; + int moved = 0; + int converted = 0; TEST_CONSTEXPR_CXX14 void clear() { assert(count == 0 && "clearing leaking allocator data?"); - count = 0; - time_to_throw = 0; - alloc_count = 0; + count = 0; + time_to_throw = 0; + alloc_count = 0; allocated_size = 0; construct_count = 0; - destroy_count = 0; - throw_after = INT_MAX; + destroy_count = 0; + throw_after = INT_MAX; clear_ctor_counters(); } TEST_CONSTEXPR_CXX14 void clear_ctor_counters() { - copied = 0; - moved = 0; + copied = 0; + moved = 0; converted = 0; } }; struct test_alloc_base { TEST_CONSTEXPR static const int destructed_value = -1; - TEST_CONSTEXPR static const int moved_value = INT_MAX; + TEST_CONSTEXPR static const int moved_value = INT_MAX; }; template class test_allocator { - int data_ = 0; // participates in equality - int id_ = 0; // unique identifier, doesn't participate in equality + int data_ = 0; // participates in equality + int id_ = 0; // unique identifier, doesn't participate in equality test_allocator_statistics* stats_ = nullptr; template @@ -95,7 +95,8 @@ class test_allocator { TEST_CONSTEXPR explicit test_allocator(int data) TEST_NOEXCEPT : data_(data) {} TEST_CONSTEXPR_CXX14 explicit test_allocator(int data, test_allocator_statistics* stats) TEST_NOEXCEPT - : data_(data), stats_(stats) { + : data_(data), + stats_(stats) { if (stats != nullptr) ++stats_->count; } @@ -103,13 +104,17 @@ class test_allocator { TEST_CONSTEXPR explicit test_allocator(int data, int id) TEST_NOEXCEPT : data_(data), id_(id) {} TEST_CONSTEXPR_CXX14 explicit test_allocator(int data, int id, test_allocator_statistics* stats) TEST_NOEXCEPT - : data_(data), id_(id), stats_(stats) { + : data_(data), + id_(id), + stats_(stats) { if (stats_ != nullptr) ++stats_->count; } TEST_CONSTEXPR_CXX14 test_allocator(const test_allocator& a) TEST_NOEXCEPT - : data_(a.data_), id_(a.id_), stats_(a.stats_) { + : data_(a.data_), + id_(a.id_), + stats_(a.stats_) { assert(a.data_ != test_alloc_base::destructed_value && a.id_ != test_alloc_base::destructed_value && "copying from destroyed allocator"); if (stats_ != nullptr) { @@ -130,7 +135,9 @@ class test_allocator { template TEST_CONSTEXPR_CXX14 test_allocator(const test_allocator& a) TEST_NOEXCEPT - : data_(a.data_), id_(a.id_), stats_(a.stats_) { + : data_(a.data_), + id_(a.id_), + stats_(a.stats_) { if (stats_ != nullptr) { ++stats_->count; ++stats_->converted; @@ -143,7 +150,7 @@ class test_allocator { if (stats_ != nullptr) --stats_->count; data_ = test_alloc_base::destructed_value; - id_ = test_alloc_base::destructed_value; + id_ = test_alloc_base::destructed_value; } TEST_CONSTEXPR pointer address(reference x) const { return &x; } @@ -197,8 +204,8 @@ class test_allocator { template <> class test_allocator { - int data_ = 0; - int id_ = 0; + int data_ = 0; + int id_ = 0; test_allocator_statistics* stats_ = nullptr; template @@ -223,27 +230,30 @@ class test_allocator { TEST_CONSTEXPR explicit test_allocator(int data) TEST_NOEXCEPT : data_(data) {} TEST_CONSTEXPR explicit test_allocator(int data, test_allocator_statistics* stats) TEST_NOEXCEPT - : data_(data), stats_(stats) - {} + : data_(data), + stats_(stats) {} TEST_CONSTEXPR explicit test_allocator(int data, int id) : data_(data), id_(id) {} TEST_CONSTEXPR_CXX14 explicit test_allocator(int data, int id, test_allocator_statistics* stats) TEST_NOEXCEPT - : data_(data), id_(id), stats_(stats) - {} + : data_(data), + id_(id), + stats_(stats) {} TEST_CONSTEXPR_CXX14 explicit test_allocator(const test_allocator& a) TEST_NOEXCEPT - : data_(a.data_), id_(a.id_), stats_(a.stats_) - {} + : data_(a.data_), + id_(a.id_), + stats_(a.stats_) {} template TEST_CONSTEXPR_CXX14 test_allocator(const test_allocator& a) TEST_NOEXCEPT - : data_(a.data_), id_(a.id_), stats_(a.stats_) - {} + : data_(a.data_), + id_(a.id_), + stats_(a.stats_) {} TEST_CONSTEXPR_CXX20 ~test_allocator() TEST_NOEXCEPT { data_ = test_alloc_base::destructed_value; - id_ = test_alloc_base::destructed_value; + id_ = test_alloc_base::destructed_value; } TEST_CONSTEXPR int get_id() const { return id_; } @@ -310,9 +320,9 @@ struct Tag_X { TEST_CONSTEXPR Tag_X(Ctor_Tag, Args&&...) {} // not DefaultConstructible, CopyConstructible or MoveConstructible. - Tag_X() = delete; + Tag_X() = delete; Tag_X(const Tag_X&) = delete; - Tag_X(Tag_X&&) = delete; + Tag_X(Tag_X&&) = delete; // CopyAssignable. TEST_CONSTEXPR_CXX14 Tag_X& operator=(const Tag_X&) { return *this; }; @@ -329,7 +339,7 @@ struct Tag_X { template class TaggingAllocator { public: - using value_type = T; + using value_type = T; TaggingAllocator() = default; template @@ -356,13 +366,13 @@ class TaggingAllocator { template struct limited_alloc_handle { std::size_t outstanding_ = 0; - void* last_alloc_ = nullptr; + void* last_alloc_ = nullptr; template TEST_CONSTEXPR_CXX20 T* allocate(std::size_t N) { if (N + outstanding_ > MaxAllocs) TEST_THROW(std::bad_alloc()); - auto alloc = std::allocator().allocate(N); + auto alloc = std::allocator().allocate(N); last_alloc_ = alloc; outstanding_ += N; return alloc; @@ -480,6 +490,58 @@ TEST_CONSTEXPR inline bool operator!=(limited_allocator const& LHS, limite return !(LHS == RHS); } +// type erasure wrapper for limited_allocator +template +class limited_alloc_wrapper { +public: + typedef T value_type; + typedef value_type* pointer; + typedef const value_type* const_pointer; + typedef value_type& reference; + typedef const value_type& const_reference; + typedef std::size_t size_type; + typedef std::ptrdiff_t difference_type; + + template + limited_alloc_wrapper(Alloc&& a) : pimpl(std::make_shared >(std::forward(a))) {} + + limited_alloc_wrapper(const limited_alloc_wrapper& other) = default; + + pointer allocate(std::size_t n) { return pimpl->allocate(n); } + void deallocate(pointer p, size_t n) { pimpl->deallocate(p, n); } + size_type max_size() const { return pimpl->max_size(); } + +private: + struct impl_base { + virtual pointer allocate(std::size_t n) = 0; + virtual void deallocate(pointer p, size_t n) = 0; + virtual size_type max_size() const = 0; + virtual ~impl_base() {} + }; + + std::shared_ptr pimpl; + + template + struct impl_type : impl_base { + Alloc a; + impl_type(const Alloc& a_) : a(a_) {} + + pointer allocate(std::size_t n) override { return a.allocate(n); } + void deallocate(pointer p, size_t n) override { a.deallocate(p, n); } + size_type max_size() const override { return a.max_size(); } + }; + + template + friend bool operator==(const limited_alloc_wrapper& lhs, const limited_alloc_wrapper& rhs) { + return lhs.pimpl == rhs.pimpl; + } + + template + friend bool operator!=(const limited_alloc_wrapper& lhs, const limited_alloc_wrapper& rhs) { + return !(lhs == rhs); + } +}; + // Track the "provenance" of this allocator instance: how many times was // select_on_container_copy_construction called in order to produce it? //