Author: Serge Pavlov Date: 2025-07-15T12:56:11+07:00 New Revision: 977cfea786401eab2b167307221a15fa6747e28e
URL: https://github.com/llvm/llvm-project/commit/977cfea786401eab2b167307221a15fa6747e28e DIFF: https://github.com/llvm/llvm-project/commit/977cfea786401eab2b167307221a15fa6747e28e.diff LOG: [Analysis] Avoid some warnings about exit from noreturn function (#144408) Compiler sometimes issues warnings on exit from 'noreturn' functions, in the code like: [[noreturn]] extern void nonreturnable(); void (*func_ptr)(); [[noreturn]] void foo() { func_ptr = nonreturnable; (*func_ptr)(); } where exit cannot take place because the function pointer is actually a pointer to noreturn function. This change introduces small data analysis that can remove some of the warnings in the cases when compiler can prove that the set of reaching definitions consists of noreturn functions only. Added: clang/test/SemaCXX/noreturn-vars.cpp Modified: clang/docs/ReleaseNotes.rst clang/lib/Sema/AnalysisBasedWarnings.cpp Removed: ################################################################################ diff --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst index fa949f84b8fdf..e8c85483e9864 100644 --- a/clang/docs/ReleaseNotes.rst +++ b/clang/docs/ReleaseNotes.rst @@ -705,6 +705,11 @@ Improvements to Clang's diagnostics - Improve the diagnostics for placement new expression when const-qualified object was passed as the storage argument. (#GH143708) +- Clang now does not issue a warning about returning from a function declared with + the ``[[noreturn]]`` attribute when the function body is ended with a call via + pointer, provided it can be proven that the pointer only points to + ``[[noreturn]]`` functions. + Improvements to Clang's time-trace ---------------------------------- diff --git a/clang/lib/Sema/AnalysisBasedWarnings.cpp b/clang/lib/Sema/AnalysisBasedWarnings.cpp index 851f07bcdb97a..5e75c64eb2b9a 100644 --- a/clang/lib/Sema/AnalysisBasedWarnings.cpp +++ b/clang/lib/Sema/AnalysisBasedWarnings.cpp @@ -37,6 +37,7 @@ #include "clang/Analysis/AnalysisDeclContext.h" #include "clang/Analysis/CFG.h" #include "clang/Analysis/CFGStmtMap.h" +#include "clang/Analysis/FlowSensitive/DataflowWorklist.h" #include "clang/Basic/Diagnostic.h" #include "clang/Basic/DiagnosticSema.h" #include "clang/Basic/SourceLocation.h" @@ -46,6 +47,7 @@ #include "clang/Sema/SemaInternal.h" #include "llvm/ADT/ArrayRef.h" #include "llvm/ADT/BitVector.h" +#include "llvm/ADT/DenseMap.h" #include "llvm/ADT/MapVector.h" #include "llvm/ADT/STLFunctionalExtras.h" #include "llvm/ADT/SmallVector.h" @@ -401,6 +403,143 @@ static bool isNoexcept(const FunctionDecl *FD) { return false; } +/// Checks if the given expression is a reference to a function with +/// 'noreturn' attribute. +static bool isReferenceToNoReturn(const Expr *E) { + if (auto *DRef = dyn_cast<DeclRefExpr>(E->IgnoreParenCasts())) + if (auto *FD = dyn_cast<FunctionDecl>(DRef->getDecl())) + return FD->isNoReturn(); + return false; +} + +/// Checks if the given variable, which is assumed to be a function pointer, is +/// initialized with a function having 'noreturn' attribute. +static bool isInitializedWithNoReturn(const VarDecl *VD) { + if (const Expr *Init = VD->getInit()) { + if (auto *ListInit = dyn_cast<InitListExpr>(Init); + ListInit && ListInit->getNumInits() > 0) + Init = ListInit->getInit(0); + return isReferenceToNoReturn(Init); + } + return false; +} + +namespace { + +/// Looks for statements, that can define value of the given variable. +struct TransferFunctions : public StmtVisitor<TransferFunctions> { + const VarDecl *Var; + std::optional<bool> AllValuesAreNoReturn; + + TransferFunctions(const VarDecl *VD) : Var(VD) {} + + void reset() { AllValuesAreNoReturn = std::nullopt; } + + void VisitDeclStmt(DeclStmt *DS) { + for (auto *DI : DS->decls()) + if (auto *VD = dyn_cast<VarDecl>(DI)) + if (VarDecl *Def = VD->getDefinition()) + if (Def == Var) + AllValuesAreNoReturn = isInitializedWithNoReturn(Def); + } + + void VisitUnaryOperator(UnaryOperator *UO) { + if (UO->getOpcode() == UO_AddrOf) { + if (auto *DRef = + dyn_cast<DeclRefExpr>(UO->getSubExpr()->IgnoreParenCasts())) + if (DRef->getDecl() == Var) + AllValuesAreNoReturn = false; + } + } + + void VisitBinaryOperator(BinaryOperator *BO) { + if (BO->getOpcode() == BO_Assign) + if (auto *DRef = dyn_cast<DeclRefExpr>(BO->getLHS()->IgnoreParenCasts())) + if (DRef->getDecl() == Var) + AllValuesAreNoReturn = isReferenceToNoReturn(BO->getRHS()); + } + + void VisitCallExpr(CallExpr *CE) { + for (CallExpr::arg_iterator I = CE->arg_begin(), E = CE->arg_end(); I != E; + ++I) { + const Expr *Arg = *I; + if (Arg->isGLValue() && !Arg->getType().isConstQualified()) + if (auto *DRef = dyn_cast<DeclRefExpr>(Arg->IgnoreParenCasts())) + if (auto VD = dyn_cast<VarDecl>(DRef->getDecl())) + if (VD->getDefinition() == Var) + AllValuesAreNoReturn = false; + } + } +}; +} // namespace + +// Checks if all possible values of the given variable are functions with +// 'noreturn' attribute. +static bool areAllValuesNoReturn(const VarDecl *VD, const CFGBlock &VarBlk, + AnalysisDeclContext &AC) { + // The set of possible values of a constant variable is determined by + // its initializer, unless it is a function parameter. + if (!isa<ParmVarDecl>(VD) && VD->getType().isConstant(AC.getASTContext())) { + if (const VarDecl *Def = VD->getDefinition()) + return isInitializedWithNoReturn(Def); + return false; + } + + // In multithreaded environment the value of a global variable may be changed + // asynchronously. + if (!VD->getDeclContext()->isFunctionOrMethod()) + return false; + + // Check the condition "all values are noreturn". It is satisfied if the + // variable is set to "noreturn" value in the current block or all its + // predecessors satisfies the condition. + using MapTy = llvm::DenseMap<const CFGBlock *, std::optional<bool>>; + using ValueTy = MapTy::value_type; + MapTy BlocksToCheck; + BlocksToCheck[&VarBlk] = std::nullopt; + const auto BlockSatisfiesCondition = [](ValueTy Item) { + return Item.getSecond().value_or(false); + }; + + TransferFunctions TF(VD); + BackwardDataflowWorklist Worklist(*AC.getCFG(), AC); + Worklist.enqueueBlock(&VarBlk); + while (const CFGBlock *B = Worklist.dequeue()) { + // First check the current block. + for (CFGBlock::const_reverse_iterator ri = B->rbegin(), re = B->rend(); + ri != re; ++ri) { + if (std::optional<CFGStmt> cs = ri->getAs<CFGStmt>()) { + const Stmt *S = cs->getStmt(); + TF.reset(); + TF.Visit(const_cast<Stmt *>(S)); + if (TF.AllValuesAreNoReturn) { + if (!TF.AllValuesAreNoReturn.value()) + return false; + BlocksToCheck[B] = true; + break; + } + } + } + + // If all checked blocks satisfy the condition, the check is finished. + if (std::all_of(BlocksToCheck.begin(), BlocksToCheck.end(), + BlockSatisfiesCondition)) + return true; + + // If this block does not contain the variable definition, check + // its predecessors. + if (!BlocksToCheck[B]) { + Worklist.enqueuePredecessors(B); + BlocksToCheck.erase(B); + for (const auto &PredBlk : B->preds()) + if (!BlocksToCheck.contains(PredBlk)) + BlocksToCheck[PredBlk] = std::nullopt; + } + } + + return false; +} + //===----------------------------------------------------------------------===// // Check for missing return value. //===----------------------------------------------------------------------===// @@ -527,6 +666,17 @@ static ControlFlowKind CheckFallThrough(AnalysisDeclContext &AC) { HasAbnormalEdge = true; continue; } + if (auto *Call = dyn_cast<CallExpr>(S)) { + const Expr *Callee = Call->getCallee(); + if (Callee->getType()->isPointerType()) + if (auto *DeclRef = + dyn_cast<DeclRefExpr>(Callee->IgnoreParenImpCasts())) + if (auto *VD = dyn_cast<VarDecl>(DeclRef->getDecl())) + if (areAllValuesNoReturn(VD, B, AC)) { + HasAbnormalEdge = true; + continue; + } + } HasPlainEdge = true; } diff --git a/clang/test/SemaCXX/noreturn-vars.cpp b/clang/test/SemaCXX/noreturn-vars.cpp new file mode 100644 index 0000000000000..ca65fcf5ca31d --- /dev/null +++ b/clang/test/SemaCXX/noreturn-vars.cpp @@ -0,0 +1,227 @@ +// RUN: %clang_cc1 -fsyntax-only %s -verify + +[[noreturn]] extern void noret(); +[[noreturn]] extern void noret2(); +extern void ordinary(); + +typedef void (*func_type)(void); + +// Constant initialization. + +void (* const const_fptr)() = noret; +[[noreturn]] void test_global_const() { + const_fptr(); +} + +const func_type const_fptr_cast = (func_type)noret2; +[[noreturn]] void test_global_cast() { + const_fptr_cast(); +} + +void (* const const_fptr_list)() = {noret}; +[[noreturn]] void test_global_list() { + const_fptr_list(); +} + +const func_type const_fptr_fcast = func_type(noret2); +[[noreturn]] void test_global_fcast() { + const_fptr_fcast(); +} + +[[noreturn]] void test_local_const() { + void (* const fptr)() = noret; + fptr(); +} + +// Global variable assignment. +void (*global_fptr)() = noret; + +[[noreturn]] void test_global_noassign() { + global_fptr(); +} // expected-warning {{function declared 'noreturn' should not return}} + +[[noreturn]] void test_global_assign() { + global_fptr = noret; + global_fptr(); +} // expected-warning {{function declared 'noreturn' should not return}} + +// Local variable assignment. + +[[noreturn]] void test_init() { + func_type func_ptr = noret; + func_ptr(); +} + +[[noreturn]] void test_assign() { + void (*func_ptr)(void); + func_ptr = noret; + func_ptr(); +} + +[[noreturn]] void test_override() { + func_type func_ptr; + func_ptr = ordinary; + func_ptr = noret; + func_ptr(); +} + +[[noreturn]] void test_if_all(int x) { + func_type func_ptr; + if (x > 0) + func_ptr = noret; + else + func_ptr = noret2; + func_ptr(); +} + +[[noreturn]] void test_if_mix(int x) { + func_type func_ptr; + if (x > 0) + func_ptr = noret; + else + func_ptr = ordinary; + func_ptr(); +} // expected-warning {{function declared 'noreturn' should not return}} + +[[noreturn]] void test_if_opt(int x) { + func_type func_ptr = noret; + if (x > 0) + func_ptr = ordinary; + func_ptr(); +} // expected-warning {{function declared 'noreturn' should not return}} + +[[noreturn]] void test_if_opt2(int x) { + func_type func_ptr = ordinary; + if (x > 0) + func_ptr = noret; + func_ptr(); +} // expected-warning {{function declared 'noreturn' should not return}} + +[[noreturn]] void test_if_nest_all(int x, int y) { + func_type func_ptr; + if (x > 0) { + if (y > 0) + func_ptr = noret; + else + func_ptr = noret2; + } else { + if (y < 0) + func_ptr = noret2; + else + func_ptr = noret; + } + func_ptr(); +} + +[[noreturn]] void test_if_nest_mix(int x, int y) { + func_type func_ptr; + if (x > 0) { + if (y > 0) + func_ptr = noret; + else + func_ptr = noret2; + } else { + if (y < 0) + func_ptr = ordinary; + else + func_ptr = noret; + } + func_ptr(); +} // expected-warning {{function declared 'noreturn' should not return}} + +[[noreturn]] void test_switch_all(int x) { + func_type func_ptr; + switch(x) { + case 1: + func_ptr = noret; + break; + default: + func_ptr = noret2; + break; + } + func_ptr(); +} + +[[noreturn]] void test_switch_mix(int x) { + func_type func_ptr; + switch(x) { + case 1: + func_ptr = ordinary; + break; + default: + func_ptr = noret; + break; + } + func_ptr(); +} // expected-warning {{function declared 'noreturn' should not return}} + +[[noreturn]] void test_switch_fall(int x) { + func_type func_ptr; + switch(x) { + case 1: + func_ptr = ordinary; + default: + func_ptr = noret; + break; + } + func_ptr(); +} + +[[noreturn]] void test_switch_all_nest(int x, int y) { + func_type func_ptr; + switch(x) { + case 1: + func_ptr = noret; + break; + default: + if (y > 0) + func_ptr = noret2; + else + func_ptr = noret; + break; + } + func_ptr(); +} + +[[noreturn]] void test_switch_mix_nest(int x, int y) { + func_type func_ptr; + switch(x) { + case 1: + func_ptr = noret; + break; + default: + if (y > 0) + func_ptr = noret2; + else + func_ptr = ordinary; + break; + } + func_ptr(); +} // expected-warning {{function declared 'noreturn' should not return}} + +// Function parameters. + +[[noreturn]] void test_param(void (*func_ptr)() = noret) { + func_ptr(); +} // expected-warning {{function declared 'noreturn' should not return}} + +[[noreturn]] void test_const_param(void (* const func_ptr)() = noret) { + func_ptr(); +} // expected-warning {{function declared 'noreturn' should not return}} + +// Escaped value. + +extern void abc_01(func_type &); +extern void abc_02(func_type *); + +[[noreturn]] void test_escape_ref() { + func_type func_ptr = noret; + abc_01(func_ptr); + func_ptr(); +} // expected-warning {{function declared 'noreturn' should not return}} + +[[noreturn]] void test_escape_addr() { + func_type func_ptr = noret; + abc_02(&func_ptr); + func_ptr(); +} // expected-warning {{function declared 'noreturn' should not return}} _______________________________________________ cfe-commits mailing list cfe-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits