[libcxx-commits] [libcxx] [libc++] Add an ABI setting to harden unique_ptr<T[]>::operator[] (PR #91798)
Louis Dionne via libcxx-commits
libcxx-commits at lists.llvm.org
Wed Sep 25 13:57:41 PDT 2024
https://github.com/ldionne updated https://github.com/llvm/llvm-project/pull/91798
>From c37a05eab6e2c87e253c1703dde37dac0f7b26c5 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 1/2] [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 | 127 ++++++++++++++++-
libcxx/include/module.modulemap | 1 +
.../unique.ptr.class/incomplete.sh.cpp | 93 ++++++++++++
.../assert.subscript.pass.cpp | 133 ++++++++++++++++++
.../unique.ptr.observers/get.pass.cpp | 117 ++++++++++++---
.../op_subscript.runtime.pass.cpp | 124 ++++++++++++----
10 files changed, 602 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 8c61009167ddce..b0c8e7dc5cb42c 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..61e9df050b77b1 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(__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,7 @@ 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();
+ __checker_ = _BoundsChecker();
return __t;
}
@@ -474,6 +588,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 +596,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 +605,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 +762,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 +781,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 ef4a242cf8bf7f..c86d5cea5ee8c3 100644
--- a/libcxx/include/module.modulemap
+++ b/libcxx/include/module.modulemap
@@ -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_destructor [system] { header "__memory/allocator_destructor.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_auto_ptr [system] { header "__memory/auto_ptr.h" }
module std_private_memory_builtin_new_allocator [system] {
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..6d5bacb94a2950
--- /dev/null
+++ b/libcxx/test/std/utilities/smartptr/unique.ptr/unique.ptr.class/unique.ptr.observers/assert.subscript.pass.cpp
@@ -0,0 +1,133 @@
+//===----------------------------------------------------------------------===//
+//
+// 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
+// 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"
+
+struct MyDeleter {
+ void operator()(int* ptr) const { delete[] ptr; }
+};
+
+template <class WithCookie, class NoCookie>
+void test() {
+ // Check with a default deleter
+ {
+ // Types that have an array cookie
+ {
+ {
+ 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], "unique_ptr<T[]>::operator[](index): index out of range");
+ }
+#endif
+ }
+
+ // Types that don't have an array cookie (only available under the right ABI configuration)
+#if defined(_LIBCPP_ABI_BOUNDED_UNIQUE_PTR)
+ {
+ {
+ std::unique_ptr<NoCookie[]> ptr(new NoCookie[5]);
+ TEST_LIBCPP_ASSERT_FAILURE(ptr[6], "unique_ptr<T[]>::operator[](index): index out of range");
+ }
+ {
+ 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], "unique_ptr<T[]>::operator[](index): index out of range");
+ }
+# endif
+ }
+#endif // defined(_LIBCPP_ABI_BOUNDED_UNIQUE_PTR)
+ }
+
+ // Check with a custom deleter (only available under the right ABI configuration)
+#if defined(_LIBCPP_ABI_BOUNDED_UNIQUE_PTR)
+ {
+ // Types that have an array cookie
+ {
+ {
+ std::unique_ptr<WithCookie[], MyDeleter> 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[], MyDeleter> ptr = std::make_unique_for_overwrite<WithCookie[], MyDeleter>(5);
+ TEST_LIBCPP_ASSERT_FAILURE(ptr[6], "unique_ptr<T[]>::operator[](index): index out of range");
+ }
+# endif
+ }
+
+ // Types that don't have an array cookie
+ {
+ {
+ std::unique_ptr<NoCookie[], MyDeleter> ptr = std::make_unique<NoCookie[]>(5);
+ TEST_LIBCPP_ASSERT_FAILURE(ptr[6] = 42, "unique_ptr<T[]>::operator[](index): index out of range");
+ }
+# if TEST_STD_VER >= 20
+ {
+ std::unique_ptr<NoCookie[], MyDeleter> ptr = std::make_unique_for_overwrite<NoCookie[], MyDeleter>(5);
+ TEST_LIBCPP_ASSERT_FAILURE(ptr[6] = 42, "unique_ptr<T[]>::operator[](index): index out of range");
+ }
+# endif
+ }
+ }
+#endif // defined(_LIBCPP_ABI_BOUNDED_UNIQUE_PTR)
+}
+
+template <std::size_t Size>
+struct NoCookie {
+ char padding[Size];
+};
+
+template <std::size_t Size>
+struct WithCookie {
+ ~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..75c35caf1b4d45 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;
}
>From 6fbdea93f9af05aa65219108b279b45d3d7c0123 Mon Sep 17 00:00:00 2001
From: Louis Dionne <ldionne.2 at gmail.com>
Date: Wed, 25 Sep 2024 16:57:30 -0400
Subject: [PATCH 2/2] Format test
---
.../unique.ptr.class/unique.ptr.observers/get.pass.cpp | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
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 75c35caf1b4d45..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
@@ -88,7 +88,7 @@ TEST_CONSTEXPR_CXX23 void test_basic() {
// non-const access
{
T* x = new T[3];
- std::unique_ptr<T const []> ptr(x);
+ std::unique_ptr<T const[]> ptr(x);
ASSERT_SAME_TYPE(decltype(ptr.get()), T const*);
assert(ptr.get() == x);
}
More information about the libcxx-commits
mailing list