[libc-commits] [libc] [libc] Implement CheckedPtr utility (PR #234646)

Pavel Labath via libc-commits libc-commits at lists.llvm.org
Mon Oct 26 02:51:44 PDT 2026


https://github.com/labath updated https://github.com/llvm/llvm-project/pull/234646

>From 823ac757fd5ee22156291eb2cf37e8115bdd6a03 Mon Sep 17 00:00:00 2001
From: Pavel Labath <pavel at labath.sk>
Date: Mon, 12 Oct 2026 12:33:43 +0000
Subject: [PATCH 1/2] [libc] Implement CheckedPtr utility

This adds CheckedPtr, a wrapper around cpp::unique_ptr (along with a
make_unique helper) that asserts in !NDEBUG builds that the pointer was
checked for nullness before it is dereferenced, destroyed or overwritten
(e.g., move assignment). This way, a missing check is caught even if the
allocation succeeds.

The idea is replace AllocChecker (which was meant to serve a similar
purpose, but the checking part didn't materialize so far). See
<https://discourse.llvm.org/t/rfc-error-checking-apis-for-memory-allocation-and-management/92511>
for more details.

A few notable design choices:
- operator bool() is non-const so the checked flag does not need to be
  mutable (avoiding data races on concurrent const reads)
- moving a CheckedPtr disarms the source and leaves the destination
  unchecked (similar to llvm::Error), forcing the new owner to check it
- the class wraps unique_ptr rather than replacing it, as I expect we
  will use for a non-checked owning pointer (CheckedPtr isn't
  particularly suitable for long term storage in a member variable).
- the API is intentionally minimal for now (no default constructor,
  get, release, reset, swap, or nullptr comparisons).

Assisted-by: Gemini
---
 libc/src/__support/CMakeLists.txt            |  15 +
 libc/src/__support/checked_ptr.h             | 200 +++++++++++++
 libc/test/src/__support/CMakeLists.txt       |  14 +
 libc/test/src/__support/checked_ptr_test.cpp | 297 +++++++++++++++++++
 4 files changed, 526 insertions(+)
 create mode 100644 libc/src/__support/checked_ptr.h
 create mode 100644 libc/test/src/__support/checked_ptr_test.cpp

diff --git a/libc/src/__support/CMakeLists.txt b/libc/src/__support/CMakeLists.txt
index dfb1ed0e13ff39..2324af89ca4796 100644
--- a/libc/src/__support/CMakeLists.txt
+++ b/libc/src/__support/CMakeLists.txt
@@ -171,6 +171,21 @@ add_header_library(
     libc.src.__support.macros.config
 )
 
+add_header_library(
+  checked_ptr
+  HDRS
+    checked_ptr.h
+  DEPENDS
+    .alloc_checker
+    .libc_assert
+    libc.hdr.types.size_t
+    libc.src.__support.CPP.type_traits
+    libc.src.__support.CPP.unique_ptr
+    libc.src.__support.CPP.utility
+    libc.src.__support.macros.attributes
+    libc.src.__support.macros.config
+)
+
 add_header_library(
   ctype_utils
   HDRS
diff --git a/libc/src/__support/checked_ptr.h b/libc/src/__support/checked_ptr.h
new file mode 100644
index 00000000000000..3597ca7b05e3e0
--- /dev/null
+++ b/libc/src/__support/checked_ptr.h
@@ -0,0 +1,200 @@
+//===----------------------------------------------------------------------===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+///
+/// \file
+/// Smart pointer wrapper around cpp::unique_ptr enforcing null checks.
+///
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_LIBC_SRC___SUPPORT_CHECKED_PTR_H
+#define LLVM_LIBC_SRC___SUPPORT_CHECKED_PTR_H
+
+#include "hdr/types/size_t.h"
+#include "src/__support/CPP/type_traits.h"
+#include "src/__support/CPP/unique_ptr.h"
+#include "src/__support/CPP/utility.h"
+#include "src/__support/alloc-checker.h"
+#include "src/__support/libc_assert.h"
+#include "src/__support/macros/attributes.h"
+#include "src/__support/macros/config.h"
+
+namespace LIBC_NAMESPACE_DECL {
+
+namespace detail {
+
+class LIBC_TRIVIAL_ABI CheckedState {
+#ifndef NDEBUG
+  bool checked = false;
+
+public:
+  LIBC_INLINE constexpr CheckedState() = default;
+
+  // Moving disarms `other` and starts `this` unchecked so the new owner must
+  // explicitly check its value.
+  LIBC_INLINE constexpr CheckedState(CheckedState &&other) {
+    other.checked = true;
+  }
+
+  // Overwriting an existing CheckedState requires it to have been checked,
+  // disarms `other`, and resets `this` to unchecked.
+  LIBC_INLINE constexpr CheckedState &operator=(CheckedState &&other) {
+    assert_checked();
+    // Self-move-assignment leaves the (checked) state unchanged.
+    if (this == &other)
+      return *this;
+    other.checked = true;
+    checked = false;
+    return *this;
+  }
+
+  LIBC_INLINE ~CheckedState() { assert_checked(); }
+
+  LIBC_INLINE constexpr void mark_checked() { checked = true; }
+  LIBC_INLINE constexpr void assert_checked() const { LIBC_ASSERT(checked); }
+#else
+public:
+  LIBC_INLINE constexpr CheckedState() = default;
+  LIBC_INLINE constexpr CheckedState(CheckedState &&) = default;
+  LIBC_INLINE constexpr CheckedState &operator=(CheckedState &&) = default;
+
+  LIBC_INLINE constexpr void mark_checked() {}
+  LIBC_INLINE constexpr void assert_checked() const {}
+#endif
+};
+
+} // namespace detail
+
+/// A smart pointer wrapper around cpp::unique_ptr that enforces (in !NDEBUG
+/// builds) that callers check pointer nullness before accessing or destroying
+/// the pointer.
+///
+/// A pointer (including a null one) counts as checked once its operator bool
+/// has been called. In !NDEBUG builds, the following operations assert that the
+/// pointer has been checked:
+/// - dereferencing (operator*, operator->, operator[]),
+/// - extracting the pointer via take_unique(),
+/// - destruction,
+/// - being overwritten by a move assignment.
+///
+/// Moving from a CheckedPtr disarms the source and leaves the destination
+/// unchecked, forcing the new owner to check it.
+///
+/// In NDEBUG builds, nothing is tracked and CheckedPtr behaves like
+/// cpp::unique_ptr.
+template <typename T, typename Deleter = cpp::default_delete<T>>
+class [[nodiscard]] LIBC_TRIVIAL_ABI CheckedPtr {
+  using PtrType = cpp::unique_ptr<T, Deleter>;
+  LIBC_NO_UNIQUE_ADDRESS detail::CheckedState state;
+  PtrType ptr;
+
+  template <typename U, typename E> friend class CheckedPtr;
+
+public:
+  using deleter_type = typename PtrType::deleter_type;
+  using element_type = typename PtrType::element_type;
+  using pointer = typename PtrType::pointer;
+
+  LIBC_INLINE constexpr explicit CheckedPtr(pointer p) : ptr(p) {}
+  LIBC_INLINE constexpr CheckedPtr(pointer p, const deleter_type &d)
+      : ptr(p, d) {}
+  LIBC_INLINE constexpr CheckedPtr(pointer p, deleter_type &&d)
+      : ptr(p, cpp::move(d)) {}
+
+  LIBC_INLINE constexpr CheckedPtr(CheckedPtr &&other) = default;
+  LIBC_INLINE constexpr CheckedPtr &operator=(CheckedPtr &&other) = default;
+
+  // Conversion move constructor (available iff the underlying cpp::unique_ptr
+  // conversion is)
+  template <typename U, typename E,
+            cpp::enable_if_t<
+                cpp::is_convertible_v<cpp::unique_ptr<U, E>, PtrType>, int> = 0>
+  LIBC_INLINE constexpr CheckedPtr(CheckedPtr<U, E> &&other)
+      : state(cpp::move(other.state)), ptr(cpp::move(other.ptr)) {}
+
+  // Conversion move assignment (available iff the underlying cpp::unique_ptr
+  // assignment is)
+  template <
+      typename U, typename E,
+      cpp::enable_if_t<
+          cpp::is_assignable_v<PtrType &, cpp::unique_ptr<U, E> &&>, int> = 0>
+  LIBC_INLINE constexpr CheckedPtr &operator=(CheckedPtr<U, E> &&other) {
+    state = cpp::move(other.state);
+    ptr = cpp::move(other.ptr);
+    return *this;
+  }
+
+  /// Returns true if the pointer is non-null, and marks it as checked.
+  ///
+  /// This is intentionally non-const: recording the check in a const member
+  /// function would require a mutable flag, and concurrent checks through const
+  /// references would then race on it. As a consequence, a CheckedPtr cannot be
+  /// checked through a const reference (but it can be used through one once it
+  /// has been checked).
+  LIBC_INLINE constexpr explicit operator bool() {
+    state.mark_checked();
+    return static_cast<bool>(ptr);
+  }
+
+  template <typename U = T, cpp::enable_if_t<!cpp::is_array_v<U>, int> = 0>
+  LIBC_INLINE constexpr cpp::add_lvalue_reference_t<U> operator*() const {
+    state.assert_checked();
+    return *ptr;
+  }
+
+  template <typename U = T, cpp::enable_if_t<!cpp::is_array_v<U>, int> = 0>
+  LIBC_INLINE constexpr pointer operator->() const {
+    state.assert_checked();
+    return ptr.get();
+  }
+
+  // The return type depends on U (rather than using element_type) so that it is
+  // not formed for non-array types, e.g. CheckedPtr<void, D>.
+  template <typename U = T, cpp::enable_if_t<cpp::is_array_v<U>, int> = 0>
+  LIBC_INLINE constexpr cpp::remove_extent_t<U> &operator[](size_t i) const {
+    state.assert_checked();
+    return ptr[i];
+  }
+
+  /// Moves the owned pointer into a plain cpp::unique_ptr, leaving this
+  /// CheckedPtr null. Asserts that the pointer has been checked.
+  [[nodiscard]] LIBC_INLINE constexpr PtrType take_unique() {
+    state.assert_checked();
+    return cpp::move(ptr);
+  }
+};
+
+/// Allocates and constructs an object of type T from `args`. Returns a null
+/// CheckedPtr if the allocation fails.
+template <typename T, typename... Args,
+          cpp::enable_if_t<!cpp::is_array_v<T>, int> = 0>
+LIBC_INLINE CheckedPtr<T> make_unique(Args &&...args) {
+  // There is no need to inspect `ac`: the AllocChecker operator new is
+  // noexcept, so on failure no object is constructed and the new-expression
+  // evaluates to nullptr, which the caller is then forced to check for.
+  AllocChecker ac;
+  return CheckedPtr<T>(new (ac) T(cpp::forward<Args>(args)...));
+}
+
+/// Allocates an array of `n` value-initialized elements. T must be an array of
+/// unknown bound (e.g. `make_unique<int[]>(n)`). Returns a null CheckedPtr if
+/// the allocation fails. Note some allocators return a nullptr for zero-sized
+/// allocations (n=0), which makes it impossible to identify an allocation
+/// failure in this case.
+template <
+    typename T,
+    cpp::enable_if_t<cpp::is_same_v<T, cpp::remove_extent_t<T>[]>, int> = 0>
+LIBC_INLINE CheckedPtr<T> make_unique(size_t n) {
+  using Elem = cpp::remove_extent_t<T>;
+  // See the non-array overload for why `ac` is not inspected.
+  AllocChecker ac;
+  return CheckedPtr<T>(new (ac) Elem[n]());
+}
+
+} // namespace LIBC_NAMESPACE_DECL
+
+#endif // LLVM_LIBC_SRC___SUPPORT_CHECKED_PTR_H
diff --git a/libc/test/src/__support/CMakeLists.txt b/libc/test/src/__support/CMakeLists.txt
index 3f52faa8061087..4031c1d46dd227 100644
--- a/libc/test/src/__support/CMakeLists.txt
+++ b/libc/test/src/__support/CMakeLists.txt
@@ -122,6 +122,20 @@ add_libc_test(
     libc.src.__support.blockstore
 )
 
+add_libc_test(
+  checked_ptr_test
+  SUITE
+    libc-support-tests
+  SRCS
+    checked_ptr_test.cpp
+  DEPENDS
+    libc.src.__support.alloc_checker
+    libc.src.__support.checked_ptr
+    libc.src.__support.CPP.type_traits
+    libc.src.__support.CPP.unique_ptr
+    libc.src.__support.CPP.utility
+)
+
 add_libc_test(
   endian_internal_test
   SUITE
diff --git a/libc/test/src/__support/checked_ptr_test.cpp b/libc/test/src/__support/checked_ptr_test.cpp
new file mode 100644
index 00000000000000..80bb31adbfcd3f
--- /dev/null
+++ b/libc/test/src/__support/checked_ptr_test.cpp
@@ -0,0 +1,297 @@
+//===----------------------------------------------------------------------===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+///
+/// \file
+/// Unittests for CheckedPtr and make_unique.
+///
+//===----------------------------------------------------------------------===//
+
+#include "src/__support/CPP/type_traits.h"
+#include "src/__support/CPP/unique_ptr.h"
+#include "src/__support/CPP/utility.h"
+#include "src/__support/alloc-checker.h"
+#include "src/__support/checked_ptr.h"
+#include "test/UnitTest/Test.h"
+
+using LIBC_NAMESPACE::AllocChecker;
+using LIBC_NAMESPACE::CheckedPtr;
+using LIBC_NAMESPACE::make_unique;
+using LIBC_NAMESPACE::cpp::is_constructible_v;
+using LIBC_NAMESPACE::cpp::move;
+using LIBC_NAMESPACE::cpp::unique_ptr;
+
+// Verify compile-time properties:
+// - Non-copyable and non-default-constructible.
+// - Explicit conversion to bool is only allowed on non-const lvalues/rvalues.
+static_assert(!is_constructible_v<CheckedPtr<int>>);
+static_assert(!is_constructible_v<CheckedPtr<int>, const CheckedPtr<int> &>);
+static_assert(is_constructible_v<bool, CheckedPtr<int> &>);
+static_assert(!is_constructible_v<bool, const CheckedPtr<int> &>);
+static_assert(is_constructible_v<bool, CheckedPtr<int[]> &>);
+static_assert(!is_constructible_v<bool, const CheckedPtr<int[]> &>);
+
+struct DestructTracker {
+  int *destruct_count = nullptr;
+  DestructTracker() = default;
+  DestructTracker(int *count) : destruct_count(count) {}
+  ~DestructTracker() {
+    if (destruct_count)
+      (*destruct_count)++;
+  }
+};
+
+struct Foo {
+  int val;
+  Foo(int v) : val(v) {}
+};
+
+struct Base {
+  int val;
+  virtual ~Base() = default;
+  Base(int v) : val(v) {}
+};
+
+struct Derived : public Base {
+  Derived(int v) : Base(v) {}
+};
+
+struct CustomDeleter {
+  int *count;
+  void operator()(int *p) const {
+    (*count)++;
+    delete p;
+  }
+};
+
+struct CustomArrayDeleter {
+  int *count;
+  void operator()(int *p) const {
+    if (count)
+      (*count)++;
+    delete[] p;
+  }
+};
+
+TEST(LlvmLibcCheckedPtrTest, BasicMakeUniqueAndAccess) {
+  int destruct_count = 0;
+  {
+    auto ptr = make_unique<DestructTracker>(&destruct_count);
+    ASSERT_TRUE(static_cast<bool>(ptr));
+
+    // Once checked, access via const reference is valid and safe.
+    const auto &cref = ptr;
+    ASSERT_EQ(*cref->destruct_count, 0);
+    ASSERT_EQ(*(*cref).destruct_count, 0);
+  }
+  ASSERT_EQ(destruct_count, 1);
+}
+
+TEST(LlvmLibcCheckedPtrTest, NullptrCheck) {
+  CheckedPtr<int> ptr(nullptr);
+  ASSERT_FALSE(static_cast<bool>(ptr));
+}
+
+TEST(LlvmLibcCheckedPtrTest, MoveRequiresRecheck) {
+  int destruct_count = 0;
+  {
+    auto ptr1 = make_unique<DestructTracker>(&destruct_count);
+    ASSERT_TRUE(static_cast<bool>(ptr1));
+
+    // Even though ptr1 was checked, moving into ptr2 requires checking ptr2.
+    CheckedPtr<DestructTracker> ptr2(move(ptr1));
+    ASSERT_TRUE(static_cast<bool>(ptr2));
+    ASSERT_EQ(*ptr2->destruct_count, 0);
+  }
+  ASSERT_EQ(destruct_count, 1);
+}
+
+TEST(LlvmLibcCheckedPtrTest, MoveUncheckedThenCheck) {
+  int destruct_count = 0;
+  {
+    auto ptr1 = make_unique<DestructTracker>(&destruct_count);
+    // Move before checking: ptr1 is disarmed; ptr2 must be checked.
+    CheckedPtr<DestructTracker> ptr2(move(ptr1));
+    ASSERT_TRUE(static_cast<bool>(ptr2));
+    ASSERT_EQ(*ptr2->destruct_count, 0);
+  }
+  ASSERT_EQ(destruct_count, 1);
+}
+
+TEST(LlvmLibcCheckedPtrTest, MoveAssignment) {
+  int destruct_count1 = 0;
+  int destruct_count2 = 0;
+  {
+    auto ptr1 = make_unique<DestructTracker>(&destruct_count1);
+    auto ptr2 = make_unique<DestructTracker>(&destruct_count2);
+    ASSERT_TRUE(static_cast<bool>(ptr1));
+    ASSERT_TRUE(static_cast<bool>(ptr2));
+
+    ptr2 = move(ptr1);
+    ASSERT_EQ(destruct_count1, 0);
+    ASSERT_EQ(destruct_count2, 1);
+    ASSERT_TRUE(static_cast<bool>(ptr2));
+  }
+  ASSERT_EQ(destruct_count1, 1);
+}
+
+TEST(LlvmLibcCheckedPtrTest, Conversions) {
+  auto derived = make_unique<Derived>(100);
+  CheckedPtr<Base> base(move(derived));
+  ASSERT_TRUE(static_cast<bool>(base));
+  ASSERT_EQ(base->val, 100);
+
+  auto mut_int = make_unique<int>(42);
+  CheckedPtr<const int> const_int(move(mut_int));
+  ASSERT_TRUE(static_cast<bool>(const_int));
+  ASSERT_EQ(*const_int, 42);
+}
+
+TEST(LlvmLibcCheckedPtrTest, Array) {
+  int destruct_count = 0;
+  {
+    auto ptr = make_unique<DestructTracker[]>(3);
+    ASSERT_TRUE(static_cast<bool>(ptr));
+    ptr[0].destruct_count = &destruct_count;
+    ptr[1].destruct_count = &destruct_count;
+    ptr[2].destruct_count = &destruct_count;
+    ASSERT_EQ(destruct_count, 0);
+  }
+  ASSERT_EQ(destruct_count, 3);
+}
+
+TEST(LlvmLibcCheckedPtrTest, CustomDeleters) {
+  int obj_deletes = 0;
+  {
+    AllocChecker ac;
+    CheckedPtr<int, CustomDeleter> ptr(new (ac) int(7),
+                                       CustomDeleter{&obj_deletes});
+    ASSERT_TRUE(static_cast<bool>(ptr));
+    ASSERT_EQ(*ptr, 7);
+    ASSERT_EQ(obj_deletes, 0);
+  }
+  ASSERT_EQ(obj_deletes, 1);
+
+  int arr_deletes = 0;
+  {
+    AllocChecker ac;
+    CheckedPtr<int[], CustomArrayDeleter> arr(new (ac) int[2],
+                                              CustomArrayDeleter{&arr_deletes});
+    ASSERT_TRUE(static_cast<bool>(arr));
+    arr[0] = 10;
+    arr[1] = 20;
+    ASSERT_EQ(arr[0] + arr[1], 30);
+    ASSERT_EQ(arr_deletes, 0);
+  }
+  ASSERT_EQ(arr_deletes, 1);
+}
+
+TEST(LlvmLibcCheckedPtrTest, SelfSwap) {
+  auto ptr = make_unique<int>(1);
+  ASSERT_TRUE(static_cast<bool>(ptr));
+  // Self-move-assignment (which happens as a part of self-swap) must be a
+  // no-op.
+  LIBC_NAMESPACE::cpp::swap(ptr, ptr);
+  ASSERT_TRUE(static_cast<bool>(ptr));
+  ASSERT_EQ(*ptr, 1);
+}
+
+struct VoidDeleter {
+  void operator()(void *) const {}
+};
+
+TEST(LlvmLibcCheckedPtrTest, Void) {
+  CheckedPtr<void, VoidDeleter> ptr(nullptr);
+  ASSERT_FALSE(static_cast<bool>(ptr));
+}
+
+TEST(LlvmLibcCheckedPtrTest, TakeUnique) {
+  int destruct_count = 0;
+  unique_ptr<DestructTracker> owner;
+  {
+    auto ptr = make_unique<DestructTracker>(&destruct_count);
+    ASSERT_TRUE(static_cast<bool>(ptr));
+    owner = ptr.take_unique();
+    // `ptr` is now null and still checked, so destroying it must neither delete
+    // the object nor assert.
+  }
+  ASSERT_EQ(destruct_count, 0);
+  ASSERT_TRUE(static_cast<bool>(owner));
+  owner.reset();
+  ASSERT_EQ(destruct_count, 1);
+}
+
+#if !defined(NDEBUG) && LIBC_TEST_SUBPROCESS_TESTS
+
+#if defined(LIBC_FULL_BUILD) && !defined(LIBC_COPT_USE_C_ASSERT)
+#define EXPECT_ASSERT_FAILS(stmt) EXPECT_EXITS(stmt, 255)
+#else
+#define EXPECT_ASSERT_FAILS(stmt) EXPECT_DEATH(stmt, WITH_SIGNAL(-1))
+#endif
+
+TEST(LlvmLibcCheckedPtrDeathTest, UncheckedDestructionDies) {
+  EXPECT_ASSERT_FAILS([] {
+    auto ptr = make_unique<int>(42);
+    (void)ptr;
+  });
+}
+
+TEST(LlvmLibcCheckedPtrDeathTest, MoveFromCheckedRequiresRecheckOrDies) {
+  EXPECT_ASSERT_FAILS([] {
+    auto ptr1 = make_unique<int>(42);
+    (void)static_cast<bool>(ptr1);
+    CheckedPtr<int> ptr2(move(ptr1));
+    (void)*ptr2;
+  });
+}
+
+// In the tests below, the pointer is checked after the operation under test, so
+// that its destruction cannot be the thing that triggers the assertion.
+
+TEST(LlvmLibcCheckedPtrDeathTest, UncheckedDereferenceDies) {
+  EXPECT_ASSERT_FAILS([] {
+    auto ptr = make_unique<int>(42);
+    (void)*ptr;
+    (void)static_cast<bool>(ptr);
+  });
+}
+
+TEST(LlvmLibcCheckedPtrDeathTest, UncheckedArrowDies) {
+  EXPECT_ASSERT_FAILS([] {
+    auto ptr = make_unique<Foo>(42);
+    (void)ptr->val;
+    (void)static_cast<bool>(ptr);
+  });
+}
+
+TEST(LlvmLibcCheckedPtrDeathTest, UncheckedArraySubscriptDies) {
+  EXPECT_ASSERT_FAILS([] {
+    auto ptr = make_unique<int[]>(3);
+    (void)ptr[0];
+    (void)static_cast<bool>(ptr);
+  });
+}
+
+TEST(LlvmLibcCheckedPtrDeathTest, OverwritingUncheckedViaMoveAssignmentDies) {
+  EXPECT_ASSERT_FAILS([] {
+    auto ptr1 = make_unique<int>(1);
+    auto ptr2 = make_unique<int>(2);
+    (void)static_cast<bool>(ptr2);
+    ptr1 = move(ptr2);
+    (void)static_cast<bool>(ptr1);
+  });
+}
+
+TEST(LlvmLibcCheckedPtrDeathTest, UncheckedTakeUniqueDies) {
+  EXPECT_ASSERT_FAILS([] {
+    auto ptr = make_unique<int>(42);
+    (void)ptr.take_unique();
+    (void)static_cast<bool>(ptr);
+  });
+}
+
+#endif // !defined(NDEBUG) && LIBC_TEST_SUBPROCESS_TESTS

>From b2f23cf17f9b5e20c80416a36ae32fc7d0425ad3 Mon Sep 17 00:00:00 2001
From: Pavel Labath <pavel at labath.sk>
Date: Mon, 26 Oct 2026 09:50:33 +0000
Subject: [PATCH 2/2] delete array make_unique delete copy ops s/ASSERT/EXPECT

---
 libc/src/__support/checked_ptr.h             | 10 +++
 libc/test/src/__support/checked_ptr_test.cpp | 64 ++++++++++----------
 2 files changed, 42 insertions(+), 32 deletions(-)

diff --git a/libc/src/__support/checked_ptr.h b/libc/src/__support/checked_ptr.h
index 3597ca7b05e3e0..0fdc7bc6a9e172 100644
--- a/libc/src/__support/checked_ptr.h
+++ b/libc/src/__support/checked_ptr.h
@@ -108,6 +108,9 @@ class [[nodiscard]] LIBC_TRIVIAL_ABI CheckedPtr {
   LIBC_INLINE constexpr CheckedPtr(CheckedPtr &&other) = default;
   LIBC_INLINE constexpr CheckedPtr &operator=(CheckedPtr &&other) = default;
 
+  CheckedPtr(const CheckedPtr &) = delete;
+  CheckedPtr &operator=(const CheckedPtr &) = delete;
+
   // Conversion move constructor (available iff the underlying cpp::unique_ptr
   // conversion is)
   template <typename U, typename E,
@@ -195,6 +198,13 @@ LIBC_INLINE CheckedPtr<T> make_unique(size_t n) {
   return CheckedPtr<T>(new (ac) Elem[n]());
 }
 
+/// Arrays of known bound (e.g. `make_unique<int[4]>()`) are not supported.
+template <typename T, typename... Args,
+          cpp::enable_if_t<cpp::is_array_v<T> &&
+                               !cpp::is_same_v<T, cpp::remove_extent_t<T>[]>,
+                           int> = 0>
+void make_unique(Args &&...) = delete;
+
 } // namespace LIBC_NAMESPACE_DECL
 
 #endif // LLVM_LIBC_SRC___SUPPORT_CHECKED_PTR_H
diff --git a/libc/test/src/__support/checked_ptr_test.cpp b/libc/test/src/__support/checked_ptr_test.cpp
index 80bb31adbfcd3f..700dc090dce93e 100644
--- a/libc/test/src/__support/checked_ptr_test.cpp
+++ b/libc/test/src/__support/checked_ptr_test.cpp
@@ -85,29 +85,29 @@ TEST(LlvmLibcCheckedPtrTest, BasicMakeUniqueAndAccess) {
 
     // Once checked, access via const reference is valid and safe.
     const auto &cref = ptr;
-    ASSERT_EQ(*cref->destruct_count, 0);
-    ASSERT_EQ(*(*cref).destruct_count, 0);
+    EXPECT_EQ(*cref->destruct_count, 0);
+    EXPECT_EQ(*(*cref).destruct_count, 0);
   }
-  ASSERT_EQ(destruct_count, 1);
+  EXPECT_EQ(destruct_count, 1);
 }
 
 TEST(LlvmLibcCheckedPtrTest, NullptrCheck) {
   CheckedPtr<int> ptr(nullptr);
-  ASSERT_FALSE(static_cast<bool>(ptr));
+  EXPECT_FALSE(static_cast<bool>(ptr));
 }
 
 TEST(LlvmLibcCheckedPtrTest, MoveRequiresRecheck) {
   int destruct_count = 0;
   {
     auto ptr1 = make_unique<DestructTracker>(&destruct_count);
-    ASSERT_TRUE(static_cast<bool>(ptr1));
+    EXPECT_TRUE(static_cast<bool>(ptr1));
 
     // Even though ptr1 was checked, moving into ptr2 requires checking ptr2.
     CheckedPtr<DestructTracker> ptr2(move(ptr1));
     ASSERT_TRUE(static_cast<bool>(ptr2));
-    ASSERT_EQ(*ptr2->destruct_count, 0);
+    EXPECT_EQ(*ptr2->destruct_count, 0);
   }
-  ASSERT_EQ(destruct_count, 1);
+  EXPECT_EQ(destruct_count, 1);
 }
 
 TEST(LlvmLibcCheckedPtrTest, MoveUncheckedThenCheck) {
@@ -117,9 +117,9 @@ TEST(LlvmLibcCheckedPtrTest, MoveUncheckedThenCheck) {
     // Move before checking: ptr1 is disarmed; ptr2 must be checked.
     CheckedPtr<DestructTracker> ptr2(move(ptr1));
     ASSERT_TRUE(static_cast<bool>(ptr2));
-    ASSERT_EQ(*ptr2->destruct_count, 0);
+    EXPECT_EQ(*ptr2->destruct_count, 0);
   }
-  ASSERT_EQ(destruct_count, 1);
+  EXPECT_EQ(destruct_count, 1);
 }
 
 TEST(LlvmLibcCheckedPtrTest, MoveAssignment) {
@@ -127,28 +127,28 @@ TEST(LlvmLibcCheckedPtrTest, MoveAssignment) {
   int destruct_count2 = 0;
   {
     auto ptr1 = make_unique<DestructTracker>(&destruct_count1);
+    EXPECT_TRUE(static_cast<bool>(ptr1));
     auto ptr2 = make_unique<DestructTracker>(&destruct_count2);
-    ASSERT_TRUE(static_cast<bool>(ptr1));
-    ASSERT_TRUE(static_cast<bool>(ptr2));
+    EXPECT_TRUE(static_cast<bool>(ptr2));
 
     ptr2 = move(ptr1);
-    ASSERT_EQ(destruct_count1, 0);
-    ASSERT_EQ(destruct_count2, 1);
-    ASSERT_TRUE(static_cast<bool>(ptr2));
+    EXPECT_EQ(destruct_count1, 0);
+    EXPECT_EQ(destruct_count2, 1);
+    EXPECT_TRUE(static_cast<bool>(ptr2));
   }
-  ASSERT_EQ(destruct_count1, 1);
+  EXPECT_EQ(destruct_count1, 1);
 }
 
 TEST(LlvmLibcCheckedPtrTest, Conversions) {
   auto derived = make_unique<Derived>(100);
   CheckedPtr<Base> base(move(derived));
   ASSERT_TRUE(static_cast<bool>(base));
-  ASSERT_EQ(base->val, 100);
+  EXPECT_EQ(base->val, 100);
 
   auto mut_int = make_unique<int>(42);
   CheckedPtr<const int> const_int(move(mut_int));
   ASSERT_TRUE(static_cast<bool>(const_int));
-  ASSERT_EQ(*const_int, 42);
+  EXPECT_EQ(*const_int, 42);
 }
 
 TEST(LlvmLibcCheckedPtrTest, Array) {
@@ -159,9 +159,9 @@ TEST(LlvmLibcCheckedPtrTest, Array) {
     ptr[0].destruct_count = &destruct_count;
     ptr[1].destruct_count = &destruct_count;
     ptr[2].destruct_count = &destruct_count;
-    ASSERT_EQ(destruct_count, 0);
+    EXPECT_EQ(destruct_count, 0);
   }
-  ASSERT_EQ(destruct_count, 3);
+  EXPECT_EQ(destruct_count, 3);
 }
 
 TEST(LlvmLibcCheckedPtrTest, CustomDeleters) {
@@ -171,10 +171,10 @@ TEST(LlvmLibcCheckedPtrTest, CustomDeleters) {
     CheckedPtr<int, CustomDeleter> ptr(new (ac) int(7),
                                        CustomDeleter{&obj_deletes});
     ASSERT_TRUE(static_cast<bool>(ptr));
-    ASSERT_EQ(*ptr, 7);
-    ASSERT_EQ(obj_deletes, 0);
+    EXPECT_EQ(*ptr, 7);
+    EXPECT_EQ(obj_deletes, 0);
   }
-  ASSERT_EQ(obj_deletes, 1);
+  EXPECT_EQ(obj_deletes, 1);
 
   int arr_deletes = 0;
   {
@@ -184,20 +184,20 @@ TEST(LlvmLibcCheckedPtrTest, CustomDeleters) {
     ASSERT_TRUE(static_cast<bool>(arr));
     arr[0] = 10;
     arr[1] = 20;
-    ASSERT_EQ(arr[0] + arr[1], 30);
-    ASSERT_EQ(arr_deletes, 0);
+    EXPECT_EQ(arr[0] + arr[1], 30);
+    EXPECT_EQ(arr_deletes, 0);
   }
-  ASSERT_EQ(arr_deletes, 1);
+  EXPECT_EQ(arr_deletes, 1);
 }
 
 TEST(LlvmLibcCheckedPtrTest, SelfSwap) {
   auto ptr = make_unique<int>(1);
-  ASSERT_TRUE(static_cast<bool>(ptr));
+  EXPECT_TRUE(static_cast<bool>(ptr));
   // Self-move-assignment (which happens as a part of self-swap) must be a
   // no-op.
   LIBC_NAMESPACE::cpp::swap(ptr, ptr);
   ASSERT_TRUE(static_cast<bool>(ptr));
-  ASSERT_EQ(*ptr, 1);
+  EXPECT_EQ(*ptr, 1);
 }
 
 struct VoidDeleter {
@@ -206,7 +206,7 @@ struct VoidDeleter {
 
 TEST(LlvmLibcCheckedPtrTest, Void) {
   CheckedPtr<void, VoidDeleter> ptr(nullptr);
-  ASSERT_FALSE(static_cast<bool>(ptr));
+  EXPECT_FALSE(static_cast<bool>(ptr));
 }
 
 TEST(LlvmLibcCheckedPtrTest, TakeUnique) {
@@ -214,15 +214,15 @@ TEST(LlvmLibcCheckedPtrTest, TakeUnique) {
   unique_ptr<DestructTracker> owner;
   {
     auto ptr = make_unique<DestructTracker>(&destruct_count);
-    ASSERT_TRUE(static_cast<bool>(ptr));
+    EXPECT_TRUE(static_cast<bool>(ptr));
     owner = ptr.take_unique();
     // `ptr` is now null and still checked, so destroying it must neither delete
     // the object nor assert.
   }
-  ASSERT_EQ(destruct_count, 0);
-  ASSERT_TRUE(static_cast<bool>(owner));
+  EXPECT_EQ(destruct_count, 0);
+  EXPECT_TRUE(static_cast<bool>(owner));
   owner.reset();
-  ASSERT_EQ(destruct_count, 1);
+  EXPECT_EQ(destruct_count, 1);
 }
 
 #if !defined(NDEBUG) && LIBC_TEST_SUBPROCESS_TESTS



More information about the libc-commits mailing list