[libc-commits] [libc] [libc] Assert non-nullness in cpp::unique_ptr dereference operators (PR #235658)

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


https://github.com/labath created https://github.com/llvm/llvm-project/pull/235658

Add LIBC_ASSERTs to operator*, operator-> and (for the array version) operator[]. Dereferencing an empty unique_ptr is UB, so we might as well catch it in debug builds.

The death test is a bit awkward because LIBC_ASSERT fails differently depending on the build mode. In full builds it exits with 0xFF, while in overlay builds (or with LIBC_COPT_USE_C_ASSERT) it calls the system assert, which raises SIGABRT. I've added a small helper macro to the test to handle both cases.

Assisted-by: Gemini

>From 55c22ea4151a81b72f7752adab7232655be6f655 Mon Sep 17 00:00:00 2001
From: Pavel Labath <pavel at labath.sk>
Date: Mon, 26 Oct 2026 09:02:57 +0000
Subject: [PATCH] [libc] Assert non-nullness in cpp::unique_ptr dereference
 operators

Add LIBC_ASSERTs to operator*, operator-> and (for the array version)
operator[]. Dereferencing an empty unique_ptr is UB, so we might as well
catch it in debug builds.

The death test is a bit awkward because LIBC_ASSERT fails differently
depending on the build mode. In full builds it exits with 0xFF, while in
overlay builds (or with LIBC_COPT_USE_C_ASSERT) it calls the system
assert, which raises SIGABRT. I've added a small helper macro to the test
to handle both cases.

Assisted-by: Gemini
---
 libc/src/__support/CPP/CMakeLists.txt         |  1 +
 libc/src/__support/CPP/unique_ptr.h           | 12 +++++++--
 libc/test/src/__support/CPP/CMakeLists.txt    |  1 +
 .../src/__support/CPP/unique_ptr_test.cpp     | 25 +++++++++++++++++++
 4 files changed, 37 insertions(+), 2 deletions(-)

diff --git a/libc/src/__support/CPP/CMakeLists.txt b/libc/src/__support/CPP/CMakeLists.txt
index 0a793e897fb864..73aec294df6922 100644
--- a/libc/src/__support/CPP/CMakeLists.txt
+++ b/libc/src/__support/CPP/CMakeLists.txt
@@ -228,6 +228,7 @@ add_header_library(
     .type_traits
     .utility
     libc.hdr.types.size_t
+    libc.src.__support.libc_assert
     libc.src.__support.macros.attributes
     libc.src.__support.macros.config
 )
diff --git a/libc/src/__support/CPP/unique_ptr.h b/libc/src/__support/CPP/unique_ptr.h
index ebba21299f4b80..a8cf11d2ae8a72 100644
--- a/libc/src/__support/CPP/unique_ptr.h
+++ b/libc/src/__support/CPP/unique_ptr.h
@@ -18,6 +18,7 @@
 #include "src/__support/CPP/new.h"
 #include "src/__support/CPP/type_traits.h"
 #include "src/__support/CPP/utility.h"
+#include "src/__support/libc_assert.h"
 #include "src/__support/macros/attributes.h"
 #include "src/__support/macros/config.h"
 
@@ -134,9 +135,13 @@ class LIBC_TRIVIAL_ABI unique_ptr {
 
   LIBC_INLINE constexpr typename add_lvalue_reference<T>::type
   operator*() const {
+    LIBC_ASSERT(ptr != nullptr);
     return *ptr;
   }
-  LIBC_INLINE constexpr pointer operator->() const { return ptr; }
+  LIBC_INLINE constexpr pointer operator->() const {
+    LIBC_ASSERT(ptr != nullptr);
+    return ptr;
+  }
 };
 
 /// A smart pointer that owns and manages an array of objects through a pointer.
@@ -199,7 +204,10 @@ class LIBC_TRIVIAL_ABI unique_ptr<T[], Deleter> {
       deleter(old_ptr);
   }
 
-  LIBC_INLINE constexpr T &operator[](size_t i) const { return ptr[i]; }
+  LIBC_INLINE constexpr T &operator[](size_t i) const {
+    LIBC_ASSERT(ptr != nullptr);
+    return ptr[i];
+  }
 };
 
 } // namespace cpp
diff --git a/libc/test/src/__support/CPP/CMakeLists.txt b/libc/test/src/__support/CPP/CMakeLists.txt
index ac6699c3e65b7f..5b8b1c60965550 100644
--- a/libc/test/src/__support/CPP/CMakeLists.txt
+++ b/libc/test/src/__support/CPP/CMakeLists.txt
@@ -162,6 +162,7 @@ add_libc_test(
   SRCS
     unique_ptr_test.cpp
   DEPENDS
+    libc.hdr.signal_macros
     libc.src.__support.CPP.unique_ptr
     libc.src.__support.CPP.utility
 )
diff --git a/libc/test/src/__support/CPP/unique_ptr_test.cpp b/libc/test/src/__support/CPP/unique_ptr_test.cpp
index 1064ae36de8272..51a21b740fccb6 100644
--- a/libc/test/src/__support/CPP/unique_ptr_test.cpp
+++ b/libc/test/src/__support/CPP/unique_ptr_test.cpp
@@ -11,6 +11,7 @@
 ///
 //===----------------------------------------------------------------------===//
 
+#include "hdr/signal_macros.h"
 #include "src/__support/CPP/unique_ptr.h"
 #include "src/__support/CPP/utility.h"
 #include "src/__support/alloc-checker.h"
@@ -242,3 +243,27 @@ TEST(LlvmLibcUniquePtrTest, BaseDerivedConversion) {
   ASSERT_TRUE(static_cast<bool>(base_ptr));
   ASSERT_EQ(base_ptr->val, 100);
 }
+
+
+#ifndef NDEBUG
+// In full builds, LIBC_ASSERT exits with code 0xFF. Otherwise, it uses the
+// system assert, which raises SIGABRT.
+#if defined(LIBC_FULL_BUILD) && !defined(LIBC_COPT_USE_C_ASSERT)
+#define EXPECT_ASSERT_FAILURE(FUNC) EXPECT_EXITS(FUNC, 0xFF)
+#else
+#define EXPECT_ASSERT_FAILURE(FUNC) EXPECT_DEATH(FUNC, WITH_SIGNAL(SIGABRT))
+#endif
+
+// Test that dereferencing a null unique_ptr asserts.
+TEST(LlvmLibcUniquePtrTest, NullDereferenceDeath) {
+  struct Foo {
+    int val;
+  };
+  unique_ptr<Foo> ptr;
+  EXPECT_ASSERT_FAILURE([&] { (void)*ptr; });
+  EXPECT_ASSERT_FAILURE([&] { (void)ptr->val; });
+
+  unique_ptr<int[]> arr;
+  EXPECT_ASSERT_FAILURE([&] { (void)arr[0]; });
+}
+#endif // NDEBUG



More information about the libc-commits mailing list