[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:11 PDT 2026


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

>From bc7aa1f6dcee303f17aaef4723cb970e81a89254 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/3] [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 765eec6b421780..947504ea43987b 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 591381c81e4a43328ddee58c0a0ba0155b9f8530 Mon Sep 17 00:00:00 2001
From: Pavel Labath <pavel at labath.sk>
Date: Thu, 22 Oct 2026 15:02:44 +0000
Subject: [PATCH 2/3] [libc] Use CheckedPtr to manage regex memory

This patch replaces the manual AllocChecker + new/delete calls (and the
scope guards protecting them) in regcomp and ExprPool with make_unique
and cpp::unique_ptr, which is what the TODOs in that code were waiting
for.

In regcomp, the RegexInternal object is now freed automatically on the
error paths. Since regex_t is a C struct, it still holds the object
through a raw pointer, which regfree deletes.

In ExprPool, the manually owned spans become cpp::unique_ptr arrays
(with an explicit capacity) and the blocks are owned by unique_ptrs, so
the destructor goes away. The explicit zeroing of the hash table is gone
too, as make_unique value-initializes the array. I'm also deleting the
copy operations: the old class had an implicit (shallow) copy
constructor, which would have caused a double free if anyone used it.

Assisted-by: Gemini
---
 libc/src/__support/regex/CMakeLists.txt      |   6 +-
 libc/src/__support/regex/regex_expr_pool.cpp | 108 +++++++------------
 libc/src/__support/regex/regex_expr_pool.h   |  35 +++---
 libc/src/regex/CMakeLists.txt                |   4 +-
 libc/src/regex/regcomp.cpp                   |  18 ++--
 libc/src/regex/regfree.cpp                   |   4 +-
 6 files changed, 69 insertions(+), 106 deletions(-)

diff --git a/libc/src/__support/regex/CMakeLists.txt b/libc/src/__support/regex/CMakeLists.txt
index 95857681ed51d4..e65ee9237d9d41 100644
--- a/libc/src/__support/regex/CMakeLists.txt
+++ b/libc/src/__support/regex/CMakeLists.txt
@@ -9,18 +9,16 @@ add_object_library(
     libc.hdr.limits_macros
     libc.hdr.regex_macros
     libc.hdr.stdint_proxy
-    libc.src.__support.alloc_checker
+    libc.src.__support.checked_ptr
     libc.src.__support.CPP.array
     libc.src.__support.CPP.bitset
-    libc.src.__support.CPP.new
-    libc.src.__support.CPP.scope
     libc.src.__support.CPP.span
+    libc.src.__support.CPP.unique_ptr
     libc.src.__support.CPP.utility
     libc.src.__support.error_or
     libc.src.__support.hash
     libc.src.__support.libc_assert
     libc.src.__support.macros.config
-    libc.src.string.memory_utils.inline_memset
 )
 
 add_header_library(
diff --git a/libc/src/__support/regex/regex_expr_pool.cpp b/libc/src/__support/regex/regex_expr_pool.cpp
index 6c82b62b7def90..812d55ef0daf97 100644
--- a/libc/src/__support/regex/regex_expr_pool.cpp
+++ b/libc/src/__support/regex/regex_expr_pool.cpp
@@ -13,13 +13,10 @@
 
 #include "src/__support/regex/regex_expr_pool.h"
 #include "hdr/regex_macros.h"
-#include "src/__support/CPP/new.h"
-#include "src/__support/CPP/scope.h"
 #include "src/__support/CPP/utility.h"
-#include "src/__support/alloc-checker.h"
+#include "src/__support/checked_ptr.h"
 #include "src/__support/hash.h"
 #include "src/__support/macros/config.h"
-#include "src/string/memory_utils/inline_memset.h"
 
 namespace LIBC_NAMESPACE_DECL {
 namespace regex {
@@ -94,33 +91,17 @@ class BucketProber {
 
 } // namespace
 
-// TODO: When cpp::unique_ptr and cpp::make_unique are added to
-// src/__support/CPP/, replace the manual AllocChecker + new[]/delete[] calls in
-// ensure_initialized(), ~ExprPool(), allocate_block(), make_byte_set(), and
-// grow_hashtable() with cpp::make_unique<ExprId[]>(...) and
-// cpp::make_unique<Block>(), eliminating explicit zeroing via inline_memset and
-// the destructor cleanup loop.
-ExprPool::~ExprPool() {
-  delete[] byte_sets.data();
-  delete[] hashtable.data();
-  for (Block *blk : active_blocks())
-    delete blk;
-  delete[] blocks.data();
-}
-
 bool ExprPool::ensure_initialized() {
   if (block_count > 0)
     return true;
-  AllocChecker ac;
-  ExprId *raw_table = new (ac) ExprId[INITIAL_HASH_CAPACITY];
-  if (!ac)
+  auto table = make_unique<ExprId[]>(INITIAL_HASH_CAPACITY);
+  if (!table)
     return false;
-  cpp::scope_exit table_guard([raw_table] { delete[] raw_table; });
   if (!allocate_block())
     return false;
-  table_guard.release();
-  hashtable = cpp::span<ExprId>(raw_table, INITIAL_HASH_CAPACITY);
-  inline_memset(hashtable.data(), 0, hashtable.size_bytes());
+  // The table is value-initialized, so all buckets start out empty.
+  hashtable = table.take_unique();
+  hashtable_capacity = INITIAL_HASH_CAPACITY;
 
   [[maybe_unused]] auto empty_set_id = intern(Expr::make_empty_set());
   [[maybe_unused]] auto empty_str_id = intern(Expr::make_empty_str());
@@ -141,44 +122,38 @@ ExprId &ExprPool::find_bucket(cpp::span<ExprId> table, const Expr &e) const {
 }
 
 bool ExprPool::allocate_block() {
-  AllocChecker ac;
-  if (block_count == blocks.size()) {
-    size_t new_cap = blocks.empty() ? INITIAL_BLOCKS_CAPACITY
-                                    : blocks.size() * GROWTH_FACTOR;
-    Block **new_blocks = new (ac) Block *[new_cap];
-    if (!ac)
+  if (block_count == blocks_capacity) {
+    size_t new_cap = blocks_capacity == 0 ? INITIAL_BLOCKS_CAPACITY
+                                          : blocks_capacity * GROWTH_FACTOR;
+    auto new_blocks = make_unique<cpp::unique_ptr<Block>[]>(new_cap);
+    if (!new_blocks)
       return false;
-    Block **dst = new_blocks;
-    for (Block *blk : active_blocks()) {
-      *dst = blk;
-      ++dst;
-    }
-    delete[] blocks.data();
-    blocks = cpp::span<Block *>(new_blocks, new_cap);
+    for (size_t i = 0; i < block_count; ++i)
+      new_blocks[i] = cpp::move(blocks[i]);
+    blocks = new_blocks.take_unique();
+    blocks_capacity = new_cap;
   }
-  Block *blk = new (ac) Block();
-  if (!ac)
+  auto blk = make_unique<Block>();
+  if (!blk)
     return false;
-  blocks[block_count] = blk;
+  blocks[block_count] = blk.take_unique();
   ++block_count;
   return true;
 }
 
 bool ExprPool::grow_hashtable() {
-  size_t new_cap = hashtable.size() * GROWTH_FACTOR;
-  AllocChecker ac;
-  ExprId *raw_table = new (ac) ExprId[new_cap];
-  if (!ac)
+  size_t new_cap = hashtable_capacity * GROWTH_FACTOR;
+  auto new_table = make_unique<ExprId[]>(new_cap);
+  if (!new_table)
     return false;
-  cpp::span<ExprId> new_table(raw_table, new_cap);
-  inline_memset(new_table.data(), 0, new_table.size_bytes());
-
-  for (ExprId id : hashtable)
+  // Install the new (empty) table and rehash the existing entries into it.
+  cpp::unique_ptr<ExprId[]> old_table = cpp::move(hashtable);
+  cpp::span<ExprId> old_entries(old_table.get(), hashtable_capacity);
+  hashtable = new_table.take_unique();
+  hashtable_capacity = new_cap;
+  for (ExprId id : old_entries)
     if (id != INVALID_EXPR_ID)
-      find_bucket(new_table, get(id)) = id;
-
-  delete[] hashtable.data();
-  hashtable = new_table;
+      find_bucket(hashtable_span(), get(id)) = id;
   return true;
 }
 
@@ -188,7 +163,7 @@ ErrorOr<ExprId> ExprPool::intern(Expr e) {
 
   // Probe for an existing node with identical content.
   e.hash = hash_expr(e);
-  ExprId *bucket = &find_bucket(hashtable, e);
+  ExprId *bucket = &find_bucket(hashtable_span(), e);
   if (*bucket != INVALID_EXPR_ID)
     return *bucket;
 
@@ -199,7 +174,7 @@ ErrorOr<ExprId> ExprPool::intern(Expr e) {
   if (node_count >= max_hashtable_nodes()) {
     if (!grow_hashtable())
       return Error(REG_ESPACE);
-    bucket = &find_bucket(hashtable, e);
+    bucket = &find_bucket(hashtable_span(), e);
   }
 
   // Arena Allocation: If no matching node found, allocate a stable slot.
@@ -254,20 +229,17 @@ ErrorOr<ExprId> ExprPool::make_byte_set(const ByteSet &bs) {
     ++bs_id;
   }
 
-  if (byte_set_count == byte_sets.size()) {
-    size_t new_cap = byte_sets.empty() ? INITIAL_BYTE_SETS_CAPACITY
-                                       : byte_sets.size() * GROWTH_FACTOR;
-    AllocChecker ac;
-    ByteSet *new_sets = new (ac) ByteSet[new_cap];
-    if (!ac)
+  if (byte_set_count == byte_sets_capacity) {
+    size_t new_cap = byte_sets_capacity == 0
+                         ? INITIAL_BYTE_SETS_CAPACITY
+                         : byte_sets_capacity * GROWTH_FACTOR;
+    auto new_sets = make_unique<ByteSet[]>(new_cap);
+    if (!new_sets)
       return Error(REG_ESPACE);
-    ByteSet *dst = new_sets;
-    for (const ByteSet &existing : active_byte_sets()) {
-      *dst = existing;
-      ++dst;
-    }
-    delete[] byte_sets.data();
-    byte_sets = cpp::span<ByteSet>(new_sets, new_cap);
+    for (size_t i = 0; i < byte_set_count; ++i)
+      new_sets[i] = byte_sets[i];
+    byte_sets = new_sets.take_unique();
+    byte_sets_capacity = new_cap;
   }
 
   ErrorOr<ExprId> id = intern(Expr::make_byte_set(bs_id));
diff --git a/libc/src/__support/regex/regex_expr_pool.h b/libc/src/__support/regex/regex_expr_pool.h
index 746ad5d9789d9c..96eedd8e4b0015 100644
--- a/libc/src/__support/regex/regex_expr_pool.h
+++ b/libc/src/__support/regex/regex_expr_pool.h
@@ -16,6 +16,7 @@
 
 #include "src/__support/CPP/array.h"
 #include "src/__support/CPP/span.h"
+#include "src/__support/CPP/unique_ptr.h"
 #include "src/__support/error_or.h"
 #include "src/__support/libc_assert.h"
 #include "src/__support/macros/config.h"
@@ -65,36 +66,33 @@ class ExprPool {
   /// regular expressions.
   static constexpr size_t MAX_NODE_LIMIT = 10000;
 
-  // TODO: Once cpp::unique_ptr and AllocChecker-backed cpp::make_unique (and/or
-  // an owning unique_span) are available in src/__support/CPP/, replace raw
-  // Block* and span-backed manual new[]/delete[] ownership here so that Block
-  // lifecycles, directory growth, ByteSet growth, and hash-table growth are
-  // managed via RAII and ~ExprPool() can be defaulted.
-
-  /// Span of allocated Block* slots (capacity = blocks.size()).
-  cpp::span<Block *> blocks;
+  /// Directory of allocated blocks (capacity = blocks_capacity).
+  cpp::unique_ptr<cpp::unique_ptr<Block>[]> blocks;
+  size_t blocks_capacity = 0;
   size_t block_count = 0;
   /// Total number of nodes allocated across all blocks.
   size_t node_count = 0;
 
   /// Lazily allocated array of deduplicated 256-bit character sets.
-  cpp::span<ByteSet> byte_sets;
+  cpp::unique_ptr<ByteSet[]> byte_sets;
+  size_t byte_sets_capacity = 0;
   size_t byte_set_count = 0;
 
   /// Open-addressing hash table storing ExprIds (0 = empty bucket).
-  cpp::span<ExprId> hashtable;
+  cpp::unique_ptr<ExprId[]> hashtable;
+  size_t hashtable_capacity = 0;
 
-  cpp::span<Block *const> active_blocks() const {
-    return blocks.first(block_count);
+  cpp::span<ExprId> hashtable_span() const {
+    return {hashtable.get(), hashtable_capacity};
   }
 
   cpp::span<const ByteSet> active_byte_sets() const {
-    return byte_sets.first(byte_set_count);
+    return {byte_sets.get(), byte_set_count};
   }
 
   /// Maximum number of nodes the current hash table can hold before rehashing.
   size_t max_hashtable_nodes() const {
-    return static_cast<size_t>(static_cast<double>(hashtable.size()) *
+    return static_cast<size_t>(static_cast<double>(hashtable_capacity) *
                                MAX_LOAD_FACTOR);
   }
 
@@ -123,7 +121,8 @@ class ExprPool {
 
 public:
   constexpr ExprPool() = default;
-  ~ExprPool();
+  ExprPool(const ExprPool &) = delete;
+  ExprPool &operator=(const ExprPool &) = delete;
 
   /// Resolves an ExprId to a reference.
   const Expr &get(ExprId id) const { return slot_at(id); }
@@ -148,8 +147,10 @@ class ExprPool {
 
   /// Returns the current heap bytes allocated by this pool.
   size_t allocated_bytes() const {
-    return (block_count * sizeof(Block)) + blocks.size_bytes() +
-           hashtable.size_bytes() + byte_sets.size_bytes();
+    return (block_count * sizeof(Block)) +
+           (blocks_capacity * sizeof(cpp::unique_ptr<Block>)) +
+           (hashtable_capacity * sizeof(ExprId)) +
+           (byte_sets_capacity * sizeof(ByteSet));
   }
 
   // TODO: Use fluent interface (and_then, transform) for these factories once
diff --git a/libc/src/regex/CMakeLists.txt b/libc/src/regex/CMakeLists.txt
index 74859f7d52c2f3..f844cb88e79d3f 100644
--- a/libc/src/regex/CMakeLists.txt
+++ b/libc/src/regex/CMakeLists.txt
@@ -7,9 +7,7 @@ add_entrypoint_object(
   DEPENDS
     libc.hdr.regex_macros
     libc.hdr.types.regex_t
-    libc.src.__support.alloc_checker
-    libc.src.__support.CPP.new
-    libc.src.__support.CPP.scope
+    libc.src.__support.checked_ptr
     libc.src.__support.CPP.string_view
     libc.src.__support.error_or
     libc.src.__support.macros.null_check
diff --git a/libc/src/regex/regcomp.cpp b/libc/src/regex/regcomp.cpp
index 7bb362a592ea3a..1130605b834741 100644
--- a/libc/src/regex/regcomp.cpp
+++ b/libc/src/regex/regcomp.cpp
@@ -14,10 +14,8 @@
 #include "src/regex/regcomp.h"
 
 #include "hdr/regex_macros.h"
-#include "src/__support/CPP/new.h"
-#include "src/__support/CPP/scope.h"
 #include "src/__support/CPP/string_view.h"
-#include "src/__support/alloc-checker.h"
+#include "src/__support/checked_ptr.h"
 #include "src/__support/common.h"
 #include "src/__support/error_or.h"
 #include "src/__support/macros/config.h"
@@ -48,24 +46,20 @@ LLVM_LIBC_FUNCTION(int, regcomp,
       (cflags & ~(REG_EXTENDED | REG_NOSUB | REG_NEWLINE)) != 0)
     return REG_BADPAT;
 
-  // TODO: Once cpp::unique_ptr and AllocChecker-backed cpp::make_unique are
-  // available in src/__support/CPP/, manage RegexInternal via unique_ptr so
-  // error paths do not require a scope guard.
-  AllocChecker ac;
-  regex::RegexInternal *internal = new (ac) regex::RegexInternal();
-  if (!ac)
+  auto internal = make_unique<regex::RegexInternal>();
+  if (!internal)
     return REG_ESPACE;
-  cpp::scope_exit internal_guard([internal] { delete internal; });
 
   ErrorOr<regex::ExprId> root =
       regex::parse_ere(cpp::string_view(pattern), cflags, internal->pool);
   if (!root.has_value())
     return root.error();
 
-  internal_guard.release();
   internal->root = *root;
   internal->cflags = cflags;
-  preg->__internal = internal;
+  // regex_t is a C struct, so it owns the RegexInternal through a raw pointer.
+  // regfree() deletes it.
+  preg->__internal = internal.take_unique().release();
   return 0;
 }
 
diff --git a/libc/src/regex/regfree.cpp b/libc/src/regex/regfree.cpp
index f7550d53eeada2..e7608d9b3ff40a 100644
--- a/libc/src/regex/regfree.cpp
+++ b/libc/src/regex/regfree.cpp
@@ -23,8 +23,8 @@ namespace LIBC_NAMESPACE_DECL {
 
 LLVM_LIBC_FUNCTION(void, regfree, (regex_t * preg)) {
   LIBC_CRASH_ON_NULLPTR(preg);
-  // TODO: Once RegexInternal is managed via cpp::unique_ptr, reset the smart
-  // pointer here instead of calling delete manually.
+  // regcomp() hands over the ownership of the RegexInternal through a raw
+  // pointer, as regex_t is a C struct.
   delete static_cast<regex::RegexInternal *>(preg->__internal);
   preg->__internal = nullptr;
   preg->re_nsub = 0;

>From d429bb31bcba266533ebce3080e2f427552c4bb8 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 3/3] 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