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
