[libcxx-commits] [libcxx] [libc++] Fix size_t overflow in make_shared<T[]> and allocate_shared<T[]> (PR #223735)
Louis Dionne via libcxx-commits
libcxx-commits at lists.llvm.org
Thu Oct 8 06:49:30 PDT 2026
https://github.com/ldionne updated https://github.com/llvm/llvm-project/pull/223735
>From e5e20dd0adfd38c3b403120ef513b1ad6f804233 Mon Sep 17 00:00:00 2001
From: Louis Dionne <ldionne.2 at gmail.com>
Date: Wed, 26 Aug 2026 09:16:12 -0400
Subject: [PATCH] [libc++] Fix size_t overflow in make_shared<T[]> and
allocate_shared<T[]>
The computation of the number of bytes to allocate could overflow when
the size was large enough. Fix this by using the overflow-checking builtins
to perform the computation instead.
---
libcxx/include/__memory/shared_ptr.h | 24 +++++++++---
.../allocate_shared.array.unbounded.pass.cpp | 38 +++++++++++++++++++
.../make_shared.array.unbounded.pass.cpp | 35 +++++++++++++++++
3 files changed, 91 insertions(+), 6 deletions(-)
diff --git a/libcxx/include/__memory/shared_ptr.h b/libcxx/include/__memory/shared_ptr.h
index 5386eb29e58a2..fd3a7cdcddf3c 100644
--- a/libcxx/include/__memory/shared_ptr.h
+++ b/libcxx/include/__memory/shared_ptr.h
@@ -36,6 +36,7 @@
#include <__memory/uninitialized_algorithms.h>
#include <__memory/uninitialized_multidimensional_algorithms.h>
#include <__memory/unique_ptr.h>
+#include <__new/exceptions.h>
#include <__type_traits/add_reference.h>
#include <__type_traits/conditional.h>
#include <__type_traits/conjunction.h>
@@ -738,17 +739,28 @@ struct __unbounded_array_control_block<_Tp[], _Alloc> : __shared_weak_count {
// Returns the number of bytes required to store a control block followed by the given number
// of elements of _Tp, with the whole storage being aligned to a multiple of _Tp's alignment.
+ //
+ // Throws std::bad_array_new_length if that number of bytes is not representable as a size_t.
_LIBCPP_HIDE_FROM_ABI static constexpr size_t __bytes_for(size_t __elements) {
+ constexpr size_t __align = alignof(__unbounded_array_control_block);
+
// When there's 0 elements, the control block alone is enough since it holds one element.
// Otherwise, we allocate one fewer element than requested because the control block already
- // holds one. Also, we use the bitwise formula below to ensure that we allocate enough bytes
- // for the whole allocation to be a multiple of _Tp's alignment. That formula is taken from [1].
+ // holds one.
+ size_t __bytes = sizeof(__unbounded_array_control_block);
+ if (__elements != 0) {
+ if (__builtin_mul_overflow(__elements - 1, sizeof(_Tp), &__bytes) ||
+ __builtin_add_overflow(__bytes, sizeof(__unbounded_array_control_block), &__bytes))
+ std::__throw_bad_array_new_length();
+ }
+
+ // We use the bitwise formula below to ensure that we allocate enough bytes for the whole
+ // allocation to be a multiple of _Tp's alignment. That formula is taken from [1].
//
// [1]: https://en.wikipedia.org/wiki/Data_structure_alignment#Computing_padding
- size_t __bytes = __elements == 0 ? sizeof(__unbounded_array_control_block)
- : (__elements - 1) * sizeof(_Tp) + sizeof(__unbounded_array_control_block);
- constexpr size_t __align = alignof(__unbounded_array_control_block);
- return (__bytes + __align - 1) & ~(__align - 1);
+ if (__builtin_add_overflow(__bytes, __align - 1, &__bytes))
+ std::__throw_bad_array_new_length();
+ return __bytes & ~(__align - 1);
}
_LIBCPP_HIDE_FROM_ABI_VIRTUAL
diff --git a/libcxx/test/std/utilities/memory/util.smartptr/util.smartptr.shared/util.smartptr.shared.create/allocate_shared.array.unbounded.pass.cpp b/libcxx/test/std/utilities/memory/util.smartptr/util.smartptr.shared/util.smartptr.shared.create/allocate_shared.array.unbounded.pass.cpp
index ee8c9d5ea596f..57b6baa82e40a 100644
--- a/libcxx/test/std/utilities/memory/util.smartptr/util.smartptr.shared/util.smartptr.shared.create/allocate_shared.array.unbounded.pass.cpp
+++ b/libcxx/test/std/utilities/memory/util.smartptr/util.smartptr.shared/util.smartptr.shared.create/allocate_shared.array.unbounded.pass.cpp
@@ -25,8 +25,11 @@
#include <cassert>
#include <concepts>
+#include <cstddef>
#include <cstdint> // std::uintptr_t
+#include <limits>
#include <memory>
+#include <new>
#include <utility>
#include "min_allocator.h"
@@ -39,6 +42,28 @@ concept CanAllocateShared = requires(Args&& ...args) {
{ std::allocate_shared<T>(std::forward<Args>(args)...) } -> std::same_as<std::shared_ptr<T>>;
};
+// Make sure we throw an exception derived from std::bad_alloc when the number of bytes to
+// allocate isn't representable as a std::size_t.
+template <class Array, class Alloc>
+void check_size_overflow([[maybe_unused]] Alloc const& alloc, [[maybe_unused]] std::size_t n) {
+#ifndef TEST_HAS_NO_EXCEPTIONS
+ try {
+ std::shared_ptr<Array> ptr = std::allocate_shared<Array>(alloc, n);
+ assert(false); // expected an exception to be thrown
+ } catch (std::bad_alloc const&) {
+ // expected
+ }
+
+ try {
+ std::remove_extent_t<Array> init{};
+ std::shared_ptr<Array> ptr = std::allocate_shared<Array>(alloc, n, init);
+ assert(false); // expected an exception to be thrown
+ } catch (std::bad_alloc const&) {
+ // expected
+ }
+#endif
+}
+
int main(int, char**) {
// Check behavior for a zero-sized array
{
@@ -463,5 +488,18 @@ int main(int, char**) {
static_assert(!CanAllocateShared<T[], std::allocator<T[]>, std::size_t, int>); // T not constructible from int
}
+ // Check that requesting a number of elements that overflows the computation of the allocation
+ // size is diagnosed instead of leading to an undersized allocation.
+ {
+ constexpr std::size_t max = std::numeric_limits<std::size_t>::max();
+ check_size_overflow<char[]>(std::allocator<char[]>(), max);
+ check_size_overflow<int[]>(std::allocator<int[]>(), max);
+ check_size_overflow<int[]>(std::allocator<int[]>(), max / sizeof(int) + 2);
+ check_size_overflow<int[][3]>(std::allocator<int[][3]>(), max / sizeof(int[3]) + 2);
+
+ // Also check with an allocator that isn't std::allocator
+ check_size_overflow<int[]>(min_allocator<int[]>(), max);
+ }
+
return 0;
}
diff --git a/libcxx/test/std/utilities/memory/util.smartptr/util.smartptr.shared/util.smartptr.shared.create/make_shared.array.unbounded.pass.cpp b/libcxx/test/std/utilities/memory/util.smartptr/util.smartptr.shared/util.smartptr.shared.create/make_shared.array.unbounded.pass.cpp
index 56d123a5fc284..a05f2319ffffb 100644
--- a/libcxx/test/std/utilities/memory/util.smartptr/util.smartptr.shared/util.smartptr.shared.create/make_shared.array.unbounded.pass.cpp
+++ b/libcxx/test/std/utilities/memory/util.smartptr/util.smartptr.shared/util.smartptr.shared.create/make_shared.array.unbounded.pass.cpp
@@ -24,8 +24,11 @@
#include <cassert>
#include <concepts>
+#include <cstddef>
#include <cstdint> // std::uintptr_t
+#include <limits>
#include <memory>
+#include <new>
#include <utility>
#include "operator_hijacker.h"
@@ -37,6 +40,28 @@ concept CanMakeShared = requires(Args&& ...args) {
{ std::make_shared<T>(std::forward<Args>(args)...) } -> std::same_as<std::shared_ptr<T>>;
};
+// Make sure we throw an exception derived from std::bad_alloc when the number of bytes to
+// allocate isn't representable as a std::size_t.
+template <class Array>
+void check_size_overflow([[maybe_unused]] std::size_t n) {
+#ifndef TEST_HAS_NO_EXCEPTIONS
+ try {
+ std::shared_ptr<Array> ptr = std::make_shared<Array>(n);
+ assert(false); // expected an exception to be thrown
+ } catch (std::bad_alloc const&) {
+ // expected
+ }
+
+ try {
+ std::remove_extent_t<Array> init{};
+ std::shared_ptr<Array> ptr = std::make_shared<Array>(n, init);
+ assert(false); // expected an exception to be thrown
+ } catch (std::bad_alloc const&) {
+ // expected
+ }
+#endif
+}
+
int main(int, char**) {
// Check behavior for a zero-sized array
{
@@ -434,5 +459,15 @@ int main(int, char**) {
static_assert(!CanMakeShared<T[], std::size_t, T, int>); // too many arguments
}
+ // Check that requesting a number of elements that overflows the computation of the allocation
+ // size is diagnosed instead of leading to an undersized allocation.
+ {
+ constexpr std::size_t max = std::numeric_limits<std::size_t>::max();
+ check_size_overflow<char[]>(max);
+ check_size_overflow<int[]>(max);
+ check_size_overflow<int[]>(max / sizeof(int) + 2);
+ check_size_overflow<int[][3]>(max / sizeof(int[3]) + 2);
+ }
+
return 0;
}
More information about the libcxx-commits
mailing list