[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
Sat Nov 15 19:38:11 PST 2025


https://github.com/kisuhorikka updated https://github.com/llvm/llvm-project/pull/167112

>From 1b34bbd6eb9d96e88933a5e1e9a159321b2e7129 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       |  31 +-
 libcxx/include/__vector/vector.h              |  10 +-
 .../exception_construct_at_end.pass.cpp       | 351 ++++++++++++++++++
 .../allocate_shared_for_overwrite.pass.cpp    |  10 +-
 .../make_shared_for_overwrite.pass.cpp        |  10 +-
 5 files changed, 379 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 <class _ValueType, class _ForwardIterator, class _Size, class _Tp>
-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<void*>(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 <class _ValueType, class _ForwardIterator, class _Size>
+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 <class _ValueType, class _ForwardIterator, class _Size>
-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<void*>(std::addressof(*__idx))) _ValueType;
-  __guard.__complete();
-
-  return __idx;
-}
-
 template <class _ForwardIterator, class _Size>
 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 <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<_Tp>(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_fill_n<_Tp>(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..b94e7a142df53
--- /dev/null
+++ b/libcxx/test/std/containers/sequences/vector/exception_construct_at_end.pass.cpp
@@ -0,0 +1,351 @@
+//===----------------------------------------------------------------------===//
+//
+// 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
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<std::shared_ptr<int[2]>> decltype(auto) ptr =
         std::allocate_shared_for_overwrite<int[2]>(AllocatorWithPattern<int>{});
-    assert(*(reinterpret_cast<char*>(&ptr[0])) == AllocatorWithPattern<int>::pattern);
-    assert(*(reinterpret_cast<char*>(&ptr[1])) == AllocatorWithPattern<int>::pattern);
+    assert(*(reinterpret_cast<char*>(&ptr[0])) == 0);
+    assert(*(reinterpret_cast<char*>(&ptr[1])) == 0);
   }
 
   // unbounded array int[]
   {
     std::same_as<std::shared_ptr<int[]>> decltype(auto) ptr =
         std::allocate_shared_for_overwrite<int[]>(AllocatorWithPattern<int>{}, 3);
-    assert(*(reinterpret_cast<char*>(&ptr[0])) == AllocatorWithPattern<int>::pattern);
-    assert(*(reinterpret_cast<char*>(&ptr[1])) == AllocatorWithPattern<int>::pattern);
-    assert(*(reinterpret_cast<char*>(&ptr[2])) == AllocatorWithPattern<int>::pattern);
+    assert(*(reinterpret_cast<char*>(&ptr[0])) == 0);
+    assert(*(reinterpret_cast<char*>(&ptr[1])) == 0);
+    assert(*(reinterpret_cast<char*>(&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<std::shared_ptr<int[2]>> decltype(auto) ptr = std::make_shared_for_overwrite<int[2]>();
-    assert(*(reinterpret_cast<char*>(&ptr[0])) == pattern);
-    assert(*(reinterpret_cast<char*>(&ptr[1])) == pattern);
+    assert(*(reinterpret_cast<char*>(&ptr[0])) == 0);
+    assert(*(reinterpret_cast<char*>(&ptr[1])) == 0);
   }
 
   // unbounded array int[]
   {
     std::same_as<std::shared_ptr<int[]>> decltype(auto) ptr = std::make_shared_for_overwrite<int[]>(3);
-    assert(*(reinterpret_cast<char*>(&ptr[0])) == pattern);
-    assert(*(reinterpret_cast<char*>(&ptr[1])) == pattern);
-    assert(*(reinterpret_cast<char*>(&ptr[2])) == pattern);
+    assert(*(reinterpret_cast<char*>(&ptr[0])) == 0);
+    assert(*(reinterpret_cast<char*>(&ptr[1])) == 0);
+    assert(*(reinterpret_cast<char*>(&ptr[2])) == 0);
   }
 
   // single with default constructor



More information about the libcxx-commits mailing list