diff --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst index 8ba493b2ca89b..0f56b7369e138 100644 --- a/clang/docs/ReleaseNotes.rst +++ b/clang/docs/ReleaseNotes.rst @@ -702,6 +702,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 ec8acbdff3b49..c50e951842547 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(E->IgnoreParenCasts())) + if (auto *FD = dyn_cast(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(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 { + const VarDecl *Var; + std::optional 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(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(UO->getSubExpr()->IgnoreParenCasts())) + if (DRef->getDecl() == Var) + AllValuesAreNoReturn = false; + } + } + + void VisitBinaryOperator(BinaryOperator *BO) { + if (BO->getOpcode() == BO_Assign) + if (auto *DRef = dyn_cast(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(Arg->IgnoreParenCasts())) + if (auto VD = dyn_cast(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(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>; + 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 cs = ri->getAs()) { + const Stmt *S = cs->getStmt(); + TF.reset(); + TF.Visit(const_cast(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(S)) { + const Expr *Callee = Call->getCallee(); + if (Callee->getType()->isPointerType()) + if (auto *DeclRef = + dyn_cast(Callee->IgnoreParenImpCasts())) + if (auto *VD = dyn_cast(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}}