[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