Skip to content

Commit c5a3aa8

Browse files
ldionnec-rhodes
authored andcommitted
[libc++] Properly implement array cookies in the ARM ABI (llvm#160182)
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 (cherry picked from commit b3a1994)
1 parent 0d819a9 commit c5a3aa8

File tree

3 files changed

+136
-8
lines changed

3 files changed

+136
-8
lines changed

libcxx/include/__configuration/abi.h

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,20 @@
3030
#elif _LIBCPP_ABI_FORCE_MICROSOFT
3131
# define _LIBCPP_ABI_MICROSOFT
3232
#else
33+
// Windows uses the Microsoft ABI
3334
# if defined(_WIN32) && defined(_MSC_VER)
3435
# define _LIBCPP_ABI_MICROSOFT
36+
37+
// 32-bit ARM uses the Itanium ABI with a few differences (array cookies, etc),
38+
// and so does 64-bit ARM on Apple platforms.
39+
# elif defined(__arm__) || (defined(__APPLE__) && defined(__aarch64__))
40+
# define _LIBCPP_ABI_ITANIUM_WITH_ARM_DIFFERENCES
41+
42+
// Non-Apple 64-bit ARM uses the vanilla Itanium ABI
43+
# elif defined(__aarch64__)
44+
# define _LIBCPP_ABI_ITANIUM
45+
46+
// We assume that other architectures also use the vanilla Itanium ABI too
3547
# else
3648
# define _LIBCPP_ABI_ITANIUM
3749
# endif

libcxx/include/__memory/array_cookie.h

Lines changed: 76 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
#include <__config>
1414
#include <__configuration/abi.h>
1515
#include <__cstddef/size_t.h>
16+
#include <__memory/addressof.h>
1617
#include <__type_traits/integral_constant.h>
1718
#include <__type_traits/is_trivially_destructible.h>
1819
#include <__type_traits/negation.h>
@@ -26,28 +27,95 @@ _LIBCPP_BEGIN_NAMESPACE_STD
2627
// Trait representing whether a type requires an array cookie at the start of its allocation when
2728
// allocated as `new T[n]` and deallocated as `delete[] array`.
2829
//
29-
// Under the Itanium C++ ABI [1], we know that an array cookie is available unless `T` is trivially
30-
// destructible and the call to `operator delete[]` is not a sized operator delete. Under ABIs other
31-
// than the Itanium ABI, we assume there are no array cookies.
30+
// Under the Itanium C++ ABI [1] and the ARM ABI which derives from it, we know that an array cookie is available
31+
// unless `T` is trivially destructible and the call to `operator delete[]` is not a sized operator delete. Under
32+
// other ABIs, we assume there are no array cookies.
3233
//
3334
// [1]: https://itanium-cxx-abi.github.io/cxx-abi/abi.html#array-cookies
34-
#ifdef _LIBCPP_ABI_ITANIUM
35+
#if defined(_LIBCPP_ABI_ITANIUM) || defined(_LIBCPP_ABI_ITANIUM_WITH_ARM_DIFFERENCES)
3536
// TODO: Use a builtin instead
36-
// TODO: We should factor in the choice of the usual deallocation function in this determination.
37+
// TODO: We should factor in the choice of the usual deallocation function in this determination:
38+
// a cookie may be available in more cases but we ignore those for now.
3739
template <class _Tp>
3840
struct __has_array_cookie : _Not<is_trivially_destructible<_Tp> > {};
3941
#else
4042
template <class _Tp>
4143
struct __has_array_cookie : false_type {};
4244
#endif
4345

46+
struct __itanium_array_cookie {
47+
size_t __element_count;
48+
};
49+
50+
template <class _Tp>
51+
struct [[__gnu__::__aligned__(_LIBCPP_ALIGNOF(_Tp))]] __arm_array_cookie {
52+
size_t __element_size;
53+
size_t __element_count;
54+
};
55+
56+
// Return the element count in the array cookie located before the given pointer.
57+
//
58+
// In the Itanium ABI [1]
59+
// ----------------------
60+
// The element count is stored immediately before the first element of the array. If the preferred alignment
61+
// of array elements (which is different from the ABI alignment) is more than that of size_t, additional
62+
// padding bytes exist before the array cookie. Assuming array elements of size and alignment 16 bytes, that
63+
// gives us the following layout:
64+
//
65+
// |ooooooooxxxxxxxxaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbccccccccccccccccdddddddddddddddd|
66+
// ^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
67+
// | ^^^^^^^^ |
68+
// | | array elements
69+
// padding |
70+
// element count
71+
//
72+
//
73+
// In the Itanium ABI with ARM differences [2]
74+
// -------------------------------------------
75+
// The array cookie is stored at the very start of the allocation and it has the following form:
76+
//
77+
// struct array_cookie {
78+
// std::size_t element_size; // element_size != 0
79+
// std::size_t element_count;
80+
// };
81+
//
82+
// Assuming elements of size and alignment 32 bytes, this gives us the following layout:
83+
//
84+
// |xxxxxxxxXXXXXXXXooooooooooooooooaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb|
85+
// ^^^^^^^^ ^^^^^^^^^^^^^^^^
86+
// | ^^^^^^^^ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
87+
// element size | padding |
88+
// element count array elements
89+
//
90+
// We must be careful to take into account the alignment of the array cookie, which may result in padding
91+
// bytes between the element count and the first element of the array. Note that for ARM, the compiler
92+
// aligns the array cookie using the ABI alignment, not the preferred alignment of array elements.
93+
//
94+
// [1]: https://itanium-cxx-abi.github.io/cxx-abi/abi.html#array-cookies
95+
// [2]: https://developer.apple.com/documentation/xcode/writing-arm64-code-for-apple-platforms#Handle-C++-differences
4496
template <class _Tp>
4597
// Avoid failures when -fsanitize-address-poison-custom-array-cookie is enabled
46-
_LIBCPP_HIDE_FROM_ABI _LIBCPP_NO_SANITIZE("address") size_t __get_array_cookie(_Tp const* __ptr) {
98+
_LIBCPP_HIDE_FROM_ABI _LIBCPP_NO_SANITIZE("address") size_t __get_array_cookie([[__maybe_unused__]] _Tp const* __ptr) {
4799
static_assert(
48100
__has_array_cookie<_Tp>::value, "Trying to access the array cookie of a type that is not guaranteed to have one");
49-
size_t const* __cookie = reinterpret_cast<size_t const*>(__ptr) - 1; // TODO: Use a builtin instead
50-
return *__cookie;
101+
102+
#if defined(_LIBCPP_ABI_ITANIUM)
103+
using _ArrayCookie = __itanium_array_cookie;
104+
#elif defined(_LIBCPP_ABI_ITANIUM_WITH_ARM_DIFFERENCES)
105+
using _ArrayCookie = __arm_array_cookie<_Tp>;
106+
#else
107+
static_assert(false, "The array cookie layout is unknown on this ABI");
108+
struct _ArrayCookie { // dummy definition required to make the function parse
109+
size_t element_count;
110+
};
111+
#endif
112+
113+
char const* __array_cookie_start = reinterpret_cast<char const*>(__ptr) - sizeof(_ArrayCookie);
114+
_ArrayCookie __cookie;
115+
// This is necessary to avoid violating strict aliasing. It's valid because _ArrayCookie is an
116+
// implicit lifetime type.
117+
__builtin_memcpy(std::addressof(__cookie), __array_cookie_start, sizeof(_ArrayCookie));
118+
return __cookie.__element_count;
51119
}
52120

53121
_LIBCPP_END_NAMESPACE_STD

libcxx/test/std/utilities/smartptr/unique.ptr/unique.ptr.class/unique.ptr.observers/assert.subscript.pass.cpp

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,15 +58,18 @@ void test() {
5858
{
5959
{
6060
std::unique_ptr<WithCookie[]> ptr(new WithCookie[5]);
61+
assert(&ptr[1] == ptr.get() + 1); // ensure no assertion
6162
TEST_LIBCPP_ASSERT_FAILURE(ptr[6], "unique_ptr<T[]>::operator[](index): index out of range");
6263
}
6364
{
6465
std::unique_ptr<WithCookie[]> ptr = std::make_unique<WithCookie[]>(5);
66+
assert(&ptr[1] == ptr.get() + 1); // ensure no assertion
6567
TEST_LIBCPP_ASSERT_FAILURE(ptr[6], "unique_ptr<T[]>::operator[](index): index out of range");
6668
}
6769
#if TEST_STD_VER >= 20
6870
{
6971
std::unique_ptr<WithCookie[]> ptr = std::make_unique_for_overwrite<WithCookie[]>(5);
72+
assert(&ptr[1] == ptr.get() + 1); // ensure no assertion
7073
TEST_LIBCPP_ASSERT_FAILURE(ptr[6] = WithCookie(), "unique_ptr<T[]>::operator[](index): index out of range");
7174
}
7275
#endif
@@ -82,11 +85,13 @@ void test() {
8285
{
8386
{
8487
std::unique_ptr<NoCookie[]> ptr = std::make_unique<NoCookie[]>(5);
88+
assert(&ptr[1] == ptr.get() + 1); // ensure no assertion
8589
TEST_LIBCPP_ASSERT_FAILURE(ptr[6], "unique_ptr<T[]>::operator[](index): index out of range");
8690
}
8791
# if TEST_STD_VER >= 20
8892
{
8993
std::unique_ptr<NoCookie[]> ptr = std::make_unique_for_overwrite<NoCookie[]>(5);
94+
assert(&ptr[1] == ptr.get() + 1); // ensure no assertion
9095
TEST_LIBCPP_ASSERT_FAILURE(ptr[6] = NoCookie(), "unique_ptr<T[]>::operator[](index): index out of range");
9196
}
9297
# endif
@@ -101,6 +106,7 @@ void test() {
101106
{
102107
std::unique_ptr<T[]> ptr = std::make_unique<T[]>(5);
103108
std::unique_ptr<T[]> other(std::move(ptr));
109+
assert(&other[1] == other.get() + 1); // ensure no assertion
104110
TEST_LIBCPP_ASSERT_FAILURE(other[6], "unique_ptr<T[]>::operator[](index): index out of range");
105111
}
106112

@@ -109,13 +115,15 @@ void test() {
109115
std::unique_ptr<T[]> ptr = std::make_unique<T[]>(5);
110116
std::unique_ptr<T[]> other;
111117
other = std::move(ptr);
118+
assert(&other[1] == other.get() + 1); // ensure no assertion
112119
TEST_LIBCPP_ASSERT_FAILURE(other[6], "unique_ptr<T[]>::operator[](index): index out of range");
113120
}
114121

115122
// Bounds carried through converting move-constructor
116123
{
117124
std::unique_ptr<T[]> ptr = std::make_unique<T[]>(5);
118125
std::unique_ptr<T[], MyDeleter> other(std::move(ptr));
126+
assert(&other[1] == other.get() + 1); // ensure no assertion
119127
TEST_LIBCPP_ASSERT_FAILURE(other[6], "unique_ptr<T[]>::operator[](index): index out of range");
120128
}
121129

@@ -124,6 +132,7 @@ void test() {
124132
std::unique_ptr<T[]> ptr = std::make_unique<T[]>(5);
125133
std::unique_ptr<T[], MyDeleter> other;
126134
other = std::move(ptr);
135+
assert(&other[1] == other.get() + 1); // ensure no assertion
127136
TEST_LIBCPP_ASSERT_FAILURE(other[6], "unique_ptr<T[]>::operator[](index): index out of range");
128137
}
129138
});
@@ -144,6 +153,34 @@ struct WithCookie {
144153
char padding[Size];
145154
};
146155

156+
template <std::size_t Size>
157+
struct alignas(128) OveralignedNoCookie {
158+
char padding[Size];
159+
};
160+
161+
template <std::size_t Size>
162+
struct alignas(128) OveralignedWithCookie {
163+
OveralignedWithCookie() = default;
164+
OveralignedWithCookie(OveralignedWithCookie const&) {}
165+
OveralignedWithCookie& operator=(OveralignedWithCookie const&) { return *this; }
166+
~OveralignedWithCookie() {}
167+
char padding[Size];
168+
};
169+
170+
// These types have a different ABI alignment (alignof) and preferred alignment (__alignof) on some platforms.
171+
// Make sure things work with these types because array cookies can be sensitive to preferred alignment on some
172+
// platforms.
173+
struct WithCookiePreferredAlignment {
174+
WithCookiePreferredAlignment() = default;
175+
WithCookiePreferredAlignment(WithCookiePreferredAlignment const&) {}
176+
WithCookiePreferredAlignment& operator=(WithCookiePreferredAlignment const&) { return *this; }
177+
~WithCookiePreferredAlignment() {}
178+
long double data;
179+
};
180+
struct NoCookiePreferredAlignment {
181+
long double data;
182+
};
183+
147184
int main(int, char**) {
148185
test<WithCookie<1>, NoCookie<1>>();
149186
test<WithCookie<2>, NoCookie<2>>();
@@ -153,7 +190,18 @@ int main(int, char**) {
153190
test<WithCookie<16>, NoCookie<16>>();
154191
test<WithCookie<32>, NoCookie<32>>();
155192
test<WithCookie<256>, NoCookie<256>>();
193+
194+
test<OveralignedWithCookie<1>, OveralignedNoCookie<1>>();
195+
test<OveralignedWithCookie<2>, OveralignedNoCookie<2>>();
196+
test<OveralignedWithCookie<3>, OveralignedNoCookie<3>>();
197+
test<OveralignedWithCookie<4>, OveralignedNoCookie<4>>();
198+
test<OveralignedWithCookie<8>, OveralignedNoCookie<8>>();
199+
test<OveralignedWithCookie<16>, OveralignedNoCookie<16>>();
200+
test<OveralignedWithCookie<32>, OveralignedNoCookie<32>>();
201+
test<OveralignedWithCookie<256>, OveralignedNoCookie<256>>();
202+
156203
test<std::string, int>();
204+
test<WithCookiePreferredAlignment, NoCookiePreferredAlignment>();
157205

158206
return 0;
159207
}

0 commit comments

Comments
 (0)