From 76fc808922e143e81370cdc67c7d5911c2cbf244 Mon Sep 17 00:00:00 2001 From: Louis Dionne Date: Fri, 10 May 2024 15:40:18 -0400 Subject: [PATCH] [libc++] Harden unique_ptr::operator[] when we can This patch adds an ABI configuration that allows bounds-checking in unique_ptr::operator[] when it has been constructed with bounds information in the API. The patch also adds support for bounds-checking when an array cookie is known to exist, which allows validating bounds without even changing the ABI. Drive-by changes: - Improve the tests for `operator[]` - Improve the tests for `.get()` - Add a test for incomplete types support --- ...-hardening-mode-fast-with-abi-breaks.cmake | 2 +- libcxx/include/CMakeLists.txt | 1 + libcxx/include/__configuration/abi.h | 7 + libcxx/include/__memory/array_cookie.h | 55 ++++++ libcxx/include/__memory/unique_ptr.h | 128 +++++++++++++- libcxx/include/module.modulemap | 1 + .../unord.map/abi.compile.pass.cpp | 4 + .../unord.set/abi.compile.pass.cpp | 4 + .../unique.ptr.class/incomplete.sh.cpp | 93 ++++++++++ .../assert.subscript.pass.cpp | 166 ++++++++++++++++++ .../unique.ptr.observers/get.pass.cpp | 117 +++++++++--- .../op_subscript.runtime.pass.cpp | 124 ++++++++++--- libcxx/utils/libcxx/test/features.py | 1 + 13 files changed, 645 insertions(+), 58 deletions(-) create mode 100644 libcxx/include/__memory/array_cookie.h create mode 100644 libcxx/test/std/utilities/smartptr/unique.ptr/unique.ptr.class/incomplete.sh.cpp create mode 100644 libcxx/test/std/utilities/smartptr/unique.ptr/unique.ptr.class/unique.ptr.observers/assert.subscript.pass.cpp diff --git a/libcxx/cmake/caches/Generic-hardening-mode-fast-with-abi-breaks.cmake b/libcxx/cmake/caches/Generic-hardening-mode-fast-with-abi-breaks.cmake index c0f2bad1c95af0..f63436c7679478 100644 --- a/libcxx/cmake/caches/Generic-hardening-mode-fast-with-abi-breaks.cmake +++ b/libcxx/cmake/caches/Generic-hardening-mode-fast-with-abi-breaks.cmake @@ -1,2 +1,2 @@ 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 "") diff --git a/libcxx/include/CMakeLists.txt b/libcxx/include/CMakeLists.txt index c22590b0ddfdb5..b23ea87880e373 100644 --- a/libcxx/include/CMakeLists.txt +++ b/libcxx/include/CMakeLists.txt @@ -536,6 +536,7 @@ set(files __memory/allocator_arg_t.h __memory/allocator_destructor.h __memory/allocator_traits.h + __memory/array_cookie.h __memory/assume_aligned.h __memory/auto_ptr.h __memory/builtin_new_allocator.h diff --git a/libcxx/include/__configuration/abi.h b/libcxx/include/__configuration/abi.h index 707e10b5ceb53f..62c129f5921dee 100644 --- a/libcxx/include/__configuration/abi.h +++ b/libcxx/include/__configuration/abi.h @@ -181,6 +181,13 @@ # define _LIBCPP_ABI_NO_COMPRESSED_PAIR_PADDING #endif +// Tracks the bounds of the array owned by std::unique_ptr, 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 to change and its size to increase. +// #define _LIBCPP_ABI_BOUNDED_UNIQUE_PTR + #if defined(_LIBCPP_COMPILER_CLANG_BASED) # if defined(__APPLE__) # if defined(__i386__) || defined(__x86_64__) diff --git a/libcxx/include/__memory/array_cookie.h b/libcxx/include/__memory/array_cookie.h new file mode 100644 index 00000000000000..34eec643206103 --- /dev/null +++ b/libcxx/include/__memory/array_cookie.h @@ -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 + +#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 +struct __has_array_cookie : _Not > {}; +#else +template +struct __has_array_cookie : false_type {}; +#endif + +template +// 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(__ptr) - 1; // TODO: Use a builtin instead + return *__cookie; +} + +_LIBCPP_END_NAMESPACE_STD + +#endif // _LIBCPP___MEMORY_ARRAY_COOKIE_H diff --git a/libcxx/include/__memory/unique_ptr.h b/libcxx/include/__memory/unique_ptr.h index 9ca13d0e4fd1a7..11215dc111e36a 100644 --- a/libcxx/include/__memory/unique_ptr.h +++ b/libcxx/include/__memory/unique_ptr.h @@ -10,6 +10,7 @@ #ifndef _LIBCPP___MEMORY_UNIQUE_PTR_H #define _LIBCPP___MEMORY_UNIQUE_PTR_H +#include <__assert> #include <__compare/compare_three_way.h> #include <__compare/compare_three_way_result.h> #include <__compare/three_way_comparable.h> @@ -17,8 +18,10 @@ #include <__functional/hash.h> #include <__functional/operations.h> #include <__memory/allocator_traits.h> // __pointer +#include <__memory/array_cookie.h> #include <__memory/auto_ptr.h> #include <__memory/compressed_pair.h> +#include <__memory/pointer_traits.h> #include <__type_traits/add_lvalue_reference.h> #include <__type_traits/common_type.h> #include <__type_traits/conditional.h> @@ -27,6 +30,7 @@ #include <__type_traits/integral_constant.h> #include <__type_traits/is_array.h> #include <__type_traits/is_assignable.h> +#include <__type_traits/is_constant_evaluated.h> #include <__type_traits/is_constructible.h> #include <__type_traits/is_convertible.h> #include <__type_traits/is_function.h> @@ -41,7 +45,9 @@ #include <__utility/declval.h> #include <__utility/forward.h> #include <__utility/move.h> +#include <__utility/private_constructor_tag.h> #include +#include #if !defined(_LIBCPP_HAS_NO_PRAGMA_SYSTEM_HEADER) # pragma GCC system_header @@ -292,6 +298,91 @@ class _LIBCPP_UNIQUE_PTR_TRIVIAL_ABI _LIBCPP_TEMPLATE_VIS unique_ptr { } }; +// Bounds checking in unique_ptr +// ================================== +// +// We provide some helper classes that allow bounds checking when accessing a unique_ptr. +// 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`, 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 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 ::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 ::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. 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 ::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 ::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 _LIBCPP_UNIQUE_PTR_TRIVIAL_ABI _LIBCPP_TEMPLATE_VIS unique_ptr<_Tp[], _Dp> { public: @@ -300,8 +391,9 @@ class _LIBCPP_UNIQUE_PTR_TRIVIAL_ABI _LIBCPP_TEMPLATE_VIS unique_ptr<_Tp[], _Dp> typedef typename __pointer<_Tp, deleter_type>::type pointer; // 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 + // - (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 // references to itself. This means that the entire structure is trivially relocatable if its members are. @@ -311,7 +403,16 @@ class _LIBCPP_UNIQUE_PTR_TRIVIAL_ABI _LIBCPP_TEMPLATE_VIS unique_ptr<_Tp[], _Dp> void>; private: + template + friend class unique_ptr; + _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 struct _CheckArrayPointerConversion : is_same<_From, pointer> {}; @@ -373,6 +474,12 @@ class _LIBCPP_UNIQUE_PTR_TRIVIAL_ABI _LIBCPP_TEMPLATE_VIS unique_ptr<_Tp[], _Dp> : __ptr_(__p), __deleter_() {} + // Private constructor used by make_unique & friends to pass the size that was allocated + template ::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 >, @@ -411,11 +518,13 @@ class _LIBCPP_UNIQUE_PTR_TRIVIAL_ABI _LIBCPP_TEMPLATE_VIS unique_ptr<_Tp[], _Dp> _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX23 unique_ptr(unique_ptr&& __u) _NOEXCEPT : __ptr_(__u.release()), - __deleter_(std::forward(__u.get_deleter())) {} + __deleter_(std::forward(__u.get_deleter())), + __checker_(std::move(__u.__checker_)) {} _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX23 unique_ptr& operator=(unique_ptr&& __u) _NOEXCEPT { reset(__u.release()); __deleter_ = std::forward(__u.get_deleter()); + __checker_ = std::move(std::move(__u.__checker_)); return *this; } @@ -425,7 +534,8 @@ class _LIBCPP_UNIQUE_PTR_TRIVIAL_ABI _LIBCPP_TEMPLATE_VIS unique_ptr<_Tp[], _Dp> class = _EnableIfDeleterConvertible<_Ep> > _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX23 unique_ptr(unique_ptr<_Up, _Ep>&& __u) _NOEXCEPT : __ptr_(__u.release()), - __deleter_(std::forward<_Ep>(__u.get_deleter())) {} + __deleter_(std::forward<_Ep>(__u.get_deleter())), + __checker_(std::move(__u.__checker_)) {} template _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX23 unique_ptr& operator=(unique_ptr<_Up, _Ep>&& __u) _NOEXCEPT { reset(__u.release()); __deleter_ = std::forward<_Ep>(__u.get_deleter()); + __checker_ = std::move(__u.__checker_); return *this; } @@ -451,6 +562,8 @@ class _LIBCPP_UNIQUE_PTR_TRIVIAL_ABI _LIBCPP_TEMPLATE_VIS unique_ptr<_Tp[], _Dp> } _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::operator[](index): index out of range"); return __ptr_[__i]; } _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX23 pointer get() const _NOEXCEPT { return __ptr_; } @@ -467,6 +580,8 @@ class _LIBCPP_UNIQUE_PTR_TRIVIAL_ABI _LIBCPP_TEMPLATE_VIS unique_ptr<_Tp[], _Dp> _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX23 pointer release() _NOEXCEPT { pointer __t = __ptr_; __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; } @@ -474,6 +589,7 @@ class _LIBCPP_UNIQUE_PTR_TRIVIAL_ABI _LIBCPP_TEMPLATE_VIS unique_ptr<_Tp[], _Dp> _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX23 void reset(_Pp __p) _NOEXCEPT { pointer __tmp = __ptr_; __ptr_ = __p; + __checker_ = _BoundsChecker(); if (__tmp) __deleter_(__tmp); } @@ -481,6 +597,7 @@ class _LIBCPP_UNIQUE_PTR_TRIVIAL_ABI _LIBCPP_TEMPLATE_VIS unique_ptr<_Tp[], _Dp> _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX23 void reset(nullptr_t = nullptr) _NOEXCEPT { pointer __tmp = __ptr_; __ptr_ = nullptr; + __checker_ = _BoundsChecker(); if (__tmp) __deleter_(__tmp); } @@ -489,6 +606,7 @@ class _LIBCPP_UNIQUE_PTR_TRIVIAL_ABI _LIBCPP_TEMPLATE_VIS unique_ptr<_Tp[], _Dp> using std::swap; swap(__ptr_, __u.__ptr_); swap(__deleter_, __u.__deleter_); + swap(__checker_, __u.__checker_); } }; @@ -645,7 +763,7 @@ template inline _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX23 typename __unique_if<_Tp>::__unique_array_unknown_bound make_unique(size_t __n) { 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 @@ -664,7 +782,7 @@ make_unique_for_overwrite() { template _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX23 typename __unique_if<_Tp>::__unique_array_unknown_bound 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 diff --git a/libcxx/include/module.modulemap b/libcxx/include/module.modulemap index 0c5569e6bd9af1..55040e82ab4e0d 100644 --- a/libcxx/include/module.modulemap +++ b/libcxx/include/module.modulemap @@ -1485,6 +1485,7 @@ module std [system] { module allocator_destructor { header "__memory/allocator_destructor.h" } module allocator_traits { header "__memory/allocator_traits.h" } module assume_aligned { header "__memory/assume_aligned.h" } + module array_cookie { header "__memory/array_cookie.h" } module auto_ptr { header "__memory/auto_ptr.h" } module builtin_new_allocator { header "__memory/builtin_new_allocator.h" } module compressed_pair { header "__memory/compressed_pair.h" } diff --git a/libcxx/test/libcxx/containers/associative/unord.map/abi.compile.pass.cpp b/libcxx/test/libcxx/containers/associative/unord.map/abi.compile.pass.cpp index 9147ca93866b23..cea074a4e70f1f 100644 --- a/libcxx/test/libcxx/containers/associative/unord.map/abi.compile.pass.cpp +++ b/libcxx/test/libcxx/containers/associative/unord.map/abi.compile.pass.cpp @@ -8,6 +8,10 @@ // 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 #include diff --git a/libcxx/test/libcxx/containers/associative/unord.set/abi.compile.pass.cpp b/libcxx/test/libcxx/containers/associative/unord.set/abi.compile.pass.cpp index dc6cc082c3b99e..2a112aff227d8e 100644 --- a/libcxx/test/libcxx/containers/associative/unord.set/abi.compile.pass.cpp +++ b/libcxx/test/libcxx/containers/associative/unord.set/abi.compile.pass.cpp @@ -8,6 +8,10 @@ // 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 #include diff --git a/libcxx/test/std/utilities/smartptr/unique.ptr/unique.ptr.class/incomplete.sh.cpp b/libcxx/test/std/utilities/smartptr/unique.ptr/unique.ptr.class/incomplete.sh.cpp new file mode 100644 index 00000000000000..4a03d2bcf07bfe --- /dev/null +++ b/libcxx/test/std/utilities/smartptr/unique.ptr/unique.ptr.class/incomplete.sh.cpp @@ -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 +// +//===----------------------------------------------------------------------===// + +// + +// 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 +#include + +struct T; +extern void use(std::unique_ptr& ptr); +extern void use(std::unique_ptr& ptr); + +#ifdef INCOMPLETE + +void use(std::unique_ptr& ptr) { + { + T* x = ptr.get(); + assert(x != nullptr); + } + { + T& ref = *ptr; + assert(&ref == ptr.get()); + } + { + bool engaged = static_cast(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& ptr) { + { + T* x = ptr.get(); + assert(x != nullptr); + } + { + bool engaged = static_cast(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 ptr(new T()); + use(ptr); + } + + { + std::unique_ptr ptr(new T[3]()); + use(ptr); + } + return 0; +} + +#endif // COMPLETE diff --git a/libcxx/test/std/utilities/smartptr/unique.ptr/unique.ptr.class/unique.ptr.observers/assert.subscript.pass.cpp b/libcxx/test/std/utilities/smartptr/unique.ptr/unique.ptr.class/unique.ptr.observers/assert.subscript.pass.cpp new file mode 100644 index 00000000000000..1eaf2d5900356b --- /dev/null +++ b/libcxx/test/std/utilities/smartptr/unique.ptr/unique.ptr.class/unique.ptr.observers/assert.subscript.pass.cpp @@ -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 + +// +// +// unique_ptr +// +// T& operator[](std::size_t); + +// This test ensures that we catch an out-of-bounds access in std::unique_ptr::operator[] +// when unique_ptr has the appropriate ABI configuration. + +#include +#include +#include + +#include "check_assertion.h" +#include "type_algorithms.h" + +struct MyDeleter { + MyDeleter() = default; + + // required to exercise converting move-constructor + template + MyDeleter(std::default_delete const&) {} + + // required to exercise converting move-assignment + template + MyDeleter& operator=(std::default_delete const&) { + return *this; + } + + template + void operator()(T* ptr) const { + delete[] ptr; + } +}; + +template +void test() { + // For types with an array cookie, we can always detect OOB accesses. + { + // Check with the default deleter + { + { + std::unique_ptr ptr(new WithCookie[5]); + TEST_LIBCPP_ASSERT_FAILURE(ptr[6], "unique_ptr::operator[](index): index out of range"); + } + { + std::unique_ptr ptr = std::make_unique(5); + TEST_LIBCPP_ASSERT_FAILURE(ptr[6], "unique_ptr::operator[](index): index out of range"); + } +#if TEST_STD_VER >= 20 + { + std::unique_ptr ptr = std::make_unique_for_overwrite(5); + TEST_LIBCPP_ASSERT_FAILURE(ptr[6] = WithCookie(), "unique_ptr::operator[](index): index out of range"); + } +#endif + } + + // Check with a custom deleter + { + std::unique_ptr ptr(new WithCookie[5]); + TEST_LIBCPP_ASSERT_FAILURE(ptr[6], "unique_ptr::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 ptr = std::make_unique(5); + TEST_LIBCPP_ASSERT_FAILURE(ptr[6], "unique_ptr::operator[](index): index out of range"); + } +# if TEST_STD_VER >= 20 + { + std::unique_ptr ptr = std::make_unique_for_overwrite(5); + TEST_LIBCPP_ASSERT_FAILURE(ptr[6] = NoCookie(), "unique_ptr::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; +#else + using Types = types::type_list; +#endif + types::for_each(Types(), [] { + // Bounds carried through move construction + { + std::unique_ptr ptr = std::make_unique(5); + std::unique_ptr other(std::move(ptr)); + TEST_LIBCPP_ASSERT_FAILURE(other[6], "unique_ptr::operator[](index): index out of range"); + } + + // Bounds carried through move assignment + { + std::unique_ptr ptr = std::make_unique(5); + std::unique_ptr other; + other = std::move(ptr); + TEST_LIBCPP_ASSERT_FAILURE(other[6], "unique_ptr::operator[](index): index out of range"); + } + + // Bounds carried through converting move-constructor + { + std::unique_ptr ptr = std::make_unique(5); + std::unique_ptr other(std::move(ptr)); + TEST_LIBCPP_ASSERT_FAILURE(other[6], "unique_ptr::operator[](index): index out of range"); + } + + // Bounds carried through converting move-assignment + { + std::unique_ptr ptr = std::make_unique(5); + std::unique_ptr other; + other = std::move(ptr); + TEST_LIBCPP_ASSERT_FAILURE(other[6], "unique_ptr::operator[](index): index out of range"); + } + }); +} + +template +struct NoCookie { + char padding[Size]; +}; + +template +struct WithCookie { + WithCookie() = default; + WithCookie(WithCookie const&) {} + WithCookie& operator=(WithCookie const&) { return *this; } + ~WithCookie() {} + char padding[Size]; +}; + +int main(int, char**) { + test, NoCookie<1>>(); + test, NoCookie<2>>(); + test, NoCookie<3>>(); + test, NoCookie<4>>(); + test, NoCookie<8>>(); + test, NoCookie<16>>(); + test, NoCookie<32>>(); + test, NoCookie<256>>(); + test(); + + return 0; +} diff --git a/libcxx/test/std/utilities/smartptr/unique.ptr/unique.ptr.class/unique.ptr.observers/get.pass.cpp b/libcxx/test/std/utilities/smartptr/unique.ptr/unique.ptr.class/unique.ptr.observers/get.pass.cpp index 3bd3788960e2a6..c92c39c8f299e9 100644 --- a/libcxx/test/std/utilities/smartptr/unique.ptr/unique.ptr.class/unique.ptr.observers/get.pass.cpp +++ b/libcxx/test/std/utilities/smartptr/unique.ptr/unique.ptr.class/unique.ptr.observers/get.pass.cpp @@ -10,43 +10,114 @@ // unique_ptr -// test get +// pointer unique_ptr::get() const noexcept; +// pointer unique_ptr::get() const noexcept; #include #include +#include #include "test_macros.h" -#include "unique_ptr_test_helper.h" -template +template TEST_CONSTEXPR_CXX23 void test_basic() { - typedef typename std::conditional::type VT; - typedef const VT CVT; + // non-const element type { - typedef std::unique_ptr U; - int* p = newValue(1); - U s(p); - U const& sc = s; - ASSERT_SAME_TYPE(decltype(s.get()), int*); - ASSERT_SAME_TYPE(decltype(sc.get()), int*); - assert(s.get() == p); - assert(sc.get() == s.get()); + // non-const access + { + T* x = new T; + std::unique_ptr ptr(x); + ASSERT_SAME_TYPE(decltype(ptr.get()), T*); + ASSERT_NOEXCEPT(ptr.get()); + assert(ptr.get() == x); + } + + // const access + { + T* x = new T; + std::unique_ptr 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; + std::unique_ptr ptr(x); + ASSERT_SAME_TYPE(decltype(ptr.get()), T const*); + assert(ptr.get() == x); + } + + // const access + { + T* x = new T; + std::unique_ptr const ptr(x); + ASSERT_SAME_TYPE(decltype(ptr.get()), T const*); + assert(ptr.get() == x); + } + } + + // Same thing but for unique_ptr + // non-const element type + { + // non-const access + { + T* x = new T[3]; + std::unique_ptr 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 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 U; - const int* p = newValue(1); - U s(p); - U const& sc = s; - ASSERT_SAME_TYPE(decltype(s.get()), const int*); - ASSERT_SAME_TYPE(decltype(sc.get()), const int*); - assert(s.get() == p); - assert(sc.get() == s.get()); + // non-const access + { + T* x = new T[3]; + std::unique_ptr ptr(x); + ASSERT_SAME_TYPE(decltype(ptr.get()), T const*); + assert(ptr.get() == x); + } + + // const access + { + T* x = new T[3]; + std::unique_ptr const ptr(x); + ASSERT_SAME_TYPE(decltype(ptr.get()), T const*); + assert(ptr.get() == x); + } } } +template +struct WithSize { + char padding[Size]; +}; + TEST_CONSTEXPR_CXX23 bool test() { - test_basic(); - test_basic(); + test_basic(); + test_basic(); + test_basic >(); + test_basic >(); + test_basic >(); + test_basic >(); + test_basic >(); + test_basic >(); + test_basic >(); return true; } diff --git a/libcxx/test/std/utilities/smartptr/unique.ptr/unique.ptr.class/unique.ptr.observers/op_subscript.runtime.pass.cpp b/libcxx/test/std/utilities/smartptr/unique.ptr/unique.ptr.class/unique.ptr.observers/op_subscript.runtime.pass.cpp index fbb4dbc6e03088..ebfad8ec724e51 100644 --- a/libcxx/test/std/utilities/smartptr/unique.ptr/unique.ptr.class/unique.ptr.observers/op_subscript.runtime.pass.cpp +++ b/libcxx/test/std/utilities/smartptr/unique.ptr/unique.ptr.class/unique.ptr.observers/op_subscript.runtime.pass.cpp @@ -10,51 +10,117 @@ // unique_ptr -// test op[](size_t) +// T& unique_ptr::operator[](size_t) const #include #include - -// TODO: Move TEST_IS_CONSTANT_EVALUATED into its own header #include +#include #include "test_macros.h" +#include "type_algorithms.h" -class A { - int state_; - static int next_; +static int next = 0; +struct EnumeratedDefaultCtor { + EnumeratedDefaultCtor() : value(0) { value = ++next; } + int value; +}; -public: - TEST_CONSTEXPR_CXX23 A() : state_(0) { - if (!TEST_IS_CONSTANT_EVALUATED) - state_ = ++next_; +template +struct WithTrivialDtor { + std::array padding = {'x'}; + 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; +template +struct WithNonTrivialDtor { + std::array padding = {'x'}; + TEST_CONSTEXPR_CXX23 friend bool operator==(WithNonTrivialDtor const& x, WithNonTrivialDtor const& y) { + return x.padding == y.padding; } + TEST_CONSTEXPR_CXX23 ~WithNonTrivialDtor() {} }; -int A::next_ = 0; +template +struct CustomDeleter : std::default_delete {}; TEST_CONSTEXPR_CXX23 bool test() { - std::unique_ptr p(new A[3]); - if (!TEST_IS_CONSTANT_EVALUATED) { - assert(p[0] == 1); - assert(p[1] == 2); - assert(p[2] == 3); + // Basic test + { + std::unique_ptr p(new int[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); + } + + // Ensure that the order of access is correct after initializing a unique_ptr but + // before actually modifying any of its elements. The implementation would have to + // really try for this not to be the case, but we still check it. + // + // 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 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 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(), [] { + types::for_each(types::type_list, CustomDeleter>(), [] { + std::unique_ptr p(new T[3]); + assert(p[0] == T()); + assert(p[1] == T()); + assert(p[2] == T()); + }); + }); } - p[0] = 3; - p[1] = 2; - p[2] = 1; - assert(p[0] == 3); - assert(p[1] == 2); - assert(p[2] == 1); +#endif // C++20 return true; } diff --git a/libcxx/utils/libcxx/test/features.py b/libcxx/utils/libcxx/test/features.py index 15456171b54837..ea8edca0628920 100644 --- a/libcxx/utils/libcxx/test/features.py +++ b/libcxx/utils/libcxx/test/features.py @@ -374,6 +374,7 @@ def _mingwSupportsModules(cfg): "_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_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_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",