[libc++] Add an ABI setting to harden unique_ptr<T[]>::operator[] (#91798)

This allows catching OOB accesses inside `unique_ptr<T[]>` when the size
of the allocation is known. The size of the allocation can be known when
the unique_ptr has been created with make_unique & friends or when the
type necessitates an array cookie before the allocation.

This is a re-aplpication of 45a09d181 which had been reverted in
f11abac6 due to unrelated CI failures.
This commit is contained in:
Louis Dionne 2024-09-27 08:49:22 -04:00
parent 8e6bba230e
commit 18df9d23ea
13 changed files with 647 additions and 60 deletions

View File

@ -1,2 +1,2 @@
set(LIBCXX_HARDENING_MODE "fast" CACHE STRING "") set(LIBCXX_HARDENING_MODE "fast" CACHE STRING "")
set(LIBCXX_ABI_DEFINES "_LIBCPP_ABI_BOUNDED_ITERATORS;_LIBCPP_ABI_BOUNDED_ITERATORS_IN_STRING;_LIBCPP_ABI_BOUNDED_ITERATORS_IN_VECTOR" CACHE STRING "") set(LIBCXX_ABI_DEFINES "_LIBCPP_ABI_BOUNDED_ITERATORS;_LIBCPP_ABI_BOUNDED_ITERATORS_IN_STRING;_LIBCPP_ABI_BOUNDED_ITERATORS_IN_VECTOR;_LIBCPP_ABI_BOUNDED_UNIQUE_PTR" CACHE STRING "")

View File

@ -536,6 +536,7 @@ set(files
__memory/allocator_arg_t.h __memory/allocator_arg_t.h
__memory/allocator_destructor.h __memory/allocator_destructor.h
__memory/allocator_traits.h __memory/allocator_traits.h
__memory/array_cookie.h
__memory/assume_aligned.h __memory/assume_aligned.h
__memory/auto_ptr.h __memory/auto_ptr.h
__memory/builtin_new_allocator.h __memory/builtin_new_allocator.h

View File

@ -181,6 +181,13 @@
# define _LIBCPP_ABI_NO_COMPRESSED_PAIR_PADDING # define _LIBCPP_ABI_NO_COMPRESSED_PAIR_PADDING
#endif #endif
// Tracks the bounds of the array owned by std::unique_ptr<T[]>, allowing it to trap when accessed out-of-bounds.
// Note that limited bounds checking is also available outside of this ABI configuration, but only some categories
// of types can be checked.
//
// ABI impact: This causes the layout of std::unique_ptr<T[]> to change and its size to increase.
// #define _LIBCPP_ABI_BOUNDED_UNIQUE_PTR
#if defined(_LIBCPP_COMPILER_CLANG_BASED) #if defined(_LIBCPP_COMPILER_CLANG_BASED)
# if defined(__APPLE__) # if defined(__APPLE__)
# if defined(__i386__) || defined(__x86_64__) # if defined(__i386__) || defined(__x86_64__)

View File

@ -0,0 +1,55 @@
// -*- C++ -*-
//===----------------------------------------------------------------------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
#ifndef _LIBCPP___MEMORY_ARRAY_COOKIE_H
#define _LIBCPP___MEMORY_ARRAY_COOKIE_H
#include <__config>
#include <__configuration/abi.h>
#include <__type_traits/integral_constant.h>
#include <__type_traits/is_trivially_destructible.h>
#include <__type_traits/negation.h>
#include <cstddef>
#if !defined(_LIBCPP_HAS_NO_PRAGMA_SYSTEM_HEADER)
# pragma GCC system_header
#endif
_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.
//
// [1]: https://itanium-cxx-abi.github.io/cxx-abi/abi.html#array-cookies
#ifdef _LIBCPP_ABI_ITANIUM
// TODO: Use a builtin instead
// TODO: We should factor in the choice of the usual deallocation function in this determination.
template <class _Tp>
struct __has_array_cookie : _Not<is_trivially_destructible<_Tp> > {};
#else
template <class _Tp>
struct __has_array_cookie : false_type {};
#endif
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
return *__cookie;
}
_LIBCPP_END_NAMESPACE_STD
#endif // _LIBCPP___MEMORY_ARRAY_COOKIE_H

View File

@ -10,6 +10,7 @@
#ifndef _LIBCPP___MEMORY_UNIQUE_PTR_H #ifndef _LIBCPP___MEMORY_UNIQUE_PTR_H
#define _LIBCPP___MEMORY_UNIQUE_PTR_H #define _LIBCPP___MEMORY_UNIQUE_PTR_H
#include <__assert>
#include <__compare/compare_three_way.h> #include <__compare/compare_three_way.h>
#include <__compare/compare_three_way_result.h> #include <__compare/compare_three_way_result.h>
#include <__compare/three_way_comparable.h> #include <__compare/three_way_comparable.h>
@ -17,8 +18,10 @@
#include <__functional/hash.h> #include <__functional/hash.h>
#include <__functional/operations.h> #include <__functional/operations.h>
#include <__memory/allocator_traits.h> // __pointer #include <__memory/allocator_traits.h> // __pointer
#include <__memory/array_cookie.h>
#include <__memory/auto_ptr.h> #include <__memory/auto_ptr.h>
#include <__memory/compressed_pair.h> #include <__memory/compressed_pair.h>
#include <__memory/pointer_traits.h>
#include <__type_traits/add_lvalue_reference.h> #include <__type_traits/add_lvalue_reference.h>
#include <__type_traits/common_type.h> #include <__type_traits/common_type.h>
#include <__type_traits/conditional.h> #include <__type_traits/conditional.h>
@ -27,6 +30,7 @@
#include <__type_traits/integral_constant.h> #include <__type_traits/integral_constant.h>
#include <__type_traits/is_array.h> #include <__type_traits/is_array.h>
#include <__type_traits/is_assignable.h> #include <__type_traits/is_assignable.h>
#include <__type_traits/is_constant_evaluated.h>
#include <__type_traits/is_constructible.h> #include <__type_traits/is_constructible.h>
#include <__type_traits/is_convertible.h> #include <__type_traits/is_convertible.h>
#include <__type_traits/is_function.h> #include <__type_traits/is_function.h>
@ -41,7 +45,9 @@
#include <__utility/declval.h> #include <__utility/declval.h>
#include <__utility/forward.h> #include <__utility/forward.h>
#include <__utility/move.h> #include <__utility/move.h>
#include <__utility/private_constructor_tag.h>
#include <cstddef> #include <cstddef>
#include <cstdint>
#if !defined(_LIBCPP_HAS_NO_PRAGMA_SYSTEM_HEADER) #if !defined(_LIBCPP_HAS_NO_PRAGMA_SYSTEM_HEADER)
# pragma GCC system_header # pragma GCC system_header
@ -292,6 +298,91 @@ public:
} }
}; };
// 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) {}
// Use the array cookie if there's one
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 {
if (__libcpp_is_constant_evaluated())
return true;
size_t __cookie = std::__get_array_cookie(__ptr);
return __index < __cookie;
}
// Otherwise, fall back on the stored size (if any)
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 __index) const {
return __index < __size_;
}
private:
size_t __size_;
};
template <class _Tp, class _Dp> template <class _Tp, class _Dp>
class _LIBCPP_UNIQUE_PTR_TRIVIAL_ABI _LIBCPP_TEMPLATE_VIS unique_ptr<_Tp[], _Dp> { class _LIBCPP_UNIQUE_PTR_TRIVIAL_ABI _LIBCPP_TEMPLATE_VIS unique_ptr<_Tp[], _Dp> {
public: public:
@ -300,8 +391,9 @@ public:
typedef typename __pointer<_Tp, deleter_type>::type pointer; typedef typename __pointer<_Tp, deleter_type>::type pointer;
// A unique_ptr contains the following members which may be trivially relocatable: // A unique_ptr contains the following members which may be trivially relocatable:
// - pointer : this may be trivially relocatable, so it's checked // - pointer: this may be trivially relocatable, so it's checked
// - deleter_type: this may be trivially relocatable, so it's checked // - deleter_type: this may be trivially relocatable, so it's checked
// - (optionally) size: this is trivially relocatable
// //
// This unique_ptr implementation only contains a pointer to the unique object and a deleter, so there are no // This unique_ptr implementation only contains a pointer to the unique object and a deleter, so there are no
// references to itself. This means that the entire structure is trivially relocatable if its members are. // references to itself. This means that the entire structure is trivially relocatable if its members are.
@ -311,7 +403,16 @@ public:
void>; void>;
private: private:
template <class _Up, class _OtherDeleter>
friend class unique_ptr;
_LIBCPP_COMPRESSED_PAIR(pointer, __ptr_, deleter_type, __deleter_); _LIBCPP_COMPRESSED_PAIR(pointer, __ptr_, deleter_type, __deleter_);
#ifdef _LIBCPP_ABI_BOUNDED_UNIQUE_PTR
using _BoundsChecker = __unique_ptr_array_bounds_stored;
#else
using _BoundsChecker = __unique_ptr_array_bounds_stateless;
#endif
_LIBCPP_NO_UNIQUE_ADDRESS _BoundsChecker __checker_;
template <class _From> template <class _From>
struct _CheckArrayPointerConversion : is_same<_From, pointer> {}; struct _CheckArrayPointerConversion : is_same<_From, pointer> {};
@ -373,6 +474,12 @@ public:
: __ptr_(__p), : __ptr_(__p),
__deleter_() {} __deleter_() {}
// Private constructor used by make_unique & friends to pass the size that was allocated
template <class _Tag, class _Ptr, __enable_if_t<is_same<_Tag, __private_constructor_tag>::value, int> = 0>
_LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX23 explicit unique_ptr(_Tag, _Ptr __ptr, size_t __size) _NOEXCEPT
: __ptr_(__ptr),
__checker_(__size) {}
template <class _Pp, template <class _Pp,
bool _Dummy = true, bool _Dummy = true,
class = _EnableIfDeleterConstructible<_LValRefType<_Dummy> >, class = _EnableIfDeleterConstructible<_LValRefType<_Dummy> >,
@ -411,11 +518,13 @@ public:
_LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX23 unique_ptr(unique_ptr&& __u) _NOEXCEPT _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX23 unique_ptr(unique_ptr&& __u) _NOEXCEPT
: __ptr_(__u.release()), : __ptr_(__u.release()),
__deleter_(std::forward<deleter_type>(__u.get_deleter())) {} __deleter_(std::forward<deleter_type>(__u.get_deleter())),
__checker_(std::move(__u.__checker_)) {}
_LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX23 unique_ptr& operator=(unique_ptr&& __u) _NOEXCEPT { _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX23 unique_ptr& operator=(unique_ptr&& __u) _NOEXCEPT {
reset(__u.release()); reset(__u.release());
__deleter_ = std::forward<deleter_type>(__u.get_deleter()); __deleter_ = std::forward<deleter_type>(__u.get_deleter());
__checker_ = std::move(std::move(__u.__checker_));
return *this; return *this;
} }
@ -425,7 +534,8 @@ public:
class = _EnableIfDeleterConvertible<_Ep> > class = _EnableIfDeleterConvertible<_Ep> >
_LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX23 unique_ptr(unique_ptr<_Up, _Ep>&& __u) _NOEXCEPT _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX23 unique_ptr(unique_ptr<_Up, _Ep>&& __u) _NOEXCEPT
: __ptr_(__u.release()), : __ptr_(__u.release()),
__deleter_(std::forward<_Ep>(__u.get_deleter())) {} __deleter_(std::forward<_Ep>(__u.get_deleter())),
__checker_(std::move(__u.__checker_)) {}
template <class _Up, template <class _Up,
class _Ep, class _Ep,
@ -434,6 +544,7 @@ public:
_LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX23 unique_ptr& operator=(unique_ptr<_Up, _Ep>&& __u) _NOEXCEPT { _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX23 unique_ptr& operator=(unique_ptr<_Up, _Ep>&& __u) _NOEXCEPT {
reset(__u.release()); reset(__u.release());
__deleter_ = std::forward<_Ep>(__u.get_deleter()); __deleter_ = std::forward<_Ep>(__u.get_deleter());
__checker_ = std::move(__u.__checker_);
return *this; return *this;
} }
@ -451,6 +562,8 @@ public:
} }
_LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX23 __add_lvalue_reference_t<_Tp> operator[](size_t __i) const { _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX23 __add_lvalue_reference_t<_Tp> operator[](size_t __i) const {
_LIBCPP_ASSERT_VALID_ELEMENT_ACCESS(__checker_.__in_bounds(std::__to_address(__ptr_), __i),
"unique_ptr<T[]>::operator[](index): index out of range");
return __ptr_[__i]; return __ptr_[__i];
} }
_LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX23 pointer get() const _NOEXCEPT { return __ptr_; } _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX23 pointer get() const _NOEXCEPT { return __ptr_; }
@ -467,6 +580,8 @@ public:
_LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX23 pointer release() _NOEXCEPT { _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX23 pointer release() _NOEXCEPT {
pointer __t = __ptr_; pointer __t = __ptr_;
__ptr_ = pointer(); __ptr_ = pointer();
// The deleter and the optional bounds-checker are left unchanged. The bounds-checker
// will be reinitialized appropriately when/if the unique_ptr gets assigned-to or reset.
return __t; return __t;
} }
@ -474,6 +589,7 @@ public:
_LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX23 void reset(_Pp __p) _NOEXCEPT { _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX23 void reset(_Pp __p) _NOEXCEPT {
pointer __tmp = __ptr_; pointer __tmp = __ptr_;
__ptr_ = __p; __ptr_ = __p;
__checker_ = _BoundsChecker();
if (__tmp) if (__tmp)
__deleter_(__tmp); __deleter_(__tmp);
} }
@ -481,6 +597,7 @@ public:
_LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX23 void reset(nullptr_t = nullptr) _NOEXCEPT { _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX23 void reset(nullptr_t = nullptr) _NOEXCEPT {
pointer __tmp = __ptr_; pointer __tmp = __ptr_;
__ptr_ = nullptr; __ptr_ = nullptr;
__checker_ = _BoundsChecker();
if (__tmp) if (__tmp)
__deleter_(__tmp); __deleter_(__tmp);
} }
@ -489,6 +606,7 @@ public:
using std::swap; using std::swap;
swap(__ptr_, __u.__ptr_); swap(__ptr_, __u.__ptr_);
swap(__deleter_, __u.__deleter_); swap(__deleter_, __u.__deleter_);
swap(__checker_, __u.__checker_);
} }
}; };
@ -645,7 +763,7 @@ template <class _Tp>
inline _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX23 typename __unique_if<_Tp>::__unique_array_unknown_bound inline _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX23 typename __unique_if<_Tp>::__unique_array_unknown_bound
make_unique(size_t __n) { make_unique(size_t __n) {
typedef __remove_extent_t<_Tp> _Up; typedef __remove_extent_t<_Tp> _Up;
return unique_ptr<_Tp>(new _Up[__n]()); return unique_ptr<_Tp>(__private_constructor_tag(), new _Up[__n](), __n);
} }
template <class _Tp, class... _Args> template <class _Tp, class... _Args>
@ -664,7 +782,7 @@ make_unique_for_overwrite() {
template <class _Tp> template <class _Tp>
_LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX23 typename __unique_if<_Tp>::__unique_array_unknown_bound _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX23 typename __unique_if<_Tp>::__unique_array_unknown_bound
make_unique_for_overwrite(size_t __n) { make_unique_for_overwrite(size_t __n) {
return unique_ptr<_Tp>(new __remove_extent_t<_Tp>[__n]); return unique_ptr<_Tp>(__private_constructor_tag(), new __remove_extent_t<_Tp>[__n], __n);
} }
template <class _Tp, class... _Args> template <class _Tp, class... _Args>

View File

@ -1498,6 +1498,7 @@ module std_private_memory_allocator [system] { header "__m
module std_private_memory_allocator_arg_t [system] { header "__memory/allocator_arg_t.h" } module std_private_memory_allocator_arg_t [system] { header "__memory/allocator_arg_t.h" }
module std_private_memory_allocator_destructor [system] { header "__memory/allocator_destructor.h" } module std_private_memory_allocator_destructor [system] { header "__memory/allocator_destructor.h" }
module std_private_memory_allocator_traits [system] { header "__memory/allocator_traits.h" } module std_private_memory_allocator_traits [system] { header "__memory/allocator_traits.h" }
module std_private_memory_array_cookie [system] { header "__memory/array_cookie.h" }
module std_private_memory_assume_aligned [system] { header "__memory/assume_aligned.h" } module std_private_memory_assume_aligned [system] { header "__memory/assume_aligned.h" }
module std_private_memory_auto_ptr [system] { header "__memory/auto_ptr.h" } module std_private_memory_auto_ptr [system] { header "__memory/auto_ptr.h" }
module std_private_memory_builtin_new_allocator [system] { module std_private_memory_builtin_new_allocator [system] {

View File

@ -8,6 +8,10 @@
// UNSUPPORTED: libcpp-has-abi-fix-unordered-container-size-type, libcpp-abi-no-compressed-pair-padding // UNSUPPORTED: libcpp-has-abi-fix-unordered-container-size-type, libcpp-abi-no-compressed-pair-padding
// std::unique_ptr is used as an implementation detail of the unordered containers, so the layout of
// unordered containers changes when bounded unique_ptr is enabled.
// UNSUPPORTED: libcpp-has-abi-bounded-unique_ptr
#include <cstdint> #include <cstdint>
#include <unordered_map> #include <unordered_map>

View File

@ -8,6 +8,10 @@
// UNSUPPORTED: libcpp-has-abi-fix-unordered-container-size-type, libcpp-abi-no-compressed-pair-padding // UNSUPPORTED: libcpp-has-abi-fix-unordered-container-size-type, libcpp-abi-no-compressed-pair-padding
// std::unique_ptr is used as an implementation detail of the unordered containers, so the layout of
// unordered containers changes when bounded unique_ptr is enabled.
// UNSUPPORTED: libcpp-has-abi-bounded-unique_ptr
#include <cstdint> #include <cstdint>
#include <unordered_set> #include <unordered_set>

View File

@ -0,0 +1,93 @@
//===----------------------------------------------------------------------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
// <memory>
// unique_ptr
// Make sure that we can form unique_ptrs to incomplete types and perform restricted
// operations on them. This requires setting up a TU where the type is complete and
// the unique_ptr is created and destroyed, and a TU where the type is incomplete and
// we check that a restricted set of operations can be performed on the unique_ptr.
// RUN: %{cxx} %s %{flags} %{compile_flags} -c -o %t.tu1.o -DCOMPLETE
// RUN: %{cxx} %s %{flags} %{compile_flags} -c -o %t.tu2.o -DINCOMPLETE
// RUN: %{cxx} %t.tu1.o %t.tu2.o %{flags} %{link_flags} -o %t.exe
// RUN: %{exec} %t.exe
#include <memory>
#include <cassert>
struct T;
extern void use(std::unique_ptr<T>& ptr);
extern void use(std::unique_ptr<T[]>& ptr);
#ifdef INCOMPLETE
void use(std::unique_ptr<T>& ptr) {
{
T* x = ptr.get();
assert(x != nullptr);
}
{
T& ref = *ptr;
assert(&ref == ptr.get());
}
{
bool engaged = static_cast<bool>(ptr);
assert(engaged);
}
{
assert(ptr == ptr);
assert(!(ptr != ptr));
assert(!(ptr < ptr));
assert(!(ptr > ptr));
assert(ptr <= ptr);
assert(ptr >= ptr);
}
}
void use(std::unique_ptr<T[]>& ptr) {
{
T* x = ptr.get();
assert(x != nullptr);
}
{
bool engaged = static_cast<bool>(ptr);
assert(engaged);
}
{
assert(ptr == ptr);
assert(!(ptr != ptr));
assert(!(ptr < ptr));
assert(!(ptr > ptr));
assert(ptr <= ptr);
assert(ptr >= ptr);
}
}
#endif // INCOMPLETE
#ifdef COMPLETE
struct T {}; // complete the type
int main(int, char**) {
{
std::unique_ptr<T> ptr(new T());
use(ptr);
}
{
std::unique_ptr<T[]> ptr(new T[3]());
use(ptr);
}
return 0;
}
#endif // COMPLETE

View File

@ -0,0 +1,166 @@
//===----------------------------------------------------------------------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
// REQUIRES: has-unix-headers
// UNSUPPORTED: c++03, c++11, c++14, c++17
// UNSUPPORTED: libcpp-hardening-mode=none
// XFAIL: libcpp-hardening-mode=debug && availability-verbose_abort-missing
// <memory>
//
// unique_ptr<T[]>
//
// T& operator[](std::size_t);
// This test ensures that we catch an out-of-bounds access in std::unique_ptr<T[]>::operator[]
// when unique_ptr has the appropriate ABI configuration.
#include <memory>
#include <cstddef>
#include <string>
#include "check_assertion.h"
#include "type_algorithms.h"
struct MyDeleter {
MyDeleter() = default;
// required to exercise converting move-constructor
template <class T>
MyDeleter(std::default_delete<T> const&) {}
// required to exercise converting move-assignment
template <class T>
MyDeleter& operator=(std::default_delete<T> const&) {
return *this;
}
template <class T>
void operator()(T* ptr) const {
delete[] ptr;
}
};
template <class WithCookie, class NoCookie>
void test() {
// For types with an array cookie, we can always detect OOB accesses.
{
// Check with the default deleter
{
{
std::unique_ptr<WithCookie[]> ptr(new WithCookie[5]);
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);
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);
TEST_LIBCPP_ASSERT_FAILURE(ptr[6] = WithCookie(), "unique_ptr<T[]>::operator[](index): index out of range");
}
#endif
}
// Check with a custom deleter
{
std::unique_ptr<WithCookie[], MyDeleter> ptr(new WithCookie[5]);
TEST_LIBCPP_ASSERT_FAILURE(ptr[6], "unique_ptr<T[]>::operator[](index): index out of range");
}
}
// For types that don't have an array cookie, things are a bit more complicated. We can detect OOB accesses
// only when the unique_ptr is created via an API where the size is passed down to the library so that we
// can store it inside the unique_ptr. That requires the appropriate ABI configuration to be enabled.
//
// Note that APIs that allow the size to be passed down to the library only support the default deleter
// as of writing this test.
#if defined(_LIBCPP_ABI_BOUNDED_UNIQUE_PTR)
{
{
std::unique_ptr<NoCookie[]> ptr = std::make_unique<NoCookie[]>(5);
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);
TEST_LIBCPP_ASSERT_FAILURE(ptr[6] = NoCookie(), "unique_ptr<T[]>::operator[](index): index out of range");
}
# endif
}
#endif
// Make sure that we carry the bounds information properly through conversions, assignments, etc.
// These tests are mostly relevant when the ABI setting is enabled (with a stateful bounds-checker),
// but we still run them for types with an array cookie either way.
#if defined(_LIBCPP_ABI_BOUNDED_UNIQUE_PTR)
using Types = types::type_list<NoCookie, WithCookie>;
#else
using Types = types::type_list<WithCookie>;
#endif
types::for_each(Types(), []<class T> {
// Bounds carried through move construction
{
std::unique_ptr<T[]> ptr = std::make_unique<T[]>(5);
std::unique_ptr<T[]> other(std::move(ptr));
TEST_LIBCPP_ASSERT_FAILURE(other[6], "unique_ptr<T[]>::operator[](index): index out of range");
}
// Bounds carried through move assignment
{
std::unique_ptr<T[]> ptr = std::make_unique<T[]>(5);
std::unique_ptr<T[]> other;
other = std::move(ptr);
TEST_LIBCPP_ASSERT_FAILURE(other[6], "unique_ptr<T[]>::operator[](index): index out of range");
}
// Bounds carried through converting move-constructor
{
std::unique_ptr<T[]> ptr = std::make_unique<T[]>(5);
std::unique_ptr<T[], MyDeleter> other(std::move(ptr));
TEST_LIBCPP_ASSERT_FAILURE(other[6], "unique_ptr<T[]>::operator[](index): index out of range");
}
// Bounds carried through converting move-assignment
{
std::unique_ptr<T[]> ptr = std::make_unique<T[]>(5);
std::unique_ptr<T[], MyDeleter> other;
other = std::move(ptr);
TEST_LIBCPP_ASSERT_FAILURE(other[6], "unique_ptr<T[]>::operator[](index): index out of range");
}
});
}
template <std::size_t Size>
struct NoCookie {
char padding[Size];
};
template <std::size_t Size>
struct WithCookie {
WithCookie() = default;
WithCookie(WithCookie const&) {}
WithCookie& operator=(WithCookie const&) { return *this; }
~WithCookie() {}
char padding[Size];
};
int main(int, char**) {
test<WithCookie<1>, NoCookie<1>>();
test<WithCookie<2>, NoCookie<2>>();
test<WithCookie<3>, NoCookie<3>>();
test<WithCookie<4>, NoCookie<4>>();
test<WithCookie<8>, NoCookie<8>>();
test<WithCookie<16>, NoCookie<16>>();
test<WithCookie<32>, NoCookie<32>>();
test<WithCookie<256>, NoCookie<256>>();
test<std::string, int>();
return 0;
}

View File

@ -10,43 +10,114 @@
// unique_ptr // unique_ptr
// test get // pointer unique_ptr<T>::get() const noexcept;
// pointer unique_ptr<T[]>::get() const noexcept;
#include <memory> #include <memory>
#include <cassert> #include <cassert>
#include <cstddef>
#include "test_macros.h" #include "test_macros.h"
#include "unique_ptr_test_helper.h"
template <bool IsArray> template <class T>
TEST_CONSTEXPR_CXX23 void test_basic() { TEST_CONSTEXPR_CXX23 void test_basic() {
typedef typename std::conditional<IsArray, int[], int>::type VT; // non-const element type
typedef const VT CVT;
{ {
typedef std::unique_ptr<VT> U; // non-const access
int* p = newValue<VT>(1); {
U s(p); T* x = new T;
U const& sc = s; std::unique_ptr<T> ptr(x);
ASSERT_SAME_TYPE(decltype(s.get()), int*); ASSERT_SAME_TYPE(decltype(ptr.get()), T*);
ASSERT_SAME_TYPE(decltype(sc.get()), int*); ASSERT_NOEXCEPT(ptr.get());
assert(s.get() == p); assert(ptr.get() == x);
assert(sc.get() == s.get()); }
// const access
{
T* x = new T;
std::unique_ptr<T> const ptr(x);
ASSERT_SAME_TYPE(decltype(ptr.get()), T*);
ASSERT_NOEXCEPT(ptr.get());
assert(ptr.get() == x);
}
} }
// const element type
{ {
typedef std::unique_ptr<CVT> U; // non-const access
const int* p = newValue<VT>(1); {
U s(p); T* x = new T;
U const& sc = s; std::unique_ptr<T const> ptr(x);
ASSERT_SAME_TYPE(decltype(s.get()), const int*); ASSERT_SAME_TYPE(decltype(ptr.get()), T const*);
ASSERT_SAME_TYPE(decltype(sc.get()), const int*); assert(ptr.get() == x);
assert(s.get() == p); }
assert(sc.get() == s.get());
// const access
{
T* x = new T;
std::unique_ptr<T const> const ptr(x);
ASSERT_SAME_TYPE(decltype(ptr.get()), T const*);
assert(ptr.get() == x);
}
}
// Same thing but for unique_ptr<T[]>
// non-const element type
{
// non-const access
{
T* x = new T[3];
std::unique_ptr<T[]> ptr(x);
ASSERT_SAME_TYPE(decltype(ptr.get()), T*);
ASSERT_NOEXCEPT(ptr.get());
assert(ptr.get() == x);
}
// const access
{
T* x = new T[3];
std::unique_ptr<T[]> const ptr(x);
ASSERT_SAME_TYPE(decltype(ptr.get()), T*);
ASSERT_NOEXCEPT(ptr.get());
assert(ptr.get() == x);
}
}
// const element type
{
// non-const access
{
T* x = new T[3];
std::unique_ptr<T const[]> ptr(x);
ASSERT_SAME_TYPE(decltype(ptr.get()), T const*);
assert(ptr.get() == x);
}
// const access
{
T* x = new T[3];
std::unique_ptr<T const[]> const ptr(x);
ASSERT_SAME_TYPE(decltype(ptr.get()), T const*);
assert(ptr.get() == x);
}
} }
} }
template <std::size_t Size>
struct WithSize {
char padding[Size];
};
TEST_CONSTEXPR_CXX23 bool test() { TEST_CONSTEXPR_CXX23 bool test() {
test_basic</*IsArray*/ false>(); test_basic<char>();
test_basic<true>(); test_basic<int>();
test_basic<WithSize<1> >();
test_basic<WithSize<2> >();
test_basic<WithSize<3> >();
test_basic<WithSize<4> >();
test_basic<WithSize<8> >();
test_basic<WithSize<16> >();
test_basic<WithSize<256> >();
return true; return true;
} }

View File

@ -10,51 +10,117 @@
// unique_ptr // unique_ptr
// test op[](size_t) // T& unique_ptr::operator[](size_t) const
#include <memory> #include <memory>
#include <cassert> #include <cassert>
// TODO: Move TEST_IS_CONSTANT_EVALUATED into its own header
#include <type_traits> #include <type_traits>
#include <array>
#include "test_macros.h" #include "test_macros.h"
#include "type_algorithms.h"
class A { static int next = 0;
int state_; struct EnumeratedDefaultCtor {
static int next_; EnumeratedDefaultCtor() : value(0) { value = ++next; }
int value;
};
public: template <std::size_t Size>
TEST_CONSTEXPR_CXX23 A() : state_(0) { struct WithTrivialDtor {
if (!TEST_IS_CONSTANT_EVALUATED) std::array<char, Size> padding = {'x'};
state_ = ++next_; TEST_CONSTEXPR_CXX23 friend bool operator==(WithTrivialDtor const& x, WithTrivialDtor const& y) {
} return x.padding == y.padding;
TEST_CONSTEXPR_CXX23 int get() const { return state_; }
friend TEST_CONSTEXPR_CXX23 bool operator==(const A& x, int y) { return x.state_ == y; }
TEST_CONSTEXPR_CXX23 A& operator=(int i) {
state_ = i;
return *this;
} }
}; };
int A::next_ = 0; template <std::size_t Size>
struct WithNonTrivialDtor {
std::array<char, Size> padding = {'x'};
TEST_CONSTEXPR_CXX23 friend bool operator==(WithNonTrivialDtor const& x, WithNonTrivialDtor const& y) {
return x.padding == y.padding;
}
TEST_CONSTEXPR_CXX23 ~WithNonTrivialDtor() {}
};
template <class T>
struct CustomDeleter : std::default_delete<T> {};
TEST_CONSTEXPR_CXX23 bool test() { TEST_CONSTEXPR_CXX23 bool test() {
std::unique_ptr<A[]> p(new A[3]); // Basic test
if (!TEST_IS_CONSTANT_EVALUATED) { {
assert(p[0] == 1); std::unique_ptr<int[]> p(new int[3]);
assert(p[1] == 2); {
assert(p[2] == 3); int& result = p[0];
result = 0;
}
{
int& result = p[1];
result = 1;
}
{
int& result = p[2];
result = 2;
}
assert(p[0] == 0);
assert(p[1] == 1);
assert(p[2] == 2);
} }
p[0] = 3;
p[1] = 2; // Ensure that the order of access is correct after initializing a unique_ptr but
p[2] = 1; // before actually modifying any of its elements. The implementation would have to
assert(p[0] == 3); // really try for this not to be the case, but we still check it.
assert(p[1] == 2); //
assert(p[2] == 1); // This requires assigning known values to the elements when they are first constructed,
// which requires global state.
{
if (!TEST_IS_CONSTANT_EVALUATED) {
std::unique_ptr<EnumeratedDefaultCtor[]> p(new EnumeratedDefaultCtor[3]);
assert(p[0].value == 1);
assert(p[1].value == 2);
assert(p[2].value == 3);
}
}
// Make sure operator[] is const-qualified
{
std::unique_ptr<int[]> const p(new int[3]);
p[0] = 42;
assert(p[0] == 42);
}
// Make sure we properly handle types with trivial and non-trivial destructors of different
// sizes. This is relevant because some implementations may want to use properties of the
// ABI like array cookies and these properties often depend on e.g. the triviality of T's
// destructor, T's size and so on.
#if TEST_STD_VER >= 20 // this test is too painful to write before C++20
{
using TrickyCookieTypes = types::type_list<
WithTrivialDtor<1>,
WithTrivialDtor<2>,
WithTrivialDtor<3>,
WithTrivialDtor<4>,
WithTrivialDtor<8>,
WithTrivialDtor<16>,
WithTrivialDtor<256>,
WithNonTrivialDtor<1>,
WithNonTrivialDtor<2>,
WithNonTrivialDtor<3>,
WithNonTrivialDtor<4>,
WithNonTrivialDtor<8>,
WithNonTrivialDtor<16>,
WithNonTrivialDtor<256>>;
types::for_each(TrickyCookieTypes(), []<class T> {
types::for_each(types::type_list<std::default_delete<T[]>, CustomDeleter<T[]>>(), []<class Deleter> {
std::unique_ptr<T[], Deleter> p(new T[3]);
assert(p[0] == T());
assert(p[1] == T());
assert(p[2] == T());
});
});
}
#endif // C++20
return true; return true;
} }

View File

@ -374,6 +374,7 @@ macros = {
"_LIBCPP_ABI_BOUNDED_ITERATORS": "libcpp-has-abi-bounded-iterators", "_LIBCPP_ABI_BOUNDED_ITERATORS": "libcpp-has-abi-bounded-iterators",
"_LIBCPP_ABI_BOUNDED_ITERATORS_IN_STRING": "libcpp-has-abi-bounded-iterators-in-string", "_LIBCPP_ABI_BOUNDED_ITERATORS_IN_STRING": "libcpp-has-abi-bounded-iterators-in-string",
"_LIBCPP_ABI_BOUNDED_ITERATORS_IN_VECTOR": "libcpp-has-abi-bounded-iterators-in-vector", "_LIBCPP_ABI_BOUNDED_ITERATORS_IN_VECTOR": "libcpp-has-abi-bounded-iterators-in-vector",
"_LIBCPP_ABI_BOUNDED_UNIQUE_PTR": "libcpp-has-abi-bounded-unique_ptr",
"_LIBCPP_ABI_FIX_UNORDERED_CONTAINER_SIZE_TYPE": "libcpp-has-abi-fix-unordered-container-size-type", "_LIBCPP_ABI_FIX_UNORDERED_CONTAINER_SIZE_TYPE": "libcpp-has-abi-fix-unordered-container-size-type",
"_LIBCPP_DEPRECATED_ABI_DISABLE_PAIR_TRIVIAL_COPY_CTOR": "libcpp-deprecated-abi-disable-pair-trivial-copy-ctor", "_LIBCPP_DEPRECATED_ABI_DISABLE_PAIR_TRIVIAL_COPY_CTOR": "libcpp-deprecated-abi-disable-pair-trivial-copy-ctor",
"_LIBCPP_ABI_NO_COMPRESSED_PAIR_PADDING": "libcpp-abi-no-compressed-pair-padding", "_LIBCPP_ABI_NO_COMPRESSED_PAIR_PADDING": "libcpp-abi-no-compressed-pair-padding",