[libcxx-commits] [libcxx] [libc++] Properly implement array cookies in the ARM ABI (PR #160182)
Louis Dionne via libcxx-commits
libcxx-commits at lists.llvm.org
Wed Oct 8 07:00:27 PDT 2025
https://github.com/ldionne updated https://github.com/llvm/llvm-project/pull/160182
>From a7c7ceb33df6bca2ef932fefbcb621deb45ad81a Mon Sep 17 00:00:00 2001
From: Louis Dionne <ldionne.2 at gmail.com>
Date: Mon, 22 Sep 2025 15:19:42 -0400
Subject: [PATCH 1/4] [libc++] Properly implement array cookies in the ARM ABI
When we implemented array cookie support for hardening std::unique_ptr,
the implementation was only done for the Itanium ABI. I did not initially
realize that ARM was using a different ABI for array cookies, so unique_ptr
should not have been hardened on ARM.
However, we were also incorrectly setting the ABI-detection macro: we
were pretending to be using a vanilla Itanium ABI when in reality the
(similar but different) ARM ABI was in use. As a result, unique_ptr was
using the wrong representation for array cookies on ARM, which fortunately
only mattered in the case of overaligned types.
This patch fixes that.
rdar://160852193
---
libcxx/include/__configuration/abi.h | 2 +
libcxx/include/__memory/array_cookie.h | 70 +++++++++++++++++--
.../assert.subscript.pass.cpp | 33 +++++++++
3 files changed, 100 insertions(+), 5 deletions(-)
diff --git a/libcxx/include/__configuration/abi.h b/libcxx/include/__configuration/abi.h
index 2d33b9c03090b..e3c7f830358a8 100644
--- a/libcxx/include/__configuration/abi.h
+++ b/libcxx/include/__configuration/abi.h
@@ -32,6 +32,8 @@
#else
# if defined(_WIN32) && defined(_MSC_VER)
# define _LIBCPP_ABI_MICROSOFT
+# elif defined(__arm__) || defined(__aarch64__)
+# define _LIBCPP_ABI_ARM
# else
# define _LIBCPP_ABI_ITANIUM
# endif
diff --git a/libcxx/include/__memory/array_cookie.h b/libcxx/include/__memory/array_cookie.h
index 806a9e99ecafe..8cc4c0308f1dc 100644
--- a/libcxx/include/__memory/array_cookie.h
+++ b/libcxx/include/__memory/array_cookie.h
@@ -26,12 +26,12 @@ _LIBCPP_BEGIN_NAMESPACE_STD
// Trait representing whether a type requires an array cookie at the start of its allocation when
// allocated as `new T[n]` and deallocated as `delete[] array`.
//
-// Under the Itanium C++ ABI [1], we know that an array cookie is available unless `T` is trivially
-// destructible and the call to `operator delete[]` is not a sized operator delete. Under ABIs other
-// than the Itanium ABI, we assume there are no array cookies.
+// Under the Itanium C++ ABI [1] and the ARM ABI which derives from it, we know that an array cookie is available
+// unless `T` is trivially destructible and the call to `operator delete[]` is not a sized operator delete. Under
+// other ABIs, we assume there are no array cookies.
//
// [1]: https://itanium-cxx-abi.github.io/cxx-abi/abi.html#array-cookies
-#ifdef _LIBCPP_ABI_ITANIUM
+#if defined(_LIBCPP_ABI_ITANIUM) || defined(_LIBCPP_ABI_ARM)
// TODO: Use a builtin instead
// TODO: We should factor in the choice of the usual deallocation function in this determination.
template <class _Tp>
@@ -41,13 +41,73 @@ template <class _Tp>
struct __has_array_cookie : false_type {};
#endif
+// Return the array cookie located before the given pointer.
+//
+// In the Itanium ABI
+// ------------------
+// The array cookie is stored immediately before the first element of the array. If the preferred alignment
+// of array elements (which is different from the ABI alignment) is more than that of size_t, additional
+// padding bytes exist before the array cookie. Assuming array elements of size and alignment 16 bytes, that
+// gives us the following layout:
+//
+// |ooooooooxxxxxxxxaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbccccccccccccccccdddddddddddddddd|
+// ^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+// | ^^^^^^^^ |
+// | | array elements
+// padding |
+// array cookie
+//
+// In practice, it is sufficient to read the bytes immediately before the first array element.
+//
+//
+// In the ARM ABI
+// --------------
+// The array cookie is stored at the very start of the allocation and it has the following form:
+//
+// struct array_cookie {
+// std::size_t element_size; // element_size != 0
+// std::size_t element_count;
+// };
+//
+// Assuming elements of size and alignment 32 bytes, this gives us the following layout:
+//
+// |xxxxxxxxXXXXXXXXooooooooooooooooaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb|
+// ^^^^^^^^ ^^^^^^^^^^^^^^^^
+// | ^^^^^^^^ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+// element size | padding |
+// element count array elements
+//
+// We calculate the starting address of the allocation by taking into account the ABI (not the preferred)
+// alignment of the type.
template <class _Tp>
// Avoid failures when -fsanitize-address-poison-custom-array-cookie is enabled
_LIBCPP_HIDE_FROM_ABI _LIBCPP_NO_SANITIZE("address") size_t __get_array_cookie(_Tp const* __ptr) {
static_assert(
__has_array_cookie<_Tp>::value, "Trying to access the array cookie of a type that is not guaranteed to have one");
- size_t const* __cookie = reinterpret_cast<size_t const*>(__ptr) - 1; // TODO: Use a builtin instead
+
+#if defined(_LIBCPP_ABI_ITANIUM)
+
+ size_t const* __cookie = reinterpret_cast<size_t const*>(__ptr) - 1;
return *__cookie;
+
+#elif defined(_LIBCPP_ABI_ARM)
+
+ struct _ArrayCookie {
+ size_t __element_size;
+ size_t __element_count;
+ };
+
+ size_t __cookie_size_with_padding = // max(sizeof(_ArrayCookie), alignof(T))
+ sizeof(_ArrayCookie) < alignof(_Tp) ? alignof(_Tp) : sizeof(_ArrayCookie);
+ char const* __allocation_start = reinterpret_cast<char const*>(__ptr) - __cookie_size_with_padding;
+ _ArrayCookie const* __cookie = reinterpret_cast<_ArrayCookie const*>(__allocation_start);
+ return __cookie->__element_count;
+
+#else
+
+ static_assert(sizeof(_Tp) == 0, "This function is not implemented for this ABI");
+
+#endif
}
_LIBCPP_END_NAMESPACE_STD
diff --git a/libcxx/test/std/utilities/smartptr/unique.ptr/unique.ptr.class/unique.ptr.observers/assert.subscript.pass.cpp b/libcxx/test/std/utilities/smartptr/unique.ptr/unique.ptr.class/unique.ptr.observers/assert.subscript.pass.cpp
index b7cc12350027b..2de523dfb25cb 100644
--- a/libcxx/test/std/utilities/smartptr/unique.ptr/unique.ptr.class/unique.ptr.observers/assert.subscript.pass.cpp
+++ b/libcxx/test/std/utilities/smartptr/unique.ptr/unique.ptr.class/unique.ptr.observers/assert.subscript.pass.cpp
@@ -58,15 +58,18 @@ void test() {
{
{
std::unique_ptr<WithCookie[]> ptr(new WithCookie[5]);
+ assert(&ptr[1] == ptr.get() + 1); // ensure no assertion
TEST_LIBCPP_ASSERT_FAILURE(ptr[6], "unique_ptr<T[]>::operator[](index): index out of range");
}
{
std::unique_ptr<WithCookie[]> ptr = std::make_unique<WithCookie[]>(5);
+ assert(&ptr[1] == ptr.get() + 1); // ensure no assertion
TEST_LIBCPP_ASSERT_FAILURE(ptr[6], "unique_ptr<T[]>::operator[](index): index out of range");
}
#if TEST_STD_VER >= 20
{
std::unique_ptr<WithCookie[]> ptr = std::make_unique_for_overwrite<WithCookie[]>(5);
+ assert(&ptr[1] == ptr.get() + 1); // ensure no assertion
TEST_LIBCPP_ASSERT_FAILURE(ptr[6] = WithCookie(), "unique_ptr<T[]>::operator[](index): index out of range");
}
#endif
@@ -82,11 +85,13 @@ void test() {
{
{
std::unique_ptr<NoCookie[]> ptr = std::make_unique<NoCookie[]>(5);
+ assert(&ptr[1] == ptr.get() + 1); // ensure no assertion
TEST_LIBCPP_ASSERT_FAILURE(ptr[6], "unique_ptr<T[]>::operator[](index): index out of range");
}
# if TEST_STD_VER >= 20
{
std::unique_ptr<NoCookie[]> ptr = std::make_unique_for_overwrite<NoCookie[]>(5);
+ assert(&ptr[1] == ptr.get() + 1); // ensure no assertion
TEST_LIBCPP_ASSERT_FAILURE(ptr[6] = NoCookie(), "unique_ptr<T[]>::operator[](index): index out of range");
}
# endif
@@ -101,6 +106,7 @@ void test() {
{
std::unique_ptr<T[]> ptr = std::make_unique<T[]>(5);
std::unique_ptr<T[]> other(std::move(ptr));
+ assert(&other[1] == other.get() + 1); // ensure no assertion
TEST_LIBCPP_ASSERT_FAILURE(other[6], "unique_ptr<T[]>::operator[](index): index out of range");
}
@@ -109,6 +115,7 @@ void test() {
std::unique_ptr<T[]> ptr = std::make_unique<T[]>(5);
std::unique_ptr<T[]> other;
other = std::move(ptr);
+ assert(&other[1] == other.get() + 1); // ensure no assertion
TEST_LIBCPP_ASSERT_FAILURE(other[6], "unique_ptr<T[]>::operator[](index): index out of range");
}
@@ -116,6 +123,7 @@ void test() {
{
std::unique_ptr<T[]> ptr = std::make_unique<T[]>(5);
std::unique_ptr<T[], MyDeleter> other(std::move(ptr));
+ assert(&other[1] == other.get() + 1); // ensure no assertion
TEST_LIBCPP_ASSERT_FAILURE(other[6], "unique_ptr<T[]>::operator[](index): index out of range");
}
@@ -124,6 +132,7 @@ void test() {
std::unique_ptr<T[]> ptr = std::make_unique<T[]>(5);
std::unique_ptr<T[], MyDeleter> other;
other = std::move(ptr);
+ assert(&other[1] == other.get() + 1); // ensure no assertion
TEST_LIBCPP_ASSERT_FAILURE(other[6], "unique_ptr<T[]>::operator[](index): index out of range");
}
});
@@ -144,6 +153,20 @@ struct WithCookie {
char padding[Size];
};
+template <std::size_t Size>
+struct alignas(128) OveralignedNoCookie {
+ char padding[Size];
+};
+
+template <std::size_t Size>
+struct alignas(128) OveralignedWithCookie {
+ OveralignedWithCookie() = default;
+ OveralignedWithCookie(OveralignedWithCookie const&) {}
+ OveralignedWithCookie& operator=(OveralignedWithCookie const&) { return *this; }
+ ~OveralignedWithCookie() {}
+ char padding[Size];
+};
+
int main(int, char**) {
test<WithCookie<1>, NoCookie<1>>();
test<WithCookie<2>, NoCookie<2>>();
@@ -153,6 +176,16 @@ int main(int, char**) {
test<WithCookie<16>, NoCookie<16>>();
test<WithCookie<32>, NoCookie<32>>();
test<WithCookie<256>, NoCookie<256>>();
+
+ test<OveralignedWithCookie<1>, OveralignedNoCookie<1>>();
+ test<OveralignedWithCookie<2>, OveralignedNoCookie<2>>();
+ test<OveralignedWithCookie<3>, OveralignedNoCookie<3>>();
+ test<OveralignedWithCookie<4>, OveralignedNoCookie<4>>();
+ test<OveralignedWithCookie<8>, OveralignedNoCookie<8>>();
+ test<OveralignedWithCookie<16>, OveralignedNoCookie<16>>();
+ test<OveralignedWithCookie<32>, OveralignedNoCookie<32>>();
+ test<OveralignedWithCookie<256>, OveralignedNoCookie<256>>();
+
test<std::string, int>();
return 0;
>From 11c351924330eef9e21c699ba3b3d4730a2d13fa Mon Sep 17 00:00:00 2001
From: Louis Dionne <ldionne.2 at gmail.com>
Date: Tue, 7 Oct 2025 20:52:25 -0400
Subject: [PATCH 2/4] Review comments
---
libcxx/include/__configuration/abi.h | 14 ++++++++++++--
libcxx/include/__memory/array_cookie.h | 18 +++++++++++-------
2 files changed, 23 insertions(+), 9 deletions(-)
diff --git a/libcxx/include/__configuration/abi.h b/libcxx/include/__configuration/abi.h
index e3c7f830358a8..b38a57b854b09 100644
--- a/libcxx/include/__configuration/abi.h
+++ b/libcxx/include/__configuration/abi.h
@@ -30,10 +30,20 @@
#elif _LIBCPP_ABI_FORCE_MICROSOFT
# define _LIBCPP_ABI_MICROSOFT
#else
+// Windows uses the Microsoft ABI
# if defined(_WIN32) && defined(_MSC_VER)
# define _LIBCPP_ABI_MICROSOFT
-# elif defined(__arm__) || defined(__aarch64__)
-# define _LIBCPP_ABI_ARM
+
+// 32-bit ARM uses the ARM ABI with a few oddities (array cookies, etc),
+// and so does 64-bit ARM on Apple platforms.
+# elif defined(__arm__) || (defined(__APPLE__) && defined(__aarch64__))
+# define _LIBCPP_ABI_ARM_WITH_32BIT_ODDITIES
+
+// Non-Apple 64-bit ARM uses the vanilla Itanium ABI
+# elif defined(__aarch64__)
+# define _LIBCPP_ABI_ITANIUM
+
+// We assume that other architectures also use the vanilla Itanium ABI
# else
# define _LIBCPP_ABI_ITANIUM
# endif
diff --git a/libcxx/include/__memory/array_cookie.h b/libcxx/include/__memory/array_cookie.h
index 8cc4c0308f1dc..433bf095e4f25 100644
--- a/libcxx/include/__memory/array_cookie.h
+++ b/libcxx/include/__memory/array_cookie.h
@@ -31,9 +31,10 @@ _LIBCPP_BEGIN_NAMESPACE_STD
// other ABIs, we assume there are no array cookies.
//
// [1]: https://itanium-cxx-abi.github.io/cxx-abi/abi.html#array-cookies
-#if defined(_LIBCPP_ABI_ITANIUM) || defined(_LIBCPP_ABI_ARM)
+#if defined(_LIBCPP_ABI_ITANIUM) || defined(_LIBCPP_ABI_ARM_WITH_32BIT_ODDITIES)
// TODO: Use a builtin instead
-// TODO: We should factor in the choice of the usual deallocation function in this determination.
+// TODO: We should factor in the choice of the usual deallocation function in this determination:
+// a cookie may be available in more cases but we ignore those for now.
template <class _Tp>
struct __has_array_cookie : _Not<is_trivially_destructible<_Tp> > {};
#else
@@ -43,8 +44,8 @@ struct __has_array_cookie : false_type {};
// Return the array cookie located before the given pointer.
//
-// In the Itanium ABI
-// ------------------
+// In the Itanium ABI [1]
+// ----------------------
// The array cookie is stored immediately before the first element of the array. If the preferred alignment
// of array elements (which is different from the ABI alignment) is more than that of size_t, additional
// padding bytes exist before the array cookie. Assuming array elements of size and alignment 16 bytes, that
@@ -60,8 +61,8 @@ struct __has_array_cookie : false_type {};
// In practice, it is sufficient to read the bytes immediately before the first array element.
//
//
-// In the ARM ABI
-// --------------
+// In the ARM ABI [2]
+// ------------------
// The array cookie is stored at the very start of the allocation and it has the following form:
//
// struct array_cookie {
@@ -79,6 +80,9 @@ struct __has_array_cookie : false_type {};
//
// We calculate the starting address of the allocation by taking into account the ABI (not the preferred)
// alignment of the type.
+//
+// [1]: https://itanium-cxx-abi.github.io/cxx-abi/abi.html#array-cookies
+// [2]: https://developer.apple.com/documentation/xcode/writing-arm64-code-for-apple-platforms#Handle-C++-differences
template <class _Tp>
// Avoid failures when -fsanitize-address-poison-custom-array-cookie is enabled
_LIBCPP_HIDE_FROM_ABI _LIBCPP_NO_SANITIZE("address") size_t __get_array_cookie(_Tp const* __ptr) {
@@ -90,7 +94,7 @@ _LIBCPP_HIDE_FROM_ABI _LIBCPP_NO_SANITIZE("address") size_t __get_array_cookie(_
size_t const* __cookie = reinterpret_cast<size_t const*>(__ptr) - 1;
return *__cookie;
-#elif defined(_LIBCPP_ABI_ARM)
+#elif defined(_LIBCPP_ABI_ARM_WITH_32BIT_ODDITIES)
struct _ArrayCookie {
size_t __element_size;
>From 68627d8997f2db92062601121dc2d9b5db0861a5 Mon Sep 17 00:00:00 2001
From: Louis Dionne <ldionne.2 at gmail.com>
Date: Wed, 8 Oct 2025 08:17:51 -0400
Subject: [PATCH 3/4] Fix C++03
---
libcxx/include/__memory/array_cookie.h | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/libcxx/include/__memory/array_cookie.h b/libcxx/include/__memory/array_cookie.h
index 433bf095e4f25..afcde88b1047d 100644
--- a/libcxx/include/__memory/array_cookie.h
+++ b/libcxx/include/__memory/array_cookie.h
@@ -102,7 +102,7 @@ _LIBCPP_HIDE_FROM_ABI _LIBCPP_NO_SANITIZE("address") size_t __get_array_cookie(_
};
size_t __cookie_size_with_padding = // max(sizeof(_ArrayCookie), alignof(T))
- sizeof(_ArrayCookie) < alignof(_Tp) ? alignof(_Tp) : sizeof(_ArrayCookie);
+ sizeof(_ArrayCookie) < _LIBCPP_ALIGNOF(_Tp) ? _LIBCPP_ALIGNOF(_Tp) : sizeof(_ArrayCookie);
char const* __allocation_start = reinterpret_cast<char const*>(__ptr) - __cookie_size_with_padding;
_ArrayCookie const* __cookie = reinterpret_cast<_ArrayCookie const*>(__allocation_start);
return __cookie->__element_count;
>From 23177974e7282ba4c6256d67b8a3d07269f0ca9b Mon Sep 17 00:00:00 2001
From: Louis Dionne <ldionne.2 at gmail.com>
Date: Wed, 8 Oct 2025 08:20:10 -0400
Subject: [PATCH 4/4] Review comments
---
libcxx/include/__configuration/abi.h | 6 +++---
libcxx/include/__memory/array_cookie.h | 6 +++---
2 files changed, 6 insertions(+), 6 deletions(-)
diff --git a/libcxx/include/__configuration/abi.h b/libcxx/include/__configuration/abi.h
index b38a57b854b09..c9936df30ff7f 100644
--- a/libcxx/include/__configuration/abi.h
+++ b/libcxx/include/__configuration/abi.h
@@ -34,16 +34,16 @@
# if defined(_WIN32) && defined(_MSC_VER)
# define _LIBCPP_ABI_MICROSOFT
-// 32-bit ARM uses the ARM ABI with a few oddities (array cookies, etc),
+// 32-bit ARM uses the Itanium ABI with a few differences (array cookies, etc),
// and so does 64-bit ARM on Apple platforms.
# elif defined(__arm__) || (defined(__APPLE__) && defined(__aarch64__))
-# define _LIBCPP_ABI_ARM_WITH_32BIT_ODDITIES
+# define _LIBCPP_ABI_ITANIUM_WITH_ARM_DIFFERENCES
// Non-Apple 64-bit ARM uses the vanilla Itanium ABI
# elif defined(__aarch64__)
# define _LIBCPP_ABI_ITANIUM
-// We assume that other architectures also use the vanilla Itanium ABI
+// We assume that other architectures also use the vanilla Itanium ABI too
# else
# define _LIBCPP_ABI_ITANIUM
# endif
diff --git a/libcxx/include/__memory/array_cookie.h b/libcxx/include/__memory/array_cookie.h
index afcde88b1047d..4cbbf2f99ae08 100644
--- a/libcxx/include/__memory/array_cookie.h
+++ b/libcxx/include/__memory/array_cookie.h
@@ -31,7 +31,7 @@ _LIBCPP_BEGIN_NAMESPACE_STD
// other ABIs, we assume there are no array cookies.
//
// [1]: https://itanium-cxx-abi.github.io/cxx-abi/abi.html#array-cookies
-#if defined(_LIBCPP_ABI_ITANIUM) || defined(_LIBCPP_ABI_ARM_WITH_32BIT_ODDITIES)
+#if defined(_LIBCPP_ABI_ITANIUM) || defined(_LIBCPP_ABI_ITANIUM_WITH_ARM_DIFFERENCES)
// TODO: Use a builtin instead
// TODO: We should factor in the choice of the usual deallocation function in this determination:
// a cookie may be available in more cases but we ignore those for now.
@@ -85,7 +85,7 @@ struct __has_array_cookie : false_type {};
// [2]: https://developer.apple.com/documentation/xcode/writing-arm64-code-for-apple-platforms#Handle-C++-differences
template <class _Tp>
// Avoid failures when -fsanitize-address-poison-custom-array-cookie is enabled
-_LIBCPP_HIDE_FROM_ABI _LIBCPP_NO_SANITIZE("address") size_t __get_array_cookie(_Tp const* __ptr) {
+_LIBCPP_HIDE_FROM_ABI _LIBCPP_NO_SANITIZE("address") size_t __get_array_cookie([[maybe_unused]] _Tp const* __ptr) {
static_assert(
__has_array_cookie<_Tp>::value, "Trying to access the array cookie of a type that is not guaranteed to have one");
@@ -94,7 +94,7 @@ _LIBCPP_HIDE_FROM_ABI _LIBCPP_NO_SANITIZE("address") size_t __get_array_cookie(_
size_t const* __cookie = reinterpret_cast<size_t const*>(__ptr) - 1;
return *__cookie;
-#elif defined(_LIBCPP_ABI_ARM_WITH_32BIT_ODDITIES)
+#elif defined(_LIBCPP_ABI_ITANIUM_WITH_ARM_DIFFERENCES)
struct _ArrayCookie {
size_t __element_size;
More information about the libcxx-commits
mailing list