[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
Mon Dec 15 05:01:12 PST 2025
https://github.com/kisuhorikka updated https://github.com/llvm/llvm-project/pull/167112
>From f20062d127879e33748755430c57f80e70915faf Mon Sep 17 00:00:00 2001
From: kisuhorikka <kisuhorikka at gmail.com>
Date: Sat, 8 Nov 2025 16:30:50 +0800
Subject: [PATCH 1/2] [libc++] Destroy elements when exceptions are thrown in
__construct_at_end
---
.../__memory/uninitialized_algorithms.h | 26 ++++++++
libcxx/include/__vector/vector.h | 10 +--
.../std/containers/sequences/vector/common.h | 66 +++++++++++++++++++
.../resize_size_exceptions.pass.cpp | 40 ++++++++++-
.../vector/vector.cons/assign_copy.pass.cpp | 24 +++++++
.../assign_initializer_list.pass.cpp | 23 +++++++
.../vector.cons/assign_iter_iter.pass.cpp | 38 +++++++++++
.../vector.cons/construct_from_range.pass.cpp | 20 ++++++
.../vector.cons/construct_iter_iter.pass.cpp | 20 ++++++
.../construct_iter_iter_alloc.pass.cpp | 21 ++++++
.../vector.cons/construct_size.pass.cpp | 34 ++++++++++
.../vector.cons/construct_size_value.pass.cpp | 20 ++++++
.../construct_size_value_alloc.pass.cpp | 21 ++++++
.../vector/vector.cons/copy_alloc.pass.cpp | 21 ++++++
.../vector.cons/initializer_list.pass.cpp | 21 ++++++
.../initializer_list_alloc.pass.cpp | 22 +++++++
.../vector.modifiers/assign_range.pass.cpp | 24 +++++++
.../insert_iter_lvalue.pass.cpp | 25 ++++++-
.../vector.modifiers/insert_range.pass.cpp | 23 +++++++
19 files changed, 489 insertions(+), 10 deletions(-)
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 <class _Alloc, class _ForwardIterator, class _Size, class _Tp>
+_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 <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_allocator_value_construct_n
+
+template <class _Alloc, class _ForwardIterator, class _Size>
+_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 <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_allocator_value_construct_n(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_allocator_fill_n(this->__alloc_, this->__end_, __n, __x);
}
template <class _Tp, class _Allocator>
diff --git a/libcxx/test/std/containers/sequences/vector/common.h b/libcxx/test/std/containers/sequences/vector/common.h
index 34453f8889b73..91cc7e85a0490 100644
--- a/libcxx/test/std/containers/sequences/vector/common.h
+++ b/libcxx/test/std/containers/sequences/vector/common.h
@@ -260,4 +260,70 @@ inline std::vector<std::string> getStringInputsWithLength(std::size_t n, std::si
return v;
}
+struct leak_throw_context {
+ leak_throw_context(int lim = 2) {
+ num() = 0;
+ limit() = lim;
+ }
+
+ static int& num() {
+ static int n = 0;
+ return n;
+ }
+
+ static int& limit() {
+ static int lim = 0;
+ return lim;
+ }
+
+ static void inc() {
+ ++num();
+ if (num() >= limit()) {
+ --num();
+ throw 1;
+ }
+ }
+
+ static void dec() { --num(); }
+};
+
+class leak_throw_t {
+public:
+ leak_throw_t() : data(new int(1)) {
+ try {
+ leak_throw_context::inc();
+ } catch (int) {
+ delete data;
+ throw;
+ }
+ }
+
+ leak_throw_t(leak_throw_t const&) : data(new int(1)) {
+ try {
+ leak_throw_context::inc();
+ } catch (int) {
+ delete data;
+ throw;
+ }
+ }
+
+ ~leak_throw_t() {
+ if (data) {
+ delete data;
+ leak_throw_context::dec();
+ }
+ }
+
+ leak_throw_t& operator=(leak_throw_t const&) { return *this; }
+
+private:
+ int* data;
+};
+
+#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
+
#endif // TEST_STD_CONTAINERS_SEQUENCES_VECTOR_COMMON_H
diff --git a/libcxx/test/std/containers/sequences/vector/vector.capacity/resize_size_exceptions.pass.cpp b/libcxx/test/std/containers/sequences/vector/vector.capacity/resize_size_exceptions.pass.cpp
index b5e12ea8e5d0a..03efee758ba8b 100644
--- a/libcxx/test/std/containers/sequences/vector/vector.capacity/resize_size_exceptions.pass.cpp
+++ b/libcxx/test/std/containers/sequences/vector/vector.capacity/resize_size_exceptions.pass.cpp
@@ -17,7 +17,9 @@
#include <type_traits>
#include <vector>
-#include "../common.h"
+#ifndef TEST_HAS_NO_EXCEPTIONS
+# include "../common.h"
+#endif
#include "MoveOnly.h"
#include "count_new.h"
#include "increasing_allocator.h"
@@ -384,6 +386,41 @@ void test_move_ctor_exceptions() {
#endif
+void test_resize_exception_leak() {
+ // Test whether __construct_at_end leaks when exception
+#ifndef TEST_HAS_NO_EXCEPTIONS
+# if TEST_STD_VER >= 11
+ {
+ // cap < size
+ leak_throw_context ctx;
+ std::vector<leak_throw_t> 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
+ leak_throw_context ctx(3);
+ std::vector<leak_throw_t> v;
+ v.reserve(5);
+ try {
+ leak_throw_t e;
+ v.resize(4, e);
+ } catch (int) {
+ }
+ CHECK_NEW_DELETE_DIFF(1);
+ }
+ check_new_delete_called();
+# endif
+#endif
+}
+
int main(int, char**) {
test_allocation_exceptions();
test_default_ctor_exceptions();
@@ -391,4 +428,5 @@ int main(int, char**) {
#if TEST_STD_VER >= 11
test_move_ctor_exceptions();
#endif
+ test_resize_exception_leak();
}
diff --git a/libcxx/test/std/containers/sequences/vector/vector.cons/assign_copy.pass.cpp b/libcxx/test/std/containers/sequences/vector/vector.cons/assign_copy.pass.cpp
index 064d807c36ce0..2f4cf9643db52 100644
--- a/libcxx/test/std/containers/sequences/vector/vector.cons/assign_copy.pass.cpp
+++ b/libcxx/test/std/containers/sequences/vector/vector.cons/assign_copy.pass.cpp
@@ -16,6 +16,9 @@
#include "test_allocator.h"
#include "min_allocator.h"
#include "allocators.h"
+#ifndef TEST_HAS_NO_EXCEPTIONS
+# include "../common.h"
+#endif
TEST_CONSTEXPR_CXX20 bool tests() {
{
@@ -90,10 +93,31 @@ TEST_CONSTEXPR_CXX20 bool tests() {
return true;
}
+void test_assign_initializer_exception_leak() {
+ // Test whether __construct_at_end leaks when exception
+
+#ifndef TEST_HAS_NO_EXCEPTIONS
+# if TEST_STD_VER >= 11
+ {
+ leak_throw_context ctx(5);
+ std::vector<leak_throw_t> v;
+ try {
+ leak_throw_t e;
+ v = {e, e};
+ } catch (int) {
+ }
+ CHECK_NEW_DELETE_DIFF(1);
+ }
+ check_new_delete_called();
+# endif
+#endif
+}
+
int main(int, char**) {
tests();
#if TEST_STD_VER > 17
static_assert(tests());
#endif
+ test_assign_initializer_exception_leak();
return 0;
}
diff --git a/libcxx/test/std/containers/sequences/vector/vector.cons/assign_initializer_list.pass.cpp b/libcxx/test/std/containers/sequences/vector/vector.cons/assign_initializer_list.pass.cpp
index 0be881875be2d..3a71890cdba59 100644
--- a/libcxx/test/std/containers/sequences/vector/vector.cons/assign_initializer_list.pass.cpp
+++ b/libcxx/test/std/containers/sequences/vector/vector.cons/assign_initializer_list.pass.cpp
@@ -18,6 +18,9 @@
#include "test_macros.h"
#include "min_allocator.h"
#include "asan_testing.h"
+#ifndef TEST_HAS_NO_EXCEPTIONS
+# include "../common.h"
+#endif
template <typename Vec>
TEST_CONSTEXPR_CXX20 void test(Vec& v) {
@@ -51,10 +54,30 @@ TEST_CONSTEXPR_CXX20 bool tests() {
return true;
}
+void test_assign_initializer_exception_leak() {
+ // Test whether __construct_at_end leaks when exception
+#ifndef TEST_HAS_NO_EXCEPTIONS
+# if TEST_STD_VER >= 11
+ {
+ leak_throw_context ctx(5);
+ std::vector<leak_throw_t> v;
+ try {
+ leak_throw_t e;
+ v = {e, e};
+ } catch (int) {
+ }
+ CHECK_NEW_DELETE_DIFF(1);
+ }
+ check_new_delete_called();
+# endif
+#endif
+}
+
int main(int, char**) {
tests();
#if TEST_STD_VER > 17
static_assert(tests());
#endif
+ test_assign_initializer_exception_leak();
return 0;
}
diff --git a/libcxx/test/std/containers/sequences/vector/vector.cons/assign_iter_iter.pass.cpp b/libcxx/test/std/containers/sequences/vector/vector.cons/assign_iter_iter.pass.cpp
index 91060679cd7f9..cafcc7d8bdb58 100644
--- a/libcxx/test/std/containers/sequences/vector/vector.cons/assign_iter_iter.pass.cpp
+++ b/libcxx/test/std/containers/sequences/vector/vector.cons/assign_iter_iter.pass.cpp
@@ -22,6 +22,9 @@
# include "emplace_constructible.h"
# include "container_test_types.h"
#endif
+#ifndef TEST_HAS_NO_EXCEPTIONS
+# include "../common.h"
+#endif
TEST_CONSTEXPR_CXX20 bool test() {
#if TEST_STD_VER >= 11
@@ -175,10 +178,45 @@ TEST_CONSTEXPR_CXX20 bool test() {
return true;
}
+void test_assign_exception_leak() {
+ // Test whether __construct_at_end leaks when exception
+#ifndef TEST_HAS_NO_EXCEPTIONS
+# if TEST_STD_VER >= 11
+ {
+ // new size <= cap && new size > size
+ leak_throw_context ctx(4);
+ std::vector<leak_throw_t> v;
+ v.reserve(3);
+ try {
+ std::vector<leak_throw_t> data(2);
+ v.assign(data.begin(), data.end());
+ } catch (int) {
+ }
+ CHECK_NEW_DELETE_DIFF(1);
+ }
+ check_new_delete_called();
+
+ {
+ // new size > cap
+ leak_throw_context ctx(4);
+ std::vector<leak_throw_t> v;
+ try {
+ std::vector<leak_throw_t> data(2);
+ v.assign(data.begin(), data.end());
+ } catch (int) {
+ }
+ CHECK_NEW_DELETE_DIFF(1);
+ }
+ check_new_delete_called();
+# endif
+#endif
+}
+
int main(int, char**) {
test();
#if TEST_STD_VER > 17
static_assert(test());
#endif
+ test_assign_exception_leak();
return 0;
}
diff --git a/libcxx/test/std/containers/sequences/vector/vector.cons/construct_from_range.pass.cpp b/libcxx/test/std/containers/sequences/vector/vector.cons/construct_from_range.pass.cpp
index 501abf396391f..120a26096bb89 100644
--- a/libcxx/test/std/containers/sequences/vector/vector.cons/construct_from_range.pass.cpp
+++ b/libcxx/test/std/containers/sequences/vector/vector.cons/construct_from_range.pass.cpp
@@ -17,6 +17,9 @@
#include "../../from_range_sequence_containers.h"
#include "asan_testing.h"
#include "test_macros.h"
+#ifndef TEST_HAS_NO_EXCEPTIONS
+# include "../common.h"
+#endif
constexpr bool test() {
for_all_iterators_and_allocators<int>([]<class Iter, class Sent, class Alloc>() {
@@ -46,6 +49,21 @@ void test_counted_istream_view() {
}
#endif
+void test_cons_leak() {
+ // Test whether __construct_at_end leaks when exception
+#ifndef TEST_HAS_NO_EXCEPTIONS
+# if TEST_STD_VER >= 11
+ leak_throw_context ctx(4);
+ try {
+ std::vector<leak_throw_t> r(2);
+ std::vector<leak_throw_t> v(std::from_range, std::views::counted(r.begin(), 2));
+ } catch (int) {
+ }
+ check_new_delete_called();
+# endif
+#endif
+}
+
int main(int, char**) {
static_assert(test_constraints<std::vector, int, double>());
test();
@@ -59,5 +77,7 @@ int main(int, char**) {
test_counted_istream_view();
#endif
+ test_cons_leak();
+
return 0;
}
diff --git a/libcxx/test/std/containers/sequences/vector/vector.cons/construct_iter_iter.pass.cpp b/libcxx/test/std/containers/sequences/vector/vector.cons/construct_iter_iter.pass.cpp
index 1a6364a8018bc..21ed3ed40edfb 100644
--- a/libcxx/test/std/containers/sequences/vector/vector.cons/construct_iter_iter.pass.cpp
+++ b/libcxx/test/std/containers/sequences/vector/vector.cons/construct_iter_iter.pass.cpp
@@ -23,6 +23,9 @@
# include "emplace_constructible.h"
# include "container_test_types.h"
#endif
+#ifndef TEST_HAS_NO_EXCEPTIONS
+# include "../common.h"
+#endif
template <class C, class Iterator>
TEST_CONSTEXPR_CXX20 void test(Iterator first, Iterator last) {
@@ -235,11 +238,28 @@ TEST_CONSTEXPR_CXX20 bool tests() {
return true;
}
+void test_cons_leak() {
+ // Test whether __construct_at_end leaks when exception
+#ifndef TEST_HAS_NO_EXCEPTIONS
+# if TEST_STD_VER >= 11
+ leak_throw_context ctx(4);
+ try {
+ std::vector<leak_throw_t> v1(2);
+ std::vector<leak_throw_t> v2(v1.begin(), v1.end());
+ } catch (int) {
+ }
+ check_new_delete_called();
+# endif
+#endif
+}
+
int main(int, char**) {
tests();
test_ctor_under_alloc();
#if TEST_STD_VER > 17
static_assert(tests());
#endif
+
+ test_cons_leak();
return 0;
}
diff --git a/libcxx/test/std/containers/sequences/vector/vector.cons/construct_iter_iter_alloc.pass.cpp b/libcxx/test/std/containers/sequences/vector/vector.cons/construct_iter_iter_alloc.pass.cpp
index d1eff51011c4f..c2eb3c34d91a7 100644
--- a/libcxx/test/std/containers/sequences/vector/vector.cons/construct_iter_iter_alloc.pass.cpp
+++ b/libcxx/test/std/containers/sequences/vector/vector.cons/construct_iter_iter_alloc.pass.cpp
@@ -24,6 +24,9 @@
# include "emplace_constructible.h"
# include "container_test_types.h"
#endif
+#ifndef TEST_HAS_NO_EXCEPTIONS
+# include "../common.h"
+#endif
template <class C, class Iterator, class A>
TEST_CONSTEXPR_CXX20 void test(Iterator first, Iterator last, const A& a) {
@@ -213,6 +216,22 @@ TEST_CONSTEXPR_CXX20 bool test() {
return true;
}
+void test_cons_leak() {
+ // Test whether __construct_at_end leaks when exception
+#ifndef TEST_HAS_NO_EXCEPTIONS
+# if TEST_STD_VER >= 11
+ leak_throw_context ctx(4);
+ std::allocator<leak_throw_t> alloc;
+ try {
+ std::vector<leak_throw_t> v1(2);
+ std::vector<leak_throw_t> v2(v1.begin(), v1.end(), alloc);
+ } catch (int) {
+ }
+ check_new_delete_called();
+# endif
+#endif
+}
+
int main(int, char**) {
test();
#if TEST_STD_VER > 17
@@ -220,5 +239,7 @@ int main(int, char**) {
#endif
test_ctor_under_alloc();
+ test_cons_leak();
+
return 0;
}
diff --git a/libcxx/test/std/containers/sequences/vector/vector.cons/construct_size.pass.cpp b/libcxx/test/std/containers/sequences/vector/vector.cons/construct_size.pass.cpp
index cbae3653d995f..e7746a7401628 100644
--- a/libcxx/test/std/containers/sequences/vector/vector.cons/construct_size.pass.cpp
+++ b/libcxx/test/std/containers/sequences/vector/vector.cons/construct_size.pass.cpp
@@ -19,6 +19,9 @@
#include "min_allocator.h"
#include "test_allocator.h"
#include "asan_testing.h"
+#ifndef TEST_HAS_NO_EXCEPTIONS
+# include "../common.h"
+#endif
template <class C>
TEST_CONSTEXPR_CXX20 void
@@ -64,6 +67,35 @@ TEST_CONSTEXPR_CXX20 bool tests() {
return true;
}
+void test_cons_leak() {
+ // Test whether __construct_at_end leaks when exception
+#ifndef TEST_HAS_NO_EXCEPTIONS
+# if TEST_STD_VER >= 11
+ {
+ leak_throw_context ctx;
+ try {
+ std::vector<leak_throw_t> v(3);
+ } catch (int) {
+ }
+ check_new_delete_called();
+ }
+
+ // TODO: This constructor may be applied to C++11 according to https://cplusplus.github.io/LWG/issue2210
+# if TEST_STD_VER >= 14
+ {
+ leak_throw_context ctx;
+ std::allocator<leak_throw_t> alloc;
+ try {
+ std::vector<leak_throw_t> v(3, alloc);
+ } catch (int) {
+ }
+ check_new_delete_called();
+ }
+# endif
+# endif
+#endif
+}
+
int main(int, char**) {
tests();
#if TEST_STD_VER > 17
@@ -83,5 +115,7 @@ int main(int, char**) {
assert(DefaultOnly::count == 0);
#endif
+ test_cons_leak();
+
return 0;
}
diff --git a/libcxx/test/std/containers/sequences/vector/vector.cons/construct_size_value.pass.cpp b/libcxx/test/std/containers/sequences/vector/vector.cons/construct_size_value.pass.cpp
index de2df027dd530..ae430249da4fc 100644
--- a/libcxx/test/std/containers/sequences/vector/vector.cons/construct_size_value.pass.cpp
+++ b/libcxx/test/std/containers/sequences/vector/vector.cons/construct_size_value.pass.cpp
@@ -17,6 +17,9 @@
#include "test_allocator.h"
#include "min_allocator.h"
#include "asan_testing.h"
+#ifndef TEST_HAS_NO_EXCEPTIONS
+# include "../common.h"
+#endif
template <class C>
TEST_CONSTEXPR_CXX20 void test(typename C::size_type n, const typename C::value_type& x) {
@@ -45,10 +48,27 @@ TEST_CONSTEXPR_CXX20 bool tests() {
return true;
}
+void test_cons_leak() {
+ // Test whether __construct_at_end leaks when exception
+#ifndef TEST_HAS_NO_EXCEPTIONS
+# if TEST_STD_VER >= 11
+ leak_throw_context ctx(3);
+ try {
+ leak_throw_t e;
+ std::vector<leak_throw_t> v(3, e);
+ } catch (int) {
+ }
+ check_new_delete_called();
+# endif
+#endif
+}
+
int main(int, char**) {
tests();
#if TEST_STD_VER > 17
static_assert(tests());
#endif
+
+ test_cons_leak();
return 0;
}
diff --git a/libcxx/test/std/containers/sequences/vector/vector.cons/construct_size_value_alloc.pass.cpp b/libcxx/test/std/containers/sequences/vector/vector.cons/construct_size_value_alloc.pass.cpp
index c6d66d4983170..8f6556e1c72b0 100644
--- a/libcxx/test/std/containers/sequences/vector/vector.cons/construct_size_value_alloc.pass.cpp
+++ b/libcxx/test/std/containers/sequences/vector/vector.cons/construct_size_value_alloc.pass.cpp
@@ -16,6 +16,9 @@
#include "test_macros.h"
#include "min_allocator.h"
#include "asan_testing.h"
+#ifndef TEST_HAS_NO_EXCEPTIONS
+# include "../common.h"
+#endif
template <class C>
TEST_CONSTEXPR_CXX20 void
@@ -42,10 +45,28 @@ TEST_CONSTEXPR_CXX20 bool tests() {
return true;
}
+void test_cons_leak() {
+ // Test whether __construct_at_end leaks when exception
+#ifndef TEST_HAS_NO_EXCEPTIONS
+# if TEST_STD_VER >= 11
+ leak_throw_context ctx(3);
+ try {
+ leak_throw_t e;
+ std::allocator<leak_throw_t> alloc;
+ std::vector<leak_throw_t> v(4, e, alloc);
+ } catch (int) {
+ }
+ check_new_delete_called();
+# endif
+#endif
+}
+
int main(int, char**) {
tests();
#if TEST_STD_VER > 17
static_assert(tests());
#endif
+
+ test_cons_leak();
return 0;
}
diff --git a/libcxx/test/std/containers/sequences/vector/vector.cons/copy_alloc.pass.cpp b/libcxx/test/std/containers/sequences/vector/vector.cons/copy_alloc.pass.cpp
index 2c598d5dc426f..d0ea916a336d9 100644
--- a/libcxx/test/std/containers/sequences/vector/vector.cons/copy_alloc.pass.cpp
+++ b/libcxx/test/std/containers/sequences/vector/vector.cons/copy_alloc.pass.cpp
@@ -17,6 +17,9 @@
#include "test_allocator.h"
#include "min_allocator.h"
#include "asan_testing.h"
+#ifndef TEST_HAS_NO_EXCEPTIONS
+# include "../common.h"
+#endif
template <class C>
TEST_CONSTEXPR_CXX20 void test(const C& x, const typename C::allocator_type& a) {
@@ -78,10 +81,28 @@ TEST_CONSTEXPR_CXX20 bool tests() {
return true;
}
+void test_cons_leak() {
+ // Test whether __construct_at_end leaks when exception
+#ifndef TEST_HAS_NO_EXCEPTIONS
+# if TEST_STD_VER >= 11
+ leak_throw_context ctx(4);
+ try {
+ std::vector<leak_throw_t> v1(2);
+ std::vector<leak_throw_t> v2(v1);
+ } catch (int) {
+ }
+ check_new_delete_called();
+# endif
+#endif
+}
+
int main(int, char**) {
tests();
#if TEST_STD_VER > 17
static_assert(tests());
#endif
+
+ test_cons_leak();
+
return 0;
}
diff --git a/libcxx/test/std/containers/sequences/vector/vector.cons/initializer_list.pass.cpp b/libcxx/test/std/containers/sequences/vector/vector.cons/initializer_list.pass.cpp
index 02bb7f3a3ea9c..88da504ae2cf8 100644
--- a/libcxx/test/std/containers/sequences/vector/vector.cons/initializer_list.pass.cpp
+++ b/libcxx/test/std/containers/sequences/vector/vector.cons/initializer_list.pass.cpp
@@ -17,6 +17,9 @@
#include "test_macros.h"
#include "min_allocator.h"
#include "asan_testing.h"
+#ifndef TEST_HAS_NO_EXCEPTIONS
+# include "../common.h"
+#endif
TEST_CONSTEXPR_CXX20 bool tests() {
{
@@ -50,10 +53,28 @@ TEST_CONSTEXPR_CXX20 bool tests() {
return true;
}
+void test_cons_exception_leak() {
+ // Test whether __construct_at_end leaks when exception
+#ifndef TEST_HAS_NO_EXCEPTIONS
+# if TEST_STD_VER >= 11
+ {
+ leak_throw_context ctx(6);
+ try {
+ leak_throw_t e;
+ std::vector<leak_throw_t> v({e, e, e});
+ } catch (int) {
+ }
+ check_new_delete_called();
+ }
+# endif
+#endif
+}
+
int main(int, char**) {
tests();
#if TEST_STD_VER > 17
static_assert(tests());
#endif
+ test_cons_exception_leak();
return 0;
}
diff --git a/libcxx/test/std/containers/sequences/vector/vector.cons/initializer_list_alloc.pass.cpp b/libcxx/test/std/containers/sequences/vector/vector.cons/initializer_list_alloc.pass.cpp
index bfc9b5a03a617..06f56bbf7dc01 100644
--- a/libcxx/test/std/containers/sequences/vector/vector.cons/initializer_list_alloc.pass.cpp
+++ b/libcxx/test/std/containers/sequences/vector/vector.cons/initializer_list_alloc.pass.cpp
@@ -19,6 +19,9 @@
#include "test_allocator.h"
#include "min_allocator.h"
#include "asan_testing.h"
+#ifndef TEST_HAS_NO_EXCEPTIONS
+# include "../common.h"
+#endif
TEST_CONSTEXPR_CXX20 bool tests() {
{
@@ -67,10 +70,29 @@ TEST_CONSTEXPR_CXX20 bool tests() {
return true;
}
+void test_cons_exception_leak() {
+ // Test whether __construct_at_end leaks when exception
+#ifndef TEST_HAS_NO_EXCEPTIONS
+# if TEST_STD_VER >= 11
+ {
+ leak_throw_context ctx(6);
+ std::allocator<leak_throw_t> alloc;
+ try {
+ leak_throw_t e;
+ std::vector<leak_throw_t> v({e, e, e}, alloc);
+ } catch (int) {
+ }
+ check_new_delete_called();
+ }
+# endif
+#endif
+}
+
int main(int, char**) {
tests();
#if TEST_STD_VER > 17
static_assert(tests());
#endif
+ test_cons_exception_leak();
return 0;
}
diff --git a/libcxx/test/std/containers/sequences/vector/vector.modifiers/assign_range.pass.cpp b/libcxx/test/std/containers/sequences/vector/vector.modifiers/assign_range.pass.cpp
index 132343f455615..1fb76ef62ef28 100644
--- a/libcxx/test/std/containers/sequences/vector/vector.modifiers/assign_range.pass.cpp
+++ b/libcxx/test/std/containers/sequences/vector/vector.modifiers/assign_range.pass.cpp
@@ -18,6 +18,9 @@
#include "../../insert_range_sequence_containers.h"
#include "asan_testing.h"
#include "test_macros.h"
+#ifndef TEST_HAS_NO_EXCEPTIONS
+# include "../common.h"
+#endif
// Tested cases:
// - different kinds of assignments (assigning an {empty/one-element/mid-sized/long range} to an
@@ -86,6 +89,25 @@ void test_counted_istream_view() {
}
#endif
+void test_assign_range_exception_leak() {
+ // Test whether __construct_at_end leaks when exception
+#ifndef TEST_HAS_NO_EXCEPTIONS
+# if TEST_STD_VER >= 11
+ {
+ leak_throw_context ctx(5);
+ std::vector<leak_throw_t> v;
+ try {
+ std::vector<leak_throw_t> r(3);
+ v.assign_range(r);
+ } catch (int) {
+ }
+ CHECK_NEW_DELETE_DIFF(1);
+ }
+ check_new_delete_called();
+# endif
+#endif
+}
+
int main(int, char**) {
test();
static_assert(test());
@@ -97,5 +119,7 @@ int main(int, char**) {
test_counted_istream_view();
#endif
+ test_assign_range_exception_leak();
+
return 0;
}
diff --git a/libcxx/test/std/containers/sequences/vector/vector.modifiers/insert_iter_lvalue.pass.cpp b/libcxx/test/std/containers/sequences/vector/vector.modifiers/insert_iter_lvalue.pass.cpp
index 93a3ce3918615..8aa4135bf448b 100644
--- a/libcxx/test/std/containers/sequences/vector/vector.modifiers/insert_iter_lvalue.pass.cpp
+++ b/libcxx/test/std/containers/sequences/vector/vector.modifiers/insert_iter_lvalue.pass.cpp
@@ -18,6 +18,9 @@
#include "test_allocator.h"
#include "min_allocator.h"
#include "asan_testing.h"
+#ifndef TEST_HAS_NO_EXCEPTIONS
+# include "../common.h"
+#endif
TEST_CONSTEXPR_CXX20 bool test() {
{
@@ -135,11 +138,31 @@ TEST_CONSTEXPR_CXX20 bool test() {
return true;
}
+void test_insert_exception_leak() {
+ // Test whether __construct_at_end leaks when exception
+#ifndef TEST_HAS_NO_EXCEPTIONS
+# if TEST_STD_VER >= 11
+ {
+ leak_throw_context ctx(6);
+ std::vector<leak_throw_t> v;
+ v.reserve(10);
+ try {
+ std::vector<leak_throw_t> data(3);
+ v.insert(v.begin(), data.begin(), data.end());
+ } catch (int) {
+ }
+ CHECK_NEW_DELETE_DIFF(1);
+ }
+ check_new_delete_called();
+# endif
+#endif
+}
+
int main(int, char**) {
test();
#if TEST_STD_VER > 17
static_assert(test());
#endif
-
+ test_insert_exception_leak();
return 0;
}
diff --git a/libcxx/test/std/containers/sequences/vector/vector.modifiers/insert_range.pass.cpp b/libcxx/test/std/containers/sequences/vector/vector.modifiers/insert_range.pass.cpp
index ef63db083a998..a15d6e696c209 100644
--- a/libcxx/test/std/containers/sequences/vector/vector.modifiers/insert_range.pass.cpp
+++ b/libcxx/test/std/containers/sequences/vector/vector.modifiers/insert_range.pass.cpp
@@ -18,6 +18,9 @@
#include "../../insert_range_sequence_containers.h"
#include "asan_testing.h"
#include "test_macros.h"
+#ifndef TEST_HAS_NO_EXCEPTIONS
+# include "../common.h"
+#endif
// Tested cases:
// - different kinds of insertions (inserting an {empty/one-element/mid-sized/long range} into an
@@ -93,6 +96,24 @@ void test_counted_istream_view() {
}
#endif
+void test_insert_range_exception_leak() {
+ // Test whether __construct_at_end leaks when exception
+#ifndef TEST_HAS_NO_EXCEPTIONS
+# if TEST_STD_VER >= 11
+ {
+ leak_throw_context ctx(3);
+ std::vector<leak_throw_t> v;
+ try {
+ std::vector<leak_throw_t> data(2);
+ v.insert_range(v.begin(), data);
+ } catch (int) {
+ }
+ check_new_delete_called();
+ }
+# endif
+#endif
+}
+
int main(int, char**) {
test();
static_assert(test());
@@ -106,5 +127,7 @@ int main(int, char**) {
test_counted_istream_view();
#endif
+ test_insert_range_exception_leak();
+
return 0;
}
>From 763770ed5619244e205637f34b168fecc7a90e19 Mon Sep 17 00:00:00 2001
From: kisuhorikka <kisuhorikka at gmail.com>
Date: Mon, 15 Dec 2025 20:57:11 +0800
Subject: [PATCH 2/2] [libc++] Destroy elements when exceptions are thrown in
__construct_at_end
---
.../__memory/uninitialized_algorithms.h | 26 -------------------
libcxx/include/__vector/vector.h | 10 +++++--
2 files changed, 8 insertions(+), 28 deletions(-)
diff --git a/libcxx/include/__memory/uninitialized_algorithms.h b/libcxx/include/__memory/uninitialized_algorithms.h
index 09a1b43e00f17..34d065dc973e5 100644
--- a/libcxx/include/__memory/uninitialized_algorithms.h
+++ b/libcxx/include/__memory/uninitialized_algorithms.h
@@ -124,18 +124,6 @@ uninitialized_fill(_ForwardIterator __first, _ForwardIterator __last, const _Tp&
// uninitialized_fill_n
-template <class _Alloc, class _ForwardIterator, class _Size, class _Tp>
-_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 <class _ValueType, class _ForwardIterator, class _Size, class _Tp>
inline _LIBCPP_HIDE_FROM_ABI _ForwardIterator
__uninitialized_fill_n(_ForwardIterator __first, _Size __n, const _Tp& __x) {
@@ -155,20 +143,6 @@ 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 <class _Alloc, class _ForwardIterator, class _Size>
-_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 14c387414518e..316d3a9d10eff 100644
--- a/libcxx/include/__vector/vector.h
+++ b/libcxx/include/__vector/vector.h
@@ -941,7 +941,10 @@ 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);
- __tx.__pos_ = std::__uninitialized_allocator_value_construct_n(this->__alloc_, this->__end_, __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));
+ }
}
// Copy constructs __n objects starting at __end_ from __x
@@ -954,7 +957,10 @@ 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);
- __tx.__pos_ = std::__uninitialized_allocator_fill_n(this->__alloc_, this->__end_, __n, __x);
+ 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);
+ }
}
template <class _Tp, class _Allocator>
More information about the libcxx-commits
mailing list