[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