[libcxx-commits] [libcxx] [libc++] Add an ABI setting to harden unique_ptr<T[]>::operator[] (PR #91798)
Louis Dionne via libcxx-commits
libcxx-commits at lists.llvm.org
Wed Oct 9 08:48:49 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) {}
----------------
ldionne wrote:
`SIZE_MAX` is supposed to be defined in `<cstdint>`, can you check why your platform doesn't provide it?
https://github.com/llvm/llvm-project/pull/91798
More information about the libcxx-commits
mailing list