diff --git a/libcxx/include/__memory/uninitialized_algorithms.h b/libcxx/include/__memory/uninitialized_algorithms.h index 34d065dc973e5..09a1b43e00f17 100644 --- a/libcxx/include/__memory/uninitialized_algorithms.h +++ b/libcxx/include/__memory/uninitialized_algorithms.h @@ -124,6 +124,18 @@ uninitialized_fill(_ForwardIterator __first, _ForwardIterator __last, const _Tp& // uninitialized_fill_n +template +_LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 _ForwardIterator +__uninitialized_allocator_fill_n(_Alloc& __alloc, _ForwardIterator __first, _Size __n, const _Tp& __x) { + _ForwardIterator __idx = __first; + auto __guard = std::__make_exception_guard([&] { std::__destroy(__first, __idx); }); + for (; __n > 0; ++__idx, (void)--__n) + allocator_traits<_Alloc>::construct(__alloc, std::__to_address(__idx), __x); + __guard.__complete(); + + return __idx; +} + template inline _LIBCPP_HIDE_FROM_ABI _ForwardIterator __uninitialized_fill_n(_ForwardIterator __first, _Size __n, const _Tp& __x) { @@ -143,6 +155,20 @@ uninitialized_fill_n(_ForwardIterator __first, _Size __n, const _Tp& __x) { return std::__uninitialized_fill_n<_ValueType>(__first, __n, __x); } +// __uninitialized_allocator_value_construct_n + +template +_LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 _ForwardIterator +__uninitialized_allocator_value_construct_n(_Alloc& __alloc, _ForwardIterator __first, _Size __n) { + auto __idx = __first; + auto __guard = std::__make_exception_guard([&] { std::__destroy(__first, __idx); }); + for (; __n > 0; ++__idx, (void)--__n) + allocator_traits<_Alloc>::construct(__alloc, std::__to_address(__idx)); + __guard.__complete(); + + return __idx; +} + #if _LIBCPP_STD_VER >= 17 // uninitialized_default_construct diff --git a/libcxx/include/__vector/vector.h b/libcxx/include/__vector/vector.h index 316d3a9d10eff..14c387414518e 100644 --- a/libcxx/include/__vector/vector.h +++ b/libcxx/include/__vector/vector.h @@ -941,10 +941,7 @@ vector<_Tp, _Allocator>::__recommend(size_type __new_size) const { template _LIBCPP_CONSTEXPR_SINCE_CXX20 void vector<_Tp, _Allocator>::__construct_at_end(size_type __n) { _ConstructTransaction __tx(*this, __n); - const_pointer __new_end = __tx.__new_end_; - for (pointer __pos = __tx.__pos_; __pos != __new_end; __tx.__pos_ = ++__pos) { - __alloc_traits::construct(this->__alloc_, std::__to_address(__pos)); - } + __tx.__pos_ = std::__uninitialized_allocator_value_construct_n(this->__alloc_, this->__end_, __n); } // Copy constructs __n objects starting at __end_ from __x @@ -957,10 +954,7 @@ template _LIBCPP_CONSTEXPR_SINCE_CXX20 inline void vector<_Tp, _Allocator>::__construct_at_end(size_type __n, const_reference __x) { _ConstructTransaction __tx(*this, __n); - const_pointer __new_end = __tx.__new_end_; - for (pointer __pos = __tx.__pos_; __pos != __new_end; __tx.__pos_ = ++__pos) { - __alloc_traits::construct(this->__alloc_, std::__to_address(__pos), __x); - } + __tx.__pos_ = std::__uninitialized_allocator_fill_n(this->__alloc_, this->__end_, __n, __x); } template diff --git a/libcxx/test/std/containers/sequences/vector/exception_construct_at_end.pass.cpp b/libcxx/test/std/containers/sequences/vector/exception_construct_at_end.pass.cpp new file mode 100644 index 0000000000000..36ea5ba750f19 --- /dev/null +++ b/libcxx/test/std/containers/sequences/vector/exception_construct_at_end.pass.cpp @@ -0,0 +1,350 @@ +//===----------------------------------------------------------------------===// +// +// 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 +// UNSUPPORTED: c++03 + +// + +// Make sure elements are destroyed when exceptions thrown in __construct_at_end + +#include +#include +#include +#include + +#include "test_macros.h" +#if TEST_STD_VER >= 20 +# include +#endif + +#include "common.h" +#include "count_new.h" + +#ifdef DISABLE_NEW_COUNT +# define CHECK_NEW_DELETE_DIFF(...) +#else +# define CHECK_NEW_DELETE_DIFF(__n) assert(globalMemCounter.new_called == globalMemCounter.delete_called + __n) +#endif + +struct throw_context { + static int num; + static int limit; + + throw_context(int lim = 2) { + num = 0; + limit = lim; + } + + static void inc() { + ++num; + if (num >= limit) { + --num; + throw 1; + } + } + + static void dec() { --num; } +}; + +int throw_context::num = 0; +int throw_context::limit = 0; + +int debug = 0; + +class throw_element { +public: + throw_element() : data(new int(1)) { + try { + throw_context::inc(); + } catch (int) { + delete data; + throw; + } + } + + throw_element(throw_element const& other) : data(new int(1)) { + (void)other; + try { + throw_context::inc(); + } catch (int) { + delete data; + throw; + } + } + + ~throw_element() { + if (data) { + delete data; + throw_context::dec(); + if (debug) + printf("dctor\n"); + } + } + + throw_element& operator=(throw_element const& other) { + (void)other; + // nothing to do + return *this; + } + +private: + int* data; +}; + +int main(int, char*[]) { + using AllocType = std::allocator; + + // vector(size_type __n) + { + throw_context ctx; + try { + std::vector v(3); + } catch (int) { + } + check_new_delete_called(); + } + +#if TEST_STD_VER >= 14 + // vector(size_type __n, const allocator_type& __a) + { + throw_context ctx; + AllocType alloc; + try { + std::vector v(3, alloc); + } catch (int) { + } + check_new_delete_called(); + } +#endif + + // vector(size_type __n, const value_type& __x) + { + throw_context ctx(3); + try { + throw_element e; + std::vector v(3, e); + } catch (int) { + } + check_new_delete_called(); + } + + // vector(size_type __n, const value_type& __x, const allocator_type& __a) + { + throw_context ctx(3); + try { + throw_element e; + AllocType alloc; + std::vector v(4, e, alloc); + } catch (int) { + } + check_new_delete_called(); + } + + // vector(_ForwardIterator __first, _ForwardIterator __last) + { + throw_context ctx(4); + try { + std::vector v1(2); + std::vector v2(v1.begin(), v1.end()); + } catch (int) { + } + check_new_delete_called(); + } + + // vector(_ForwardIterator __first, _ForwardIterator __last, const allocator_type& __a) + { + throw_context ctx(4); + AllocType alloc; + try { + std::vector v1(2); + std::vector v2(v1.begin(), v1.end(), alloc); + } catch (int) { + } + check_new_delete_called(); + } + +#if TEST_STD_VER >= 23 + // vector(from_range_t, _Range&& __range, const allocator_type& __alloc = allocator_type()) + { + throw_context ctx(4); + try { + std::vector r(2); + std::vector v(std::from_range, std::views::counted(r.begin(), 2)); + } catch (int) { + } + check_new_delete_called(); + } +#endif + + // vector(const vector& __x) + { + throw_context ctx(4); + try { + std::vector v1(2); + std::vector v2(v1); + } catch (int) { + } + check_new_delete_called(); + } + +#if TEST_STD_VER > 3 + // vector(initializer_list __il) + { + throw_context ctx(6); + try { + throw_element e; + std::vector v({e, e, e}); + } catch (int) { + } + check_new_delete_called(); + } + + // vector(initializer_list __il, const allocator_type& __a) + { + throw_context ctx(6); + AllocType alloc; + try { + throw_element e; + std::vector v({e, e, e}, alloc); + } catch (int) { + } + check_new_delete_called(); + } +#endif + + // void resize(size_type __sz) + { + // cap < size + throw_context ctx; + std::vector v; + v.reserve(5); + try { + v.resize(4); + } catch (int) { + } + CHECK_NEW_DELETE_DIFF(1); + } + check_new_delete_called(); + + // void resize(size_type __sz, const_reference __x) + { + // cap < size + throw_context ctx(3); + std::vector v; + v.reserve(5); + try { + throw_element e; + v.resize(4, e); + } catch (int) { + } + CHECK_NEW_DELETE_DIFF(1); + } + check_new_delete_called(); + + // void assign(_ForwardIterator __first, _ForwardIterator __last) + { + // new size <= cap && new size > size + throw_context ctx(4); + std::vector v; + v.reserve(3); + try { + std::vector data(2); + v.assign(data.begin(), data.end()); + } catch (int) { + } + CHECK_NEW_DELETE_DIFF(1); + } + check_new_delete_called(); + + { + // new size > cap + throw_context ctx(4); + std::vector v; + try { + std::vector data(2); + v.assign(data.begin(), data.end()); + } catch (int) { + } + CHECK_NEW_DELETE_DIFF(1); + } + check_new_delete_called(); + +#if TEST_STD_VER >= 23 + // void assign_range(_Range&& __range) + { + throw_context ctx(5); + std::vector v; + try { + std::vector r(3); + v.assign_range(r); + } catch (int) { + } + CHECK_NEW_DELETE_DIFF(1); + } + check_new_delete_called(); +#endif + +#if TEST_STD_VER > 3 + // vector& operator=(initializer_list __il) + { + throw_context ctx(5); + std::vector v; + try { + throw_element e; + v = {e, e}; + } catch (int) { + } + CHECK_NEW_DELETE_DIFF(1); + } + check_new_delete_called(); +#endif + + // vector<_Tp, _Allocator>& vector<_Tp, _Allocator>::operator=(const vector& __x) + { + throw_context ctx(4); + std::vector v; + try { + std::vector data(2); + v = data; + } catch (int) { + } + CHECK_NEW_DELETE_DIFF(1); + } + check_new_delete_called(); + + // iterator insert(const_iterator __position, _ForwardIterator __first, _ForwardIterator __last) + { + throw_context ctx(6); + std::vector v; + v.reserve(10); + try { + std::vector data(3); + v.insert(v.begin(), data.begin(), data.end()); + } catch (int) { + } + CHECK_NEW_DELETE_DIFF(1); + } + check_new_delete_called(); + +#if TEST_STD_VER >= 23 + // iterator insert_range(const_iterator __position, _Range&& __range) + { + throw_context ctx(3); + std::vector v; + try { + std::vector data(2); + v.insert_range(v.begin(), data); + } catch (int) { + } + check_new_delete_called(); + } +#endif + + return 0; +} \ No newline at end of file