[libcxx-commits] [libcxx] [libc++] Handle Clang function effect analysis in hardening assertions (PR #177447)

Louis Dionne via libcxx-commits libcxx-commits at lists.llvm.org
Fri Jan 23 07:25:51 PST 2026


https://github.com/ldionne updated https://github.com/llvm/llvm-project/pull/177447

>From f5d74052b3b1306e221de9c36fd6a8bcfa466a55 Mon Sep 17 00:00:00 2001
From: Louis Dionne <ldionne.2 at gmail.com>
Date: Thu, 22 Jan 2026 13:30:01 -0500
Subject: [PATCH 1/4] [libc++] Handle Clang function effect analysis in
 hardening assertions

This patch ensures that libc++ assertions are compatible with the function
effect analysis extension in Clang. The function effect analysis extension
allows marking functions with attributes like [[nonblocking]], and Clang
will issue a diagnostic if such a function calls another function that
doesn't satisfy the requirements of the attribute.

When an assertion fails, libc++ sometimes calls potentially blocking
functions (e.g. in the observe mode). However, this only happens when
a precondition has been violated. Hence, it is acceptable not to satisfy
these function effect attributes once the assertion is known to fail.

The alternative would be that libc++ functions with preconditions cannot
be called from [[nonblocking]] functions, since we might add an assertion
in the future that would not satisfy the attribute.

rdar://164189063
---
 libcxx/include/__assert                       | 10 ++++-
 .../clang/function-effect-analysis.sh.cpp     | 41 +++++++++++++++++++
 2 files changed, 49 insertions(+), 2 deletions(-)
 create mode 100644 libcxx/test/extensions/clang/function-effect-analysis.sh.cpp

diff --git a/libcxx/include/__assert b/libcxx/include/__assert
index a9451daf47f2f..b114aa8c12dbb 100644
--- a/libcxx/include/__assert
+++ b/libcxx/include/__assert
@@ -17,11 +17,17 @@
 #  pragma GCC system_header
 #endif
 
+// Note that we disable -Wfunction-effects inside _LIBCPP_ASSERT when the assertion fails since
+// function effect attributes don't provide any guarantees once a function is determined to be
+// out of contract.
 #define _LIBCPP_ASSERT(expression, message)                                                                            \
   (__builtin_expect(static_cast<bool>(expression), 1)                                                                  \
        ? (void)0                                                                                                       \
-       : _LIBCPP_ASSERTION_HANDLER(__FILE__ ":" _LIBCPP_TOSTRING(                                                      \
-             __LINE__) ": libc++ Hardening assertion " _LIBCPP_TOSTRING(expression) " failed: " message "\n"))
+       : _LIBCPP_DIAGNOSTIC_PUSH                                    /*                                              */ \
+             _LIBCPP_CLANG_DIAGNOSTIC_IGNORED("-Wfunction-effects") /*                                              */ \
+       _LIBCPP_ASSERTION_HANDLER(__FILE__ ":" _LIBCPP_TOSTRING(                                                        \
+           __LINE__) ": libc++ Hardening assertion " _LIBCPP_TOSTRING(expression) " failed: " message "\n")            \
+           _LIBCPP_DIAGNOSTIC_POP)
 
 // WARNING: __builtin_assume can currently inhibit optimizations. Only add assumptions with a clear
 // optimization intent. See https://discourse.llvm.org/t/llvm-assume-blocks-optimization/71609 for a
diff --git a/libcxx/test/extensions/clang/function-effect-analysis.sh.cpp b/libcxx/test/extensions/clang/function-effect-analysis.sh.cpp
new file mode 100644
index 0000000000000..c1408724fc4c6
--- /dev/null
+++ b/libcxx/test/extensions/clang/function-effect-analysis.sh.cpp
@@ -0,0 +1,41 @@
+//===----------------------------------------------------------------------===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+
+// GCC doesn't support -Wfunction-effect-analysis and the associated attributes
+// UNSUPPORTED: gcc
+
+// The observe semantic requires experimental support for now.
+// XFAIL: libcpp-has-no-experimental-hardening-observe-semantic
+
+// Make sure that libc++ assertions are compatible with the function effect analysis
+// extension in Clang. The function effect analysis extension allows marking functions
+// with attributes like [[nonblocking]], and Clang will issue a diagnostic if such a
+// function calls another function that doesn't satisfy the requirements of the attribute.
+//
+// However, libc++'s assertion functions are called in a context where a precondition
+// has been violated. Hence, it is acceptable not to satisfy these function effect
+// attributes once the assertion is known to fail.
+//
+// This test ensures that we properly disable function effect analysis diagnostics
+// in libc++'s assertion macros, otherwise it becomes impossible to call a function
+// with hardened preconditions from e.g. a [[nonblocking]] function.
+
+// ADDITIONAL_COMPILE_FLAGS: -Wfunction-effects -Werror=function-effects
+
+// RUN: %{build}
+// RUN: %{build} -D_LIBCPP_ASSERTION_SEMANTIC=_LIBCPP_ASSERTION_SEMANTIC_IGNORE
+// RUN: %{build} -D_LIBCPP_ASSERTION_SEMANTIC=_LIBCPP_ASSERTION_SEMANTIC_OBSERVE
+// RUN: %{build} -D_LIBCPP_ASSERTION_SEMANTIC=_LIBCPP_ASSERTION_SEMANTIC_QUICK_ENFORCE
+// RUN: %{build} -D_LIBCPP_ASSERTION_SEMANTIC=_LIBCPP_ASSERTION_SEMANTIC_ENFORCE
+
+#include <__assert>
+
+void f(bool condition) noexcept [[clang::nonblocking]] { _LIBCPP_ASSERT(condition, "message"); }
+void g(bool condition) noexcept [[clang::nonallocating]] { _LIBCPP_ASSERT(condition, "message"); }
+
+int main(int, char**) { return 0; }

