[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