[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