>From 578715df4d199592ab3fd6b1c0d242d8fe59e1a1 Mon Sep 17 00:00:00 2001
From: Louis Dionne <ldionne.2 at gmail.com>
Date: Fri, 23 Jan 2026 10:03:54 -0500
Subject: [PATCH 2/4] Reword comment

---
 libcxx/include/__assert | 7 ++++---
 1 file changed, 4 insertions(+), 3 deletions(-)

diff --git a/libcxx/include/__assert b/libcxx/include/__assert
index b114aa8c12dbb..388db33f12f20 100644
--- a/libcxx/include/__assert
+++ b/libcxx/include/__assert
@@ -17,9 +17,10 @@
 #  pragma GCC system_header
 #endif
 
-// Note that we disable -Wfunction-effects inside _LIBCPP_ASSERT when the assertion fails since
-// function effect attributes don't provide any guarantees once a function is determined to be
-// out of contract.
+// Note that we disable -Wfunction-effects inside _LIBCPP_ASSERT when the assertion fails because
+// once function's preconditions are violated, function effect analysis can no longer provide any
+// meaningful guarantees (e.g., cannot guarantee the caller is non-blocking, regardless of function
+// effect attributes).
 #define _LIBCPP_ASSERT(expression, message)                                                                            \
   (__builtin_expect(static_cast<bool>(expression), 1)                                                                  \
        ? (void)0                                                                                                       \

>From 27cdfbf633410b832f177e3a2cb75b1ed8c374b6 Mon Sep 17 00:00:00 2001
From: Louis Dionne <ldionne.2 at gmail.com>
Date: Fri, 23 Jan 2026 10:04:45 -0500
Subject: [PATCH 3/4] Remove redundant warning

---
 libcxx/test/extensions/clang/function-effect-analysis.sh.cpp | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/libcxx/test/extensions/clang/function-effect-analysis.sh.cpp b/libcxx/test/extensions/clang/function-effect-analysis.sh.cpp
index c1408724fc4c6..2693d3fb7aba3 100644
--- a/libcxx/test/extensions/clang/function-effect-analysis.sh.cpp
+++ b/libcxx/test/extensions/clang/function-effect-analysis.sh.cpp
@@ -25,7 +25,7 @@
 // in libc++'s assertion macros, otherwise it becomes impossible to call a function
 // with hardened preconditions from e.g. a [[nonblocking]] function.
 
-// ADDITIONAL_COMPILE_FLAGS: -Wfunction-effects -Werror=function-effects
+// ADDITIONAL_COMPILE_FLAGS: -Werror=function-effects
 
 // RUN: %{build}
 // RUN: %{build} -D_LIBCPP_ASSERTION_SEMANTIC=_LIBCPP_ASSERTION_SEMANTIC_IGNORE

>From 50313f2bc5ca8e50fcc54a58c9b6b37b2b2e9593 Mon Sep 17 00:00:00 2001
From: Louis Dionne <ldionne.2 at gmail.com>
Date: Fri, 23 Jan 2026 10:25:34 -0500
Subject: [PATCH 4/4] Use verify and apply attribute instead of pragmas

---
 libcxx/include/__log_hardening_failure        | 10 ++++-
 .../clang/function-effect-analysis.sh.cpp     | 45 ++++++++++++++-----
 2 files changed, 42 insertions(+), 13 deletions(-)

diff --git a/libcxx/include/__log_hardening_failure b/libcxx/include/__log_hardening_failure
index d1805306f6b6e..200ade9a83be8 100644
--- a/libcxx/include/__log_hardening_failure
+++ b/libcxx/include/__log_hardening_failure
@@ -24,7 +24,15 @@ _LIBCPP_BEGIN_NAMESPACE_STD
 
 // This function should never be called directly from the code -- it should only be called through the
 // `_LIBCPP_LOG_HARDENING_FAILURE` macro.
-[[__gnu__::__cold__]] _LIBCPP_EXPORTED_FROM_ABI void __log_hardening_failure(const char* __message) noexcept;
+//
+// Furthermore, note that we pretend that this is a non-blocking and non-allocating function per Clang's
+// function effect attributes. We do that because this function is only called once a function's preconditions
+// are violated, at which point function effect analysis can no longer provide any meaningful guarantees
+// (e.g., cannot guarantee the caller is non-blocking, regardless of function effect attributes). Failure
+// to mark this function with these attributes would render it (and anything calling it, such as span::operator[])
+// unusable from a [[nonblocking]] function.
+[[__gnu__::__cold__]] _LIBCPP_EXPORTED_FROM_ABI void __log_hardening_failure(const char* __message) noexcept
+    [[_Clang::__nonblocking__]] [[_Clang::__nonallocating__]];
 
 // _LIBCPP_LOG_HARDENING_FAILURE(message)
 //
diff --git a/libcxx/test/extensions/clang/function-effect-analysis.sh.cpp b/libcxx/test/extensions/clang/function-effect-analysis.sh.cpp
index 2693d3fb7aba3..815795e1d9025 100644
--- a/libcxx/test/extensions/clang/function-effect-analysis.sh.cpp
+++ b/libcxx/test/extensions/clang/function-effect-analysis.sh.cpp
@@ -27,15 +27,36 @@
 
 // ADDITIONAL_COMPILE_FLAGS: -Werror=function-effects
 
-// RUN: %{build}
-// RUN: %{build} -D_LIBCPP_ASSERTION_SEMANTIC=_LIBCPP_ASSERTION_SEMANTIC_IGNORE
-// RUN: %{build} -D_LIBCPP_ASSERTION_SEMANTIC=_LIBCPP_ASSERTION_SEMANTIC_OBSERVE
-// RUN: %{build} -D_LIBCPP_ASSERTION_SEMANTIC=_LIBCPP_ASSERTION_SEMANTIC_QUICK_ENFORCE
-// RUN: %{build} -D_LIBCPP_ASSERTION_SEMANTIC=_LIBCPP_ASSERTION_SEMANTIC_ENFORCE
-
-#include <__assert>
-
-void f(bool condition) noexcept [[clang::nonblocking]] { _LIBCPP_ASSERT(condition, "message"); }
-void g(bool condition) noexcept [[clang::nonallocating]] { _LIBCPP_ASSERT(condition, "message"); }
-
-int main(int, char**) { return 0; }
+// RUN: %{verify}
+// RUN: %{verify} -D_LIBCPP_ASSERTION_SEMANTIC=_LIBCPP_ASSERTION_SEMANTIC_IGNORE
+// RUN: %{verify} -D_LIBCPP_ASSERTION_SEMANTIC=_LIBCPP_ASSERTION_SEMANTIC_OBSERVE
+// RUN: %{verify} -D_LIBCPP_ASSERTION_SEMANTIC=_LIBCPP_ASSERTION_SEMANTIC_QUICK_ENFORCE
+// RUN: %{verify} -D_LIBCPP_ASSERTION_SEMANTIC=_LIBCPP_ASSERTION_SEMANTIC_ENFORCE
+
+#include <cstddef>
+#include <span>
+
+// Using _LIBCPP_ASSERT directly
+void f(bool condition) noexcept [[clang::nonblocking]] {
+  _LIBCPP_ASSERT(condition, "message"); // nothing
+}
+void g(bool condition) noexcept [[clang::nonallocating]] {
+  _LIBCPP_ASSERT(condition, "message"); // nothing
+}
+
+// Sanity check with an actual std::span
+void f(std::span<int> span, std::size_t index) noexcept [[clang::nonblocking]] {
+  (void)span[index]; // nothing
+}
+void g(std::span<int> span, std::size_t index) noexcept [[clang::nonallocating]] {
+  (void)span[index]; // nothing
+}
+
+// Test the test: ensure that a diagnostic would be emitted normally
+void __potentially_blocking();
+void f() noexcept [[clang::nonblocking]] {
+  __potentially_blocking(); // expected-error {{function with 'nonblocking' attribute must not call non-'nonblocking' function}}
+}
+void g() noexcept [[clang::nonallocating]] {
+  __potentially_blocking(); // expected-error {{function with 'nonallocating' attribute must not call non-'nonallocating' function}}
+}



More information about the libcxx-commits mailing list