[libcxx-commits] [libcxx] [libc++] Add an ABI setting to harden unique_ptr<T[]>::operator[] (PR #91798)
Martin Storsjö via libcxx-commits
libcxx-commits at lists.llvm.org
Thu Oct 10 04:19:42 PDT 2024
================
@@ -292,6 +298,91 @@ class _LIBCPP_UNIQUE_PTR_TRIVIAL_ABI _LIBCPP_TEMPLATE_VIS unique_ptr {
}
};
+// Bounds checking in unique_ptr<T[]>
+// ==================================
+//
+// We provide some helper classes that allow bounds checking when accessing a unique_ptr<T[]>.
+// There are a few cases where bounds checking can be implemented:
+//
+// 1. When an array cookie (see [1]) exists at the beginning of the array allocation, we are
+// able to reuse that cookie to extract the size of the array and perform bounds checking.
+// An array cookie is a size inserted at the beginning of the allocation by the compiler.
+// That size is inserted implicitly when doing `new T[n]` in some cases, and its purpose
+// is to allow the runtime to destroy the `n` array elements when doing `delete array`.
+// When we are able to use array cookies, we reuse information already available in the
+// current runtime, so bounds checking does not require changing libc++'s ABI.
+//
+// 2. When the "bounded unique_ptr" ABI configuration (controlled by `_LIBCPP_ABI_BOUNDED_UNIQUE_PTR`)
+// is enabled, we store the size of the allocation (when it is known) so we can check it when
+// indexing into the `unique_ptr`. That changes the layout of `std::unique_ptr<T[]>`, which is
+// an ABI break from the default configuration.
+//
+// Note that even under this ABI configuration, we can't always know the size of the unique_ptr.
+// Indeed, the size of the allocation can only be known when the unique_ptr is created via
+// make_unique or a similar API. For example, it can't be known when constructed from an arbitrary
+// pointer, in which case we are not able to check the bounds on access:
+//
+// unique_ptr<T[], MyDeleter> ptr(new T[3]);
+//
+// When we don't know the size of the allocation via the API used to create the unique_ptr, we
+// try to fall back to using an array cookie when available.
+//
+// Finally, note that when this ABI configuration is enabled, we have no choice but to always
+// make space for a size to be stored in the unique_ptr. Indeed, while we might want to avoid
+// storing the size when an array cookie is available, knowing whether an array cookie is available
+// requires the type stored in the unique_ptr to be complete, while unique_ptr can normally
+// accommodate incomplete types.
+//
+// (1) Implementation where we rely on the array cookie to know the size of the allocation, if
+// an array cookie exists.
+struct __unique_ptr_array_bounds_stateless {
+ __unique_ptr_array_bounds_stateless() = default;
+ _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR explicit __unique_ptr_array_bounds_stateless(size_t) {}
+
+ template <class _Tp, __enable_if_t<__has_array_cookie<_Tp>::value, int> = 0>
+ _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR bool __in_bounds(_Tp* __ptr, size_t __index) const {
+ // In constant expressions, we can't check the array cookie so we just pretend that the index
+ // is in-bounds. The compiler catches invalid accesses anyway.
+ if (__libcpp_is_constant_evaluated())
+ return true;
+ size_t __cookie = std::__get_array_cookie(__ptr);
+ return __index < __cookie;
+ }
+
+ template <class _Tp, __enable_if_t<!__has_array_cookie<_Tp>::value, int> = 0>
+ _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR bool __in_bounds(_Tp*, size_t) const {
+ return true; // If we don't have an array cookie, we assume the access is in-bounds
+ }
+};
+
+// (2) Implementation where we store the size in the class whenever we have it.
+//
+// Semantically, we'd need to store the size as an optional<size_t>. However, since that
+// is really heavy weight, we instead store a size_t and use SIZE_MAX as a magic value
+// meaning that we don't know the size.
+struct __unique_ptr_array_bounds_stored {
+ _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR __unique_ptr_array_bounds_stored() : __size_(SIZE_MAX) {}
+ _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR explicit __unique_ptr_array_bounds_stored(size_t __size) : __size_(__size) {}
----------------
mstorsjo wrote:
It is provided by `<cstdint>` just fine. The user code essentially reduces down to this:
```c
#include <limits.h>
#include <stdint.h>
#undef SIZE_MAX
#include <string>
```
Which makes it more clear that this is the fault of user code.
That said, I guess it wouldn't hurt if libcxx would use `__SIZE_MAX__` either, if that's a kind define that libcxx can rely on being available (I guess both GCC and Clang provide such defines), which would be more resilient to user macro redefinitions. (I'm not aware of the original reason for why the user code wants to do `#undef SIZE_MAX`.)
https://github.com/llvm/llvm-project/pull/91798
More information about the libcxx-commits
mailing list