Author: Aaron Ballman Date: 2026-06-29T08:22:35-04:00 New Revision: c06d04719cd6d127db25cf38c328cda2aa27f8a2
URL: https://github.com/llvm/llvm-project/commit/c06d04719cd6d127db25cf38c328cda2aa27f8a2 DIFF: https://github.com/llvm/llvm-project/commit/c06d04719cd6d127db25cf38c328cda2aa27f8a2.diff LOG: Diagnose noreturn calls from a const or pure function (#206134) 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 Added: Modified: clang/docs/ReleaseNotes.rst clang/include/clang/Basic/DiagnosticSemaKinds.td clang/lib/Sema/SemaChecking.cpp clang/test/Sema/attr-const-pure.c Removed: ################################################################################ diff --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst index 5390abe7cb90a..a2eef75e2a719 100644 --- a/clang/docs/ReleaseNotes.rst +++ b/clang/docs/ReleaseNotes.rst @@ -538,7 +538,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 bf70b93acf0eb..ac82ba0d6773d 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/SemaChecking.cpp b/clang/lib/Sema/SemaChecking.cpp index d3eb71e1afe88..2bbd880c0e068 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/test/Sema/attr-const-pure.c b/clang/test/Sema/attr-const-pure.c index 43e22eb34014d..674a04f1cba0a 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,108 @@ __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 {{calling a 'noreturn' function from a function with the 'const' attribute is undefined behavior}} + return 12; +} + +__attribute__((const)) int noreturn_test4(void) { // expected-note {{function declared 'const' here}} + // 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) { + // 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; +} + +__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 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() { // 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]] 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 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() { + (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 _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
