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/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 <[email protected]> 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 _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
