[libc-commits] [libc] [libc] Implement CheckedPtr utility (PR #234646)
Jeff Bailey via libc-commits
libc-commits at lists.llvm.org
Fri Oct 23 03:24:48 PDT 2026
================
@@ -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;
----------------
kaladron wrote:
We could add:
```c++
LIBC_ASSERT(static_cast<bool>(ptr));
```
Here and the next few functions to add a layer of protection for us.
https://github.com/llvm/llvm-project/pull/234646
More information about the libc-commits
mailing list