[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
Mon Oct 26 03:52:57 PDT 2026
https://github.com/tomaszlewandowskivalidas created https://github.com/llvm/llvm-project/pull/235691
`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
>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] [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;
+}
More information about the libcxx-commits
mailing list