[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:07:23 PDT 2026
https://github.com/labath updated https://github.com/llvm/llvm-project/pull/235658
>From 3fb44a86bab97880d004f3effa9bc6c186d5638b 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 | 24 +++++++++++++++++++
4 files changed, 36 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..56bdd77eb1e7ac 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,26 @@ 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