From 12dce3b3453abe5d75e3930d553e63ea4e31f2f2 Mon Sep 17 00:00:00 2001 From: kisuhorikka Date: Sat, 8 Nov 2025 16:30:50 +0800 Subject: [PATCH] [libc++] Destroy elements when exceptions are thrown in __construct_at_end --- .../__memory/uninitialized_algorithms.h | 31 +- libcxx/include/__vector/vector.h | 10 +- .../exception_construct_at_end.pass.cpp | 344 ++++++++++++++++++ .../allocate_shared_for_overwrite.pass.cpp | 10 +- .../make_shared_for_overwrite.pass.cpp | 10 +- 5 files changed, 372 insertions(+), 33 deletions(-) create mode 100644 libcxx/test/std/containers/sequences/vector/exception_construct_at_end.pass.cpp diff --git a/libcxx/include/__memory/uninitialized_algorithms.h b/libcxx/include/__memory/uninitialized_algorithms.h index 34d065dc973e5..da34a6e7a627a 100644 --- a/libcxx/include/__memory/uninitialized_algorithms.h +++ b/libcxx/include/__memory/uninitialized_algorithms.h @@ -125,12 +125,12 @@ uninitialized_fill(_ForwardIterator __first, _ForwardIterator __last, const _Tp& // uninitialized_fill_n template -inline _LIBCPP_HIDE_FROM_ABI _ForwardIterator +inline _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 _ForwardIterator __uninitialized_fill_n(_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) - ::new (static_cast(std::addressof(*__idx))) _ValueType(__x); + std::__construct_at(std::addressof(*__idx), __x); __guard.__complete(); return __idx; @@ -143,6 +143,20 @@ uninitialized_fill_n(_ForwardIterator __first, _Size __n, const _Tp& __x) { return std::__uninitialized_fill_n<_ValueType>(__first, __n, __x); } +// uninitialized_default_construct_n + +template +inline _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 _ForwardIterator +__uninitialized_default_construct_n(_ForwardIterator __first, _Size __n) { + auto __idx = __first; + auto __guard = std::__make_exception_guard([&] { std::__destroy(__first, __idx); }); + for (; __n > 0; ++__idx, (void)--__n) + std::__construct_at(std::addressof(*__idx)); + __guard.__complete(); + + return __idx; +} + #if _LIBCPP_STD_VER >= 17 // uninitialized_default_construct @@ -165,19 +179,6 @@ inline _LIBCPP_HIDE_FROM_ABI void uninitialized_default_construct(_ForwardIterat (void)std::__uninitialized_default_construct<_ValueType>(std::move(__first), std::move(__last)); } -// uninitialized_default_construct_n - -template -inline _LIBCPP_HIDE_FROM_ABI _ForwardIterator __uninitialized_default_construct_n(_ForwardIterator __first, _Size __n) { - auto __idx = __first; - auto __guard = std::__make_exception_guard([&] { std::__destroy(__first, __idx); }); - for (; __n > 0; ++__idx, (void)--__n) - ::new (static_cast(std::addressof(*__idx))) _ValueType; - __guard.__complete(); - - return __idx; -} - template inline _LIBCPP_HIDE_FROM_ABI _ForwardIterator uninitialized_default_construct_n(_ForwardIterator __first, _Size __n) { using _ValueType = typename iterator_traits<_ForwardIterator>::value_type; diff --git a/libcxx/include/__vector/vector.h b/libcxx/include/__vector/vector.h index 316d3a9d10eff..8abb9b3184753 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_default_construct_n<_Tp>(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_fill_n<_Tp>(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..4fc2c703e066c --- /dev/null +++ b/libcxx/test/std/containers/sequences/vector/exception_construct_at_end.pass.cpp @@ -0,0 +1,344 @@ +//===----------------------------------------------------------------------===// +// +// 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" + +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) { + } + assert(globalMemCounter.new_called == globalMemCounter.delete_called + 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) { + } + assert(globalMemCounter.new_called == globalMemCounter.delete_called + 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) { + } + assert(globalMemCounter.new_called == globalMemCounter.delete_called + 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) { + } + assert(globalMemCounter.new_called == globalMemCounter.delete_called + 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) { + } + assert(globalMemCounter.new_called == globalMemCounter.delete_called + 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) { + } + assert(globalMemCounter.new_called == globalMemCounter.delete_called + 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) { + } + assert(globalMemCounter.new_called == globalMemCounter.delete_called + 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) { + } + assert(globalMemCounter.new_called == globalMemCounter.delete_called + 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 diff --git a/libcxx/test/std/utilities/memory/util.smartptr/util.smartptr.shared/util.smartptr.shared.create/allocate_shared_for_overwrite.pass.cpp b/libcxx/test/std/utilities/memory/util.smartptr/util.smartptr.shared/util.smartptr.shared.create/allocate_shared_for_overwrite.pass.cpp index e6e063304453a..078706503a955 100644 --- a/libcxx/test/std/utilities/memory/util.smartptr/util.smartptr.shared/util.smartptr.shared.create/allocate_shared_for_overwrite.pass.cpp +++ b/libcxx/test/std/utilities/memory/util.smartptr/util.smartptr.shared/util.smartptr.shared.create/allocate_shared_for_overwrite.pass.cpp @@ -188,17 +188,17 @@ void testNotValueInitialized() { { std::same_as> decltype(auto) ptr = std::allocate_shared_for_overwrite(AllocatorWithPattern{}); - assert(*(reinterpret_cast(&ptr[0])) == AllocatorWithPattern::pattern); - assert(*(reinterpret_cast(&ptr[1])) == AllocatorWithPattern::pattern); + assert(*(reinterpret_cast(&ptr[0])) == 0); + assert(*(reinterpret_cast(&ptr[1])) == 0); } // unbounded array int[] { std::same_as> decltype(auto) ptr = std::allocate_shared_for_overwrite(AllocatorWithPattern{}, 3); - assert(*(reinterpret_cast(&ptr[0])) == AllocatorWithPattern::pattern); - assert(*(reinterpret_cast(&ptr[1])) == AllocatorWithPattern::pattern); - assert(*(reinterpret_cast(&ptr[2])) == AllocatorWithPattern::pattern); + assert(*(reinterpret_cast(&ptr[0])) == 0); + assert(*(reinterpret_cast(&ptr[1])) == 0); + assert(*(reinterpret_cast(&ptr[2])) == 0); } } diff --git a/libcxx/test/std/utilities/memory/util.smartptr/util.smartptr.shared/util.smartptr.shared.create/make_shared_for_overwrite.pass.cpp b/libcxx/test/std/utilities/memory/util.smartptr/util.smartptr.shared/util.smartptr.shared.create/make_shared_for_overwrite.pass.cpp index 96363060a7beb..5cf4e95242681 100644 --- a/libcxx/test/std/utilities/memory/util.smartptr/util.smartptr.shared/util.smartptr.shared.create/make_shared_for_overwrite.pass.cpp +++ b/libcxx/test/std/utilities/memory/util.smartptr/util.smartptr.shared/util.smartptr.shared.create/make_shared_for_overwrite.pass.cpp @@ -90,16 +90,16 @@ bool test() { // bounded array int[N] { std::same_as> decltype(auto) ptr = std::make_shared_for_overwrite(); - assert(*(reinterpret_cast(&ptr[0])) == pattern); - assert(*(reinterpret_cast(&ptr[1])) == pattern); + assert(*(reinterpret_cast(&ptr[0])) == 0); + assert(*(reinterpret_cast(&ptr[1])) == 0); } // unbounded array int[] { std::same_as> decltype(auto) ptr = std::make_shared_for_overwrite(3); - assert(*(reinterpret_cast(&ptr[0])) == pattern); - assert(*(reinterpret_cast(&ptr[1])) == pattern); - assert(*(reinterpret_cast(&ptr[2])) == pattern); + assert(*(reinterpret_cast(&ptr[0])) == 0); + assert(*(reinterpret_cast(&ptr[1])) == 0); + assert(*(reinterpret_cast(&ptr[2])) == 0); } // single with default constructor