llvmbot wrote:
<!--LLVM PR SUMMARY COMMENT--> @llvm/pr-subscribers-clang Author: Serge Pavlov (spavloff) <details> <summary>Changes</summary> 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. --- Full diff: https://github.com/llvm/llvm-project/pull/144408.diff 2 Files Affected: - (modified) clang/lib/Sema/AnalysisBasedWarnings.cpp (+114) - (added) clang/test/SemaCXX/noreturn-vars.cpp (+107) ``````````diff diff --git a/clang/lib/Sema/AnalysisBasedWarnings.cpp b/clang/lib/Sema/AnalysisBasedWarnings.cpp index 2a107a36e24b4..2853e1e7ebf3e 100644 --- a/clang/lib/Sema/AnalysisBasedWarnings.cpp +++ b/clang/lib/Sema/AnalysisBasedWarnings.cpp @@ -36,6 +36,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" @@ -399,6 +400,108 @@ static bool isNoexcept(const FunctionDecl *FD) { 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); + if (auto *DeclRef = dyn_cast<DeclRefExpr>(Init->IgnoreParenCasts())) + if (auto *FD = dyn_cast<FunctionDecl>(DeclRef->getDecl())) + return FD->isNoReturn(); + } + 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; +} + +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 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. + if (VD->getType().isConstant(AC.getASTContext())) { + if (const VarDecl *Def = VD->getDefinition()) + return isInitializedWithNoReturn(Def); + return false; + } + + // Scan function statements for definitions of the given variable. + TransferFunctions TF(VD); + BackwardDataflowWorklist Worklist(*AC.getCFG(), AC); + Worklist.enqueueBlock(&VarBlk); + while (const CFGBlock *B = Worklist.dequeue()) { + 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.Visit(const_cast<Stmt *>(S)); + if (TF.AllValuesAreNoReturn) + return *TF.AllValuesAreNoReturn; + } + } + Worklist.enqueuePredecessors(B); + } + + return false; +} + //===----------------------------------------------------------------------===// // Check for missing return value. //===----------------------------------------------------------------------===// @@ -525,6 +628,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..31d16db46e4a2 --- /dev/null +++ b/clang/test/SemaCXX/noreturn-vars.cpp @@ -0,0 +1,107 @@ +// 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(); +} + +[[noreturn]] void test_global_override() { + global_fptr = ordinary; + global_fptr = noret; + global_fptr(); +} + +[[noreturn]] void test_global_switch_01(int x) { + switch(x) { + case 1: + global_fptr = noret; + break; + default: + global_fptr = noret2; + break; + } + global_fptr(); +} + +[[noreturn]] void test_global_switch_02(int x) { + switch(x) { + case 1: + global_fptr = ordinary; + break; + default: + global_fptr = noret; + break; + } + global_fptr(); +} + +// Local variable assignment. + +[[noreturn]] void test_local_init() { + func_type func_ptr = noret; + func_ptr(); +} + +[[noreturn]] void test_local_assign() { + void (*func_ptr)(void); + func_ptr = noret; + func_ptr(); +} + +// 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}} + `````````` </details> https://github.com/llvm/llvm-project/pull/144408 _______________________________________________ cfe-commits mailing list cfe-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits