[libcxx-commits] [libcxx] [libc++] Add an ABI setting to harden unique_ptr<T[]>::operator[] (PR #91798)
Louis Dionne via libcxx-commits
libcxx-commits at lists.llvm.org
Thu Sep 26 10:23:49 PDT 2024
https://github.com/ldionne updated https://github.com/llvm/llvm-project/pull/91798
>From 76fc808922e143e81370cdc67c7d5911c2cbf244 Mon Sep 17 00:00:00 2001
From: Louis Dionne <ldionne.2 at gmail.com>
Date: Fri, 10 May 2024 15:40:18 -0400
Subject: [PATCH] [libc++] Harden unique_ptr<T[]>::operator[] when we can
This patch adds an ABI configuration that allows bounds-checking in
unique_ptr<T[]>::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<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(__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 <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
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 <cstddef>
+#include <cstdint>
#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<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>
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 <class _Up, class _OtherDeleter>
+ 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 <class _From>
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 <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,
bool _Dummy = true,
class = _EnableIfDeleterConstructible<_LValRefType<_Dummy> >,
@@ -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<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 {
reset(__u.release());
__deleter_ = std::forward<deleter_type>(__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 <class _Up,
class _Ep,
@@ -434,6 +544,7 @@ class _LIBCPP_UNIQUE_PTR_TRIVIAL_ABI _LIBCPP_TEMPLATE_VIS unique_ptr<_Tp[], _Dp>
_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<T[]>::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 <class _Tp>
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 <class _Tp, class... _Args>
@@ -664,7 +782,7 @@ make_unique_for_overwrite() {
template <class _Tp>
_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 <class _Tp, class... _Args>
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 <cstdint>
#include <unordered_map>
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 <cstdint>
#include <unordered_set>
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
+//
+//===----------------------------------------------------------------------===//
+
+// <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
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
+
+// <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;
+}
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<T>::get() const noexcept;
+// pointer unique_ptr<T[]>::get() const noexcept;
#include <memory>
#include <cassert>
+#include <cstddef>
#include "test_macros.h"
-#include "unique_ptr_test_helper.h"
-template <bool IsArray>
+template <class T>
TEST_CONSTEXPR_CXX23 void test_basic() {
- typedef typename std::conditional<IsArray, int[], int>::type VT;
- typedef const VT CVT;
+ // non-const element type
{
- typedef std::unique_ptr<VT> U;
- int* p = newValue<VT>(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<T> 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<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;
+ 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;
+ 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
{
- typedef std::unique_ptr<CVT> U;
- const int* p = newValue<VT>(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<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_basic</*IsArray*/ false>();
- test_basic<true>();
+ test_basic<char>();
+ 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;
}
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 <memory>
#include <cassert>
-
-// TODO: Move TEST_IS_CONSTANT_EVALUATED into its own header
#include <type_traits>
+#include <array>
#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 <std::size_t Size>
+struct WithTrivialDtor {
+ std::array<char, Size> 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 <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() {}
};
-int A::next_ = 0;
+template <class T>
+struct CustomDeleter : std::default_delete<T> {};
TEST_CONSTEXPR_CXX23 bool test() {
- std::unique_ptr<A[]> 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<int[]> 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<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());
+ });
+ });
}
- 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",
More information about the libcxx-commits
mailing list