[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:
parent
8e6bba230e
commit
18df9d23ea
@ -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 "")
|
||||||
|
@ -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
|
||||||
|
@ -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__)
|
||||||
|
55
libcxx/include/__memory/array_cookie.h
Normal file
55
libcxx/include/__memory/array_cookie.h
Normal 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
|
@ -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>
|
||||||
|
@ -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] {
|
||||||
|
@ -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>
|
||||||
|
|
||||||
|
@ -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>
|
||||||
|
|
||||||
|
@ -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
|
@ -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;
|
||||||
|
}
|
@ -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;
|
||||||
}
|
}
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
|
@ -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",
|
||||||
|
Loading…
x
Reference in New Issue
Block a user