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 <[email protected]>
Date: Fri, 26 Jun 2026 13:33:47 -0400
Subject: [PATCH 1/4] 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 <[email protected]>
Date: Fri, 26 Jun 2026 13:52:54 -0400
Subject: [PATCH 2/4] 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

>From 61e8fa29d2ac55df7dc4edd957aa2de0964cc2b7 Mon Sep 17 00:00:00 2001
From: Aaron Ballman <[email protected]>
Date: Fri, 26 Jun 2026 15:12:52 -0400
Subject: [PATCH 3/4] Put the logic into checkCall so it is used by more places

---
 clang/lib/Sema/SemaChecking.cpp   | 15 ++++++++++++
 clang/lib/Sema/SemaExpr.cpp       | 17 --------------
 clang/test/Sema/attr-const-pure.c | 39 ++++++++++++++++++++++++-------
 3 files changed, 45 insertions(+), 26 deletions(-)

diff --git a/clang/lib/Sema/SemaChecking.cpp b/clang/lib/Sema/SemaChecking.cpp
index c3ca45ee55786..ffee812ec8405 100644
--- a/clang/lib/Sema/SemaChecking.cpp
+++ b/clang/lib/Sema/SemaChecking.cpp
@@ -4642,6 +4642,21 @@ void Sema::checkCall(NamedDecl *FDecl, const 
FunctionProtoType *Proto,
     SYCL().DiagIfDeviceCode(Loc, diag::err_variadic_device_fn)
         << diag::OffloadLang::SYCL;
 
+  // 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 ((FD && FD->isNoReturn()) || (Proto && Proto->getNoReturnAttr())) {
+    if (const Decl *D = getCurFunctionDecl(/*AllowLambda=*/true);
+        D && (D->hasAttr<ConstAttr>() || D->hasAttr<PureAttr>())) {
+      DiagRuntimeBehavior(Loc, nullptr,
+                          PDiag(diag::warn_const_pure_noreturn_call)
+                              << D->hasAttr<ConstAttr>());
+      DiagRuntimeBehavior(D->getLocation(), nullptr,
+                          PDiag(diag::note_const_pure_noreturn_call)
+                              << D->hasAttr<ConstAttr>());
+    }
+  }
+
   if (FD)
     diagnoseArgDependentDiagnoseIfAttrs(FD, ThisArg, Args, Loc);
 }
diff --git a/clang/lib/Sema/SemaExpr.cpp b/clang/lib/Sema/SemaExpr.cpp
index 953d483b2129b..7c868d176e803 100644
--- a/clang/lib/Sema/SemaExpr.cpp
+++ b/clang/lib/Sema/SemaExpr.cpp
@@ -7388,23 +7388,6 @@ 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 0a34c705a069d..1cb30e13d82ea 100644
--- a/clang/test/Sema/attr-const-pure.c
+++ b/clang/test/Sema/attr-const-pure.c
@@ -80,11 +80,11 @@ __attribute__((const)) int noreturn_test2(void) { // 
expected-note {{function de
 }
 
 __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}}
+  indirect_noreturn(); // expected-warning {{calling a 'noreturn' function 
from a function with the 'const' attribute is undefined behavior}}
   return 12;
 }
 
-__attribute__((const)) int noreturn_test4(void) {
+__attribute__((const)) int noreturn_test4(void) { // expected-note {{function 
declared 'const' here}}
   // This should not be diagnosed.
   (void)sizeof((direct_noreturn(), 1));
 
@@ -96,8 +96,9 @@ __attribute__((const)) int noreturn_test4(void) {
 #endif // __cplusplus
 
   if (0) {
-       // This should not be diagnosed.
-    direct_noreturn();
+       // FIXME: it would be better if this was not diagnosed because it is
+       // statically known to be unreachable.
+    direct_noreturn(); // expected-warning {{calling a 'noreturn' function 
from a function with the 'const' attribute is undefined behavior}}
   }
 
   return 12;
@@ -134,14 +135,34 @@ __attribute__((pure)) int noreturn_test8() { // 
expected-note {{function declare
 }
 
 template <typename T>
-[[gnu::pure]] int noreturn_test9() {
-  // No diagnostic expected without an instantiation because the call cannot be
-  // resolved yet.
-  T::nrcall();
+[[gnu::pure]] int noreturn_test9() { // expected-note {{function declared 
'pure' here}}
+  T::nrcall(); // expected-warning {{calling a 'noreturn' function from a 
function with the 'pure' attribute is undefined behavior}}
   return 12;
 }
 
 struct S {
-  [[noreturn]] void nrcall();
+  [[noreturn]] static void nrcall();
+  [[noreturn]] void mem_nrcall();
 };
+
+void instantiate() {
+  (void)noreturn_test9<S>(); // expected-note {{in instantiation of function 
template specialization 'noreturn_test9<S>' requested here}}
+}
+
+[[gnu::const]] int memfn() { // expected-note {{function declared 'const' 
here}}
+  S{}.mem_nrcall(); // expected-warning {{calling a 'noreturn' function from a 
function with the 'const' attribute is undefined behavior}}
+}
+
+__attribute__((pure)) int noreturn_test10() {
+  (void)[] {
+       // This should not be diagnosed, it's not called within the pure 
function.
+    direct_noreturn();
+  };
+  
+  (void)[]() __attribute__((const)) { // 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;
+}
 #endif // __cplusplus

>From a00040acc4ff7d21208616b65a9f2f5801fd1ee2 Mon Sep 17 00:00:00 2001
From: Aaron Ballman <[email protected]>
Date: Mon, 29 Jun 2026 07:05:22 -0400
Subject: [PATCH 4/4] Additional test coverage

---
 clang/test/Sema/attr-const-pure.c | 7 +++++--
 1 file changed, 5 insertions(+), 2 deletions(-)

diff --git a/clang/test/Sema/attr-const-pure.c 
b/clang/test/Sema/attr-const-pure.c
index 1cb30e13d82ea..674a04f1cba0a 100644
--- a/clang/test/Sema/attr-const-pure.c
+++ b/clang/test/Sema/attr-const-pure.c
@@ -143,14 +143,17 @@ template <typename T>
 struct S {
   [[noreturn]] static void nrcall();
   [[noreturn]] void mem_nrcall();
+
+  void (*indirect_mem_noreturn)(void) __attribute__((noreturn));
 };
 
 void instantiate() {
   (void)noreturn_test9<S>(); // expected-note {{in instantiation of function 
template specialization 'noreturn_test9<S>' requested here}}
 }
 
-[[gnu::const]] int memfn() { // expected-note {{function declared 'const' 
here}}
-  S{}.mem_nrcall(); // expected-warning {{calling a 'noreturn' function from a 
function with the 'const' attribute is undefined behavior}}
+[[gnu::const]] int memfn() {   // expected-note 2 {{function declared 'const' 
here}}
+  S{}.mem_nrcall();            // expected-warning {{calling a 'noreturn' 
function from a function with the 'const' attribute is undefined behavior}}
+  S{}.indirect_mem_noreturn(); // expected-warning {{calling a 'noreturn' 
function from a function with the 'const' attribute is undefined behavior}}
 }
 
 __attribute__((pure)) int noreturn_test10() {

_______________________________________________
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to