[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