[libcxx-commits] [libcxx] [libc++] Fix shared_ptr<T[]>(Y*) rollback to use delete[] when allocation fails (PR #235691)

Tomasz Lewandowski via libcxx-commits libcxx-commits at lists.llvm.org
Fri Oct 30 03:04:22 PDT 2026


https://github.com/tomaszlewandowskivalidas updated https://github.com/llvm/llvm-project/pull/235691

>From ef7d3d559ff87349f83ba05d1689d1a797d2c3a5 Mon Sep 17 00:00:00 2001
From: Tomasz Lewandowski <tomasz.lewandowski at validas.de>
Date: Mon, 26 Oct 2026 10:55:30 +0100
Subject: [PATCH 1/5] [libc++] Fix shared_ptr<T[]>(Y*) rollback to use delete[]
 when allocation fails

shared_ptr<T[]>::shared_ptr(Y*) held the pointer in a unique_ptr<Y>
while allocating the control block. If that allocation threw, the
unique_ptr called scalar delete on the array pointer. For types with a
non-trivial destructor the array has a cookie, so this freed an address
that was never returned by operator new[] and aborted.

Use an exception guard that calls __shared_ptr_default_delete<T, Y>, so
delete[] is used for array types, as [util.smartptr.shared.const]
requires.

Fixes #234974
---
 libcxx/include/__memory/shared_ptr.h          | 16 +++--
 .../shared_ptr.array.deallocation.pass.cpp    | 65 +++++++++++++++++++
 2 files changed, 76 insertions(+), 5 deletions(-)
 create mode 100644 libcxx/test/std/utilities/memory/util.smartptr/util.smartptr.shared/util.smartptr.shared.const/shared_ptr.array.deallocation.pass.cpp

diff --git a/libcxx/include/__memory/shared_ptr.h b/libcxx/include/__memory/shared_ptr.h
index 0db58b37a6288b..0703f3c246623c 100644
--- a/libcxx/include/__memory/shared_ptr.h
+++ b/libcxx/include/__memory/shared_ptr.h
@@ -300,12 +300,18 @@ class _LIBCPP_SHARED_PTR_TRIVIAL_ABI shared_ptr {
                               ,
                           int> = 0>
   _LIBCPP_HIDE_FROM_ABI explicit shared_ptr(_Yp* __p) : __ptr_(__p) {
-    unique_ptr<_Yp> __hold(__p);
-    typedef typename __shared_ptr_default_allocator<_Yp>::type _AllocT;
-    typedef __shared_ptr_pointer<_Yp*, __shared_ptr_default_delete<_Tp, _Yp>, _AllocT> _CntrlBlk;
-    __cntrl_ = new _CntrlBlk(__p, __shared_ptr_default_delete<_Tp, _Yp>(), _AllocT());
-    __hold.release();
+    using _Dp       = __shared_ptr_default_delete<_Tp, _Yp>;
+    using _AllocT   = typename __shared_ptr_default_allocator<_Yp>::type;
+    using _CntrlBlk = __shared_ptr_pointer<_Yp*, _Dp, _AllocT>;
+    _Dp __d{};
+    auto __guard = std::__make_exception_guard([&] { __d(__p); });
+#ifndef _LIBCPP_CXX03_LANG
+    __cntrl_ = new _CntrlBlk(__p, std::move(__d), _AllocT());
+#else
+    __cntrl_ = new _CntrlBlk(__p, __d, _AllocT());
+#endif // not _LIBCPP_CXX03_LANG
     __enable_weak_this(__p, __p);
+    __guard.__complete();
   }
 
   template <class _Yp, class _Dp, __enable_if_t<__shared_ptr_deleter_ctor_reqs_v<_Dp, _Yp, _Tp>, int> = 0>
diff --git a/libcxx/test/std/utilities/memory/util.smartptr/util.smartptr.shared/util.smartptr.shared.const/shared_ptr.array.deallocation.pass.cpp b/libcxx/test/std/utilities/memory/util.smartptr/util.smartptr.shared/util.smartptr.shared.const/shared_ptr.array.deallocation.pass.cpp
new file mode 100644
index 00000000000000..5280fa20c06890
--- /dev/null
+++ b/libcxx/test/std/utilities/memory/util.smartptr/util.smartptr.shared/util.smartptr.shared.const/shared_ptr.array.deallocation.pass.cpp
@@ -0,0 +1,65 @@
+//===----------------------------------------------------------------------===//
+//
+// 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: c++03, c++1, c++14
+// UNSUPPORTED: no-exceptions
+
+// <memory>
+
+// template<class Y> explicit shared_ptr(Y* p);
+
+#include <memory>
+#include <new>
+#include <cstdlib>
+#include <cassert>
+
+#include "count_new.h"
+#include "test_macros.h"
+
+// virtual destructor forces the compiler to emit an array cookie
+struct ElemVirtualDtor {
+  int v                      = 0;
+  virtual ~ElemVirtualDtor() = default;
+};
+
+// non-trivial destructor forces the compiler to emit an array cookie
+struct ElemNonTrivialDtor {
+  int v = 0;
+  ~ElemNonTrivialDtor() { v = 42; }
+};
+
+// custom `operator delete[](void* p, std::size_t s)` forces the compiler to emit an array cookie
+struct ElemWithDeleter {
+  int v = 0;
+  // a class-specific sized operator delete[] also requires an array cookie.
+  static void operator delete[](void* p, std::size_t s) noexcept { ::operator delete[](p, s); }
+};
+
+template <typename T, std::size_t N>
+void run() {
+  globalMemCounter.reset();
+  T* arr = new T[N];
+  assert(globalMemCounter.checkOutstandingArrayNewEq(1));
+  globalMemCounter.throw_after = 0;
+
+  try {
+    std::shared_ptr<T[]> sp(arr);
+    assert(false);
+  } catch (const std::bad_alloc&) {
+    assert(globalMemCounter.checkOutstandingNewEq(0));
+    assert(globalMemCounter.outstanding_array_new == 0);
+  }
+}
+
+int main(int, char**) {
+  constexpr std::size_t N = 10;
+  run<ElemVirtualDtor, N>();
+  run<ElemNonTrivialDtor, N>();
+  run<ElemWithDeleter, N>();
+  return 0;
+}

>From 7296d2cbb5cdbaffedb3041639238e737236b6c1 Mon Sep 17 00:00:00 2001
From: Tomasz Lewandowski <tomasz.lewandowski at validas.de>
Date: Tue, 27 Oct 2026 11:35:45 +0100
Subject: [PATCH 2/5] [libc++] Address review: use unique_ptr<Y[]> guard for
 arrays

---
 libcxx/include/__memory/shared_ptr.h           | 18 ++++++++----------
 .../shared_ptr.array.deallocation.pass.cpp     |  2 +-
 2 files changed, 9 insertions(+), 11 deletions(-)

diff --git a/libcxx/include/__memory/shared_ptr.h b/libcxx/include/__memory/shared_ptr.h
index 0703f3c246623c..99476f71d9c5e8 100644
--- a/libcxx/include/__memory/shared_ptr.h
+++ b/libcxx/include/__memory/shared_ptr.h
@@ -300,18 +300,16 @@ class _LIBCPP_SHARED_PTR_TRIVIAL_ABI shared_ptr {
                               ,
                           int> = 0>
   _LIBCPP_HIDE_FROM_ABI explicit shared_ptr(_Yp* __p) : __ptr_(__p) {
-    using _Dp       = __shared_ptr_default_delete<_Tp, _Yp>;
-    using _AllocT   = typename __shared_ptr_default_allocator<_Yp>::type;
-    using _CntrlBlk = __shared_ptr_pointer<_Yp*, _Dp, _AllocT>;
-    _Dp __d{};
-    auto __guard = std::__make_exception_guard([&] { __d(__p); });
-#ifndef _LIBCPP_CXX03_LANG
-    __cntrl_ = new _CntrlBlk(__p, std::move(__d), _AllocT());
+#if _LIBCPP_STD_VER >= 17
+    unique_ptr<_If<is_array<_Tp>::value, _Yp[], _Yp> > __hold(__p);
 #else
-    __cntrl_ = new _CntrlBlk(__p, __d, _AllocT());
-#endif // not _LIBCPP_CXX03_LANG
+    unique_ptr<_Yp> __hold(__p);
+#endif
+    typedef typename __shared_ptr_default_allocator<_Yp>::type _AllocT;
+    typedef __shared_ptr_pointer<_Yp*, __shared_ptr_default_delete<_Tp, _Yp>, _AllocT> _CntrlBlk;
+    __cntrl_ = new _CntrlBlk(__p, __shared_ptr_default_delete<_Tp, _Yp>(), _AllocT());
+    __hold.release();
     __enable_weak_this(__p, __p);
-    __guard.__complete();
   }
 
   template <class _Yp, class _Dp, __enable_if_t<__shared_ptr_deleter_ctor_reqs_v<_Dp, _Yp, _Tp>, int> = 0>
diff --git a/libcxx/test/std/utilities/memory/util.smartptr/util.smartptr.shared/util.smartptr.shared.const/shared_ptr.array.deallocation.pass.cpp b/libcxx/test/std/utilities/memory/util.smartptr/util.smartptr.shared/util.smartptr.shared.const/shared_ptr.array.deallocation.pass.cpp
index 5280fa20c06890..f06e71e93ea3f9 100644
--- a/libcxx/test/std/utilities/memory/util.smartptr/util.smartptr.shared/util.smartptr.shared.const/shared_ptr.array.deallocation.pass.cpp
+++ b/libcxx/test/std/utilities/memory/util.smartptr/util.smartptr.shared/util.smartptr.shared.const/shared_ptr.array.deallocation.pass.cpp
@@ -6,7 +6,7 @@
 //
 //===----------------------------------------------------------------------===//
 
-// UNSUPPORTED: c++03, c++1, c++14
+// REQUIRES: std-at-least-c++17
 // UNSUPPORTED: no-exceptions
 
 // <memory>

>From a388793791fd74a27ba0e9c22f8818503d290920 Mon Sep 17 00:00:00 2001
From: Tomasz Lewandowski <tomasz.lewandowski at validas.de>
Date: Tue, 27 Oct 2026 14:36:52 +0100
Subject: [PATCH 3/5] [libc++] Address review: Used C++17 syntax

Co-authored-by: A. Jiang <de34 at live.cn>
---
 libcxx/include/__memory/shared_ptr.h | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/libcxx/include/__memory/shared_ptr.h b/libcxx/include/__memory/shared_ptr.h
index 99476f71d9c5e8..ae1abce43e100b 100644
--- a/libcxx/include/__memory/shared_ptr.h
+++ b/libcxx/include/__memory/shared_ptr.h
@@ -301,7 +301,7 @@ class _LIBCPP_SHARED_PTR_TRIVIAL_ABI shared_ptr {
                           int> = 0>
   _LIBCPP_HIDE_FROM_ABI explicit shared_ptr(_Yp* __p) : __ptr_(__p) {
 #if _LIBCPP_STD_VER >= 17
-    unique_ptr<_If<is_array<_Tp>::value, _Yp[], _Yp> > __hold(__p);
+    unique_ptr<_If<is_array_v<_Tp>, _Yp[], _Yp>> __hold(__p);
 #else
     unique_ptr<_Yp> __hold(__p);
 #endif

>From 0cc8d73045af0f1b096607edce7cfc1c0b93fb59 Mon Sep 17 00:00:00 2001
From: Tomasz Lewandowski <tomasz.lewandowski at validas.de>
Date: Thu, 29 Oct 2026 15:12:38 +0100
Subject: [PATCH 4/5] [libc++] Address review: add __copy_arrayness_to, drop
 sized delete[] from test

---
 libcxx/include/__memory/shared_ptr.h          | 21 +++++++++++----
 .../shared_ptr.array.deallocation.pass.cpp    | 26 +++++++++----------
 2 files changed, 29 insertions(+), 18 deletions(-)

diff --git a/libcxx/include/__memory/shared_ptr.h b/libcxx/include/__memory/shared_ptr.h
index ae1abce43e100b..da01df8beb8965 100644
--- a/libcxx/include/__memory/shared_ptr.h
+++ b/libcxx/include/__memory/shared_ptr.h
@@ -264,6 +264,21 @@ inline const bool __shared_ptr_nullptr_deleter_ctor_reqs_v =
 #  define _LIBCPP_SHARED_PTR_TRIVIAL_ABI
 #endif
 
+template <class _Tp, class _Yp>
+struct __copy_arrayness_to {
+  using type _LIBCPP_NODEBUG = _Yp;
+};
+
+template <class _Tp, class _Yp>
+struct __copy_arrayness_to<_Tp[], _Yp> {
+  using type _LIBCPP_NODEBUG = _Yp[];
+};
+
+template <class _Tp, size_t _Count, class _Yp>
+struct __copy_arrayness_to<_Tp[_Count], _Yp> {
+  using type _LIBCPP_NODEBUG = _Yp[];
+};
+
 template <class _Tp>
 class _LIBCPP_SHARED_PTR_TRIVIAL_ABI shared_ptr {
   struct __nullptr_sfinae_tag {};
@@ -300,11 +315,7 @@ class _LIBCPP_SHARED_PTR_TRIVIAL_ABI shared_ptr {
                               ,
                           int> = 0>
   _LIBCPP_HIDE_FROM_ABI explicit shared_ptr(_Yp* __p) : __ptr_(__p) {
-#if _LIBCPP_STD_VER >= 17
-    unique_ptr<_If<is_array_v<_Tp>, _Yp[], _Yp>> __hold(__p);
-#else
-    unique_ptr<_Yp> __hold(__p);
-#endif
+    unique_ptr<typename __copy_arrayness_to<_Tp, _Yp>::type> __hold(__p);
     typedef typename __shared_ptr_default_allocator<_Yp>::type _AllocT;
     typedef __shared_ptr_pointer<_Yp*, __shared_ptr_default_delete<_Tp, _Yp>, _AllocT> _CntrlBlk;
     __cntrl_ = new _CntrlBlk(__p, __shared_ptr_default_delete<_Tp, _Yp>(), _AllocT());
diff --git a/libcxx/test/std/utilities/memory/util.smartptr/util.smartptr.shared/util.smartptr.shared.const/shared_ptr.array.deallocation.pass.cpp b/libcxx/test/std/utilities/memory/util.smartptr/util.smartptr.shared/util.smartptr.shared.const/shared_ptr.array.deallocation.pass.cpp
index f06e71e93ea3f9..9b9bbd60803399 100644
--- a/libcxx/test/std/utilities/memory/util.smartptr/util.smartptr.shared/util.smartptr.shared.const/shared_ptr.array.deallocation.pass.cpp
+++ b/libcxx/test/std/utilities/memory/util.smartptr/util.smartptr.shared/util.smartptr.shared.const/shared_ptr.array.deallocation.pass.cpp
@@ -6,7 +6,6 @@
 //
 //===----------------------------------------------------------------------===//
 
-// REQUIRES: std-at-least-c++17
 // UNSUPPORTED: no-exceptions
 
 // <memory>
@@ -15,7 +14,6 @@
 
 #include <memory>
 #include <new>
-#include <cstdlib>
 #include <cassert>
 
 #include "count_new.h"
@@ -23,8 +21,7 @@
 
 // virtual destructor forces the compiler to emit an array cookie
 struct ElemVirtualDtor {
-  int v                      = 0;
-  virtual ~ElemVirtualDtor() = default;
+  virtual ~ElemVirtualDtor() {}
 };
 
 // non-trivial destructor forces the compiler to emit an array cookie
@@ -33,16 +30,10 @@ struct ElemNonTrivialDtor {
   ~ElemNonTrivialDtor() { v = 42; }
 };
 
-// custom `operator delete[](void* p, std::size_t s)` forces the compiler to emit an array cookie
-struct ElemWithDeleter {
-  int v = 0;
-  // a class-specific sized operator delete[] also requires an array cookie.
-  static void operator delete[](void* p, std::size_t s) noexcept { ::operator delete[](p, s); }
-};
-
 template <typename T, std::size_t N>
 void run() {
   globalMemCounter.reset();
+#if TEST_STD_VER >= 17
   T* arr = new T[N];
   assert(globalMemCounter.checkOutstandingArrayNewEq(1));
   globalMemCounter.throw_after = 0;
@@ -54,12 +45,21 @@ void run() {
     assert(globalMemCounter.checkOutstandingNewEq(0));
     assert(globalMemCounter.outstanding_array_new == 0);
   }
+#else
+  T* p                         = new T;
+  globalMemCounter.throw_after = 0;
+  try {
+    std::shared_ptr<T> sp(p);
+    assert(false);
+  } catch (const std::bad_alloc&) {
+    assert(globalMemCounter.checkOutstandingNewEq(0));
+  }
+#endif
 }
 
 int main(int, char**) {
-  constexpr std::size_t N = 10;
+  const std::size_t N = 10;
   run<ElemVirtualDtor, N>();
   run<ElemNonTrivialDtor, N>();
-  run<ElemWithDeleter, N>();
   return 0;
 }

>From 6800d0f291d07645fd358f33b3c98643c4864bec Mon Sep 17 00:00:00 2001
From: Tomasz Lewandowski <tomasz.lewandowski at validas.de>
Date: Fri, 30 Oct 2026 11:03:49 +0100
Subject: [PATCH 5/5] [libc++] Unsupported sanitizer-new-delete for the
 deallocation test

---
 .../shared_ptr.array.deallocation.pass.cpp                       | 1 +
 1 file changed, 1 insertion(+)

diff --git a/libcxx/test/std/utilities/memory/util.smartptr/util.smartptr.shared/util.smartptr.shared.const/shared_ptr.array.deallocation.pass.cpp b/libcxx/test/std/utilities/memory/util.smartptr/util.smartptr.shared/util.smartptr.shared.const/shared_ptr.array.deallocation.pass.cpp
index 9b9bbd60803399..2495f34eb3a5a8 100644
--- a/libcxx/test/std/utilities/memory/util.smartptr/util.smartptr.shared/util.smartptr.shared.const/shared_ptr.array.deallocation.pass.cpp
+++ b/libcxx/test/std/utilities/memory/util.smartptr/util.smartptr.shared/util.smartptr.shared.const/shared_ptr.array.deallocation.pass.cpp
@@ -7,6 +7,7 @@
 //===----------------------------------------------------------------------===//
 
 // UNSUPPORTED: no-exceptions
+// UNSUPPORTED: sanitizer-new-delete
 
 // <memory>
 



More information about the libcxx-commits mailing list