[libcxx-commits] [libcxx] [libc++] Destroy elements when exceptions are thrown in __construct_at_end (PR #167112)
via libcxx-commits
libcxx-commits at lists.llvm.org
Sun Nov 16 02:33:50 PST 2025
https://github.com/kisuhorikka updated https://github.com/llvm/llvm-project/pull/167112
>From d4f5f499d416897c883bd98f5475c5a81618f4e6 Mon Sep 17 00:00:00 2001
From: kisuhorikka <kisuhorikka at gmail.com>
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 | 26 ++
libcxx/include/__vector/vector.h | 10 +-
.../exception_construct_at_end.pass.cpp | 350 ++++++++++++++++++
3 files changed, 378 insertions(+), 8 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..f35d89aa3201a 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 <class _Alloc, class _ForwardIterator, class _Size, class _Tp>
+inline _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 _ForwardIterator
+__uninitialized_default_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 <class _ValueType, class _ForwardIterator, class _Size, class _Tp>
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_default_construct_n_with_alloc
+
+template <class _Alloc, class _ForwardIterator, class _Size>
+inline _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 _ForwardIterator
+__uninitialized_default_construct_n_with_alloc(_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..e2f6262dcd3ad 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 <class _Tp, class _Allocator>
_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_with_alloc<_Allocator>(this->__alloc_, this->__end_, __n);
}
// Copy constructs __n objects starting at __end_ from __x
@@ -957,10 +954,7 @@ template <class _Tp, class _Allocator>
_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_default_fill_n<_Allocator>(this->__alloc_, this->__end_, __n, __x);
}
template <class _Tp, class _Allocator>
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
+
+// <vector>
+
+// Make sure elements are destroyed when exceptions thrown in __construct_at_end
+
+#include <cassert>
+#include <cstddef>
+#include <memory>
+#include <vector>
+
+#include "test_macros.h"
+#if TEST_STD_VER >= 20
+# include <ranges>
+#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<throw_element>;
+
+ // vector(size_type __n)
+ {
+ throw_context ctx;
+ try {
+ std::vector<throw_element> 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<throw_element> 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<throw_element> 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<throw_element> v(4, e, alloc);
+ } catch (int) {
+ }
+ check_new_delete_called();
+ }
+
+ // vector(_ForwardIterator __first, _ForwardIterator __last)
+ {
+ throw_context ctx(4);
+ try {
+ std::vector<throw_element> v1(2);
+ std::vector<throw_element> 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<throw_element> v1(2);
+ std::vector<throw_element> 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<throw_element> r(2);
+ std::vector<throw_element> 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<throw_element> v1(2);
+ std::vector<throw_element> v2(v1);
+ } catch (int) {
+ }
+ check_new_delete_called();
+ }
+
+#if TEST_STD_VER > 3
+ // vector(initializer_list<value_type> __il)
+ {
+ throw_context ctx(6);
+ try {
+ throw_element e;
+ std::vector<throw_element> v({e, e, e});
+ } catch (int) {
+ }
+ check_new_delete_called();
+ }
+
+ // vector(initializer_list<value_type> __il, const allocator_type& __a)
+ {
+ throw_context ctx(6);
+ AllocType alloc;
+ try {
+ throw_element e;
+ std::vector<throw_element> v({e, e, e}, alloc);
+ } catch (int) {
+ }
+ check_new_delete_called();
+ }
+#endif
+
+ // void resize(size_type __sz)
+ {
+ // cap < size
+ throw_context ctx;
+ std::vector<throw_element> 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<throw_element> 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<throw_element> v;
+ v.reserve(3);
+ try {
+ std::vector<throw_element> 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<throw_element> v;
+ try {
+ std::vector<throw_element> 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<throw_element> v;
+ try {
+ std::vector<throw_element> 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<value_type> __il)
+ {
+ throw_context ctx(5);
+ std::vector<throw_element> 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<throw_element> v;
+ try {
+ std::vector<throw_element> 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<throw_element> v;
+ v.reserve(10);
+ try {
+ std::vector<throw_element> 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<throw_element> v;
+ try {
+ std::vector<throw_element> data(2);
+ v.insert_range(v.begin(), data);
+ } catch (int) {
+ }
+ check_new_delete_called();
+ }
+#endif
+
+ return 0;
+}
\ No newline at end of file
More information about the libcxx-commits
mailing list