[clang] Diagnose noreturn calls from a const or pure function (PR #206134)
Aaron Ballman via cfe-commits
cfe-commits at lists.llvm.org
Fri Jun 26 10:53:18 PDT 2026
https://github.com/AaronBallman updated https://github.com/llvm/llvm-project/pull/206134
>From f9b4076e0d01c281b9d64c48f180a0272e8996e2 Mon Sep 17 00:00:00 2001
From: Aaron Ballman <aaron at aaronballman.com>
Date: Fri, 26 Jun 2026 13:33:47 -0400
Subject: [PATCH 1/2] Diagnose noreturn calls from a const or pure function
The const and pure functions add the WillReturn LLVM IR attribute which
require the function to return. Calling a noreturn function is UB, so
it is now being diagnosed unless the call is known to be unevaluated.
This diagnostic is enabled by default.
Fixes #129022
---
clang/docs/ReleaseNotes.rst | 4 +-
.../clang/Basic/DiagnosticSemaKinds.td | 7 ++
clang/lib/Sema/SemaExpr.cpp | 17 +++++
clang/test/Sema/attr-const-pure.c | 71 ++++++++++++++++++-
4 files changed, 97 insertions(+), 2 deletions(-)
diff --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst
index 7fb3f273c5608..ce9553ca61920 100644
--- a/clang/docs/ReleaseNotes.rst
+++ b/clang/docs/ReleaseNotes.rst
@@ -527,7 +527,9 @@ Attribute Changes in Clang
ISO 18037 fixed-point ``printf`` specifiers.
- The ``const`` and ``pure`` attributes only apply to functions; they are now
- diagnosed and ignored when applied to anything else.
+ diagnosed and ignored when applied to anything else. Additionally, calling
+ a function marked ``noreturn`` from a function marked ``const`` or ``pure``
+ is now diagnosed as undefined behavior (#GH129022).
Improvements to Clang's diagnostics
-----------------------------------
diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td
index 7e20630708312..a2d9851ce4a1f 100644
--- a/clang/include/clang/Basic/DiagnosticSemaKinds.td
+++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td
@@ -796,6 +796,13 @@ def warn_const_attr_with_pure_attr : Warning<
def warn_pure_function_returns_void : Warning<
"'%select{pure|const}0' attribute on function returning 'void'; attribute ignored">,
InGroup<IgnoredAttributes>;
+def warn_const_pure_noreturn_call : Warning<
+ "calling a 'noreturn' function from a function with the "
+ "'%select{pure|const}0' attribute is undefined behavior">,
+ InGroup<DiagGroup<"noreturn-const-pure">>;
+def note_const_pure_noreturn_call : Note<
+ "function declared '%select{pure|const}0' here">;
+
def warn_suggest_noreturn_function : Warning<
"%select{function|method}0 %1 could be declared with attribute 'noreturn'">,
diff --git a/clang/lib/Sema/SemaExpr.cpp b/clang/lib/Sema/SemaExpr.cpp
index 7c868d176e803..953d483b2129b 100644
--- a/clang/lib/Sema/SemaExpr.cpp
+++ b/clang/lib/Sema/SemaExpr.cpp
@@ -7388,6 +7388,23 @@ ExprResult Sema::BuildResolvedCallExpr(Expr *Fn, NamedDecl *NDecl,
}
}
+ // Diagnose calls to noreturn functions from within a function declared as
+ // being const or pure; this is undefined behavior. But only if the expression
+ // is actually evaluated.
+ if ((FDecl && FDecl->isNoReturn()) || (FuncT && FuncT->getNoReturnAttr())) {
+ if (clang::Scope *Parent = CurScope->getFnParent()) {
+ if (const Decl *D = dyn_cast<Decl>(Parent->getEntity());
+ D && (D->hasAttr<ConstAttr>() || D->hasAttr<PureAttr>())) {
+ DiagRuntimeBehavior(Fn->getExprLoc(), Fn,
+ PDiag(diag::warn_const_pure_noreturn_call)
+ << D->hasAttr<ConstAttr>());
+ DiagRuntimeBehavior(D->getLocation(), Fn,
+ PDiag(diag::note_const_pure_noreturn_call)
+ << D->hasAttr<ConstAttr>());
+ }
+ }
+ }
+
// Do special checking on direct calls to functions.
if (FDecl) {
if (CheckFunctionCall(FDecl, TheCall, Proto))
diff --git a/clang/test/Sema/attr-const-pure.c b/clang/test/Sema/attr-const-pure.c
index 43e22eb34014d..e378cad62bacc 100644
--- a/clang/test/Sema/attr-const-pure.c
+++ b/clang/test/Sema/attr-const-pure.c
@@ -33,7 +33,7 @@ __attribute__((const)) int temp_func1(Ty);
// FIXME: this should be diagnosed because it ends up with both the const and pure attributes.
template <>
[[gnu::pure]] int temp_func1<int>(int) { return 12; }
-#endif
+#endif // __cplusplus
// They do not apply to types, including function pointer types.
int (*fp1)(void) [[gnu::const]]; // expected-warning {{attribute 'gnu::const' ignored, because it cannot be applied to a type}}
@@ -64,3 +64,72 @@ __attribute__((pure)) int func8(void);
return 12;
}
+[[noreturn]] void direct_noreturn(void);
+// FIXME: the cast should not be necessary.
+void (*indirect_noreturn)(void) __attribute__((noreturn)) = (__typeof__(indirect_noreturn)) direct_noreturn;
+void returns_okay();
+
+__attribute__((const)) int noreturn_test1(void) {
+ returns_okay();
+ return 12;
+}
+
+__attribute__((const)) int noreturn_test2(void) { // expected-note {{function declared 'const' here}}
+ direct_noreturn(); // expected-warning {{alling a 'noreturn' function from a function with the 'const' attribute is undefined behavior}}
+ return 12;
+}
+
+__attribute__((const)) int noreturn_test3(void) { // expected-note {{function declared 'const' here}}
+ indirect_noreturn(); // expected-warning {{alling a 'noreturn' function from a function with the 'const' attribute is undefined behavior}}
+ return 12;
+}
+
+__attribute__((const)) int noreturn_test4(void) {
+ // This should not be diagnosed.
+ (void)sizeof((direct_noreturn(), 1));
+
+#ifdef __cplusplus
+ if constexpr(false) {
+ // This should not be diagnosed.
+ direct_noreturn();
+ }
+#endif // __cplusplus
+
+ if (0) {
+ // This should not be diagnosed.
+ direct_noreturn();
+ }
+
+ return 12;
+}
+
+__attribute__((pure)) int noreturn_test5(int x) { // expected-note {{function declared 'pure' here}}
+ if (x)
+ direct_noreturn(); // expected-warning {{calling a 'noreturn' function from a function with the 'pure' attribute is undefined behavior}}
+ return 12;
+}
+
+// FIXME: should this be diagnosed because of the noreturn call?
+[[gnu::pure]] int noreturn_test6(int array[(direct_noreturn(), 1)]);
+
+#ifdef __cplusplus
+
+template <typename Ty>
+int noreturn_test7(void) {
+ direct_noreturn(); // okay
+ return 12;
+}
+
+template <>
+__attribute__((const)) int noreturn_test7<int>() { // expected-note {{function declared 'const' here}}
+ direct_noreturn(); // expected-warning {{calling a 'noreturn' function from a function with the 'const' attribute is undefined behavior}}
+ return 12;
+}
+
+template <typename Ty>
+__attribute__((pure)) int noreturn_test8() { // expected-note {{function declared 'pure' here}}
+ // Diagnosed even though test7 is not instantiated
+ direct_noreturn(); // expected-warning {{calling a 'noreturn' function from a function with the 'pure' attribute is undefined behavior}}
+ return 12;
+}
+#endif // __cplusplus
>From 4614eb35479996544ed826f3c8dd27592f396638 Mon Sep 17 00:00:00 2001
From: Aaron Ballman <aaron at aaronballman.com>
Date: Fri, 26 Jun 2026 13:52:54 -0400
Subject: [PATCH 2/2] Update based on review feedback
---
clang/test/Sema/attr-const-pure.c | 14 +++++++++++++-
1 file changed, 13 insertions(+), 1 deletion(-)
diff --git a/clang/test/Sema/attr-const-pure.c b/clang/test/Sema/attr-const-pure.c
index e378cad62bacc..0a34c705a069d 100644
--- a/clang/test/Sema/attr-const-pure.c
+++ b/clang/test/Sema/attr-const-pure.c
@@ -128,8 +128,20 @@ __attribute__((const)) int noreturn_test7<int>() { // expected-note {{function d
template <typename Ty>
__attribute__((pure)) int noreturn_test8() { // expected-note {{function declared 'pure' here}}
- // Diagnosed even though test7 is not instantiated
+ // Diagnosed even though noreturn_test8 is not instantiated
direct_noreturn(); // expected-warning {{calling a 'noreturn' function from a function with the 'pure' attribute is undefined behavior}}
return 12;
}
+
+template <typename T>
+[[gnu::pure]] int noreturn_test9() {
+ // No diagnostic expected without an instantiation because the call cannot be
+ // resolved yet.
+ T::nrcall();
+ return 12;
+}
+
+struct S {
+ [[noreturn]] void nrcall();
+};
#endif // __cplusplus
More information about the cfe-commits
mailing list