llvm-project
332 строки · 11.7 Кб
1//===--- InfiniteLoopCheck.cpp - clang-tidy -------------------------------===//
2//
3// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4// See https://llvm.org/LICENSE.txt for license information.
5// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6//
7//===----------------------------------------------------------------------===//
8
9#include "InfiniteLoopCheck.h"10#include "../utils/Aliasing.h"11#include "clang/AST/ASTContext.h"12#include "clang/ASTMatchers/ASTMatchFinder.h"13#include "clang/Analysis/Analyses/ExprMutationAnalyzer.h"14#include "clang/Analysis/CallGraph.h"15#include "llvm/ADT/SCCIterator.h"16#include "llvm/ADT/SmallVector.h"17
18using namespace clang::ast_matchers;19using clang::tidy::utils::hasPtrOrReferenceInFunc;20
21namespace clang {22namespace ast_matchers {23/// matches a Decl if it has a "no return" attribute of any kind
24AST_MATCHER(Decl, declHasNoReturnAttr) {25return Node.hasAttr<NoReturnAttr>() || Node.hasAttr<CXX11NoReturnAttr>() ||26Node.hasAttr<C11NoReturnAttr>();27}
28
29/// matches a FunctionType if the type includes the GNU no return attribute
30AST_MATCHER(FunctionType, typeHasNoReturnAttr) {31return Node.getNoReturnAttr();32}
33} // namespace ast_matchers34namespace tidy::bugprone {35
36static internal::Matcher<Stmt>37loopEndingStmt(internal::Matcher<Stmt> Internal) {38internal::Matcher<QualType> isNoReturnFunType =39ignoringParens(functionType(typeHasNoReturnAttr()));40internal::Matcher<Decl> isNoReturnDecl =41anyOf(declHasNoReturnAttr(), functionDecl(hasType(isNoReturnFunType)),42varDecl(hasType(blockPointerType(pointee(isNoReturnFunType)))));43
44return stmt(anyOf(45mapAnyOf(breakStmt, returnStmt, gotoStmt, cxxThrowExpr).with(Internal),46callExpr(Internal,47callee(mapAnyOf(functionDecl, /* block callee */ varDecl)48.with(isNoReturnDecl))),49objcMessageExpr(Internal, callee(isNoReturnDecl))));50}
51
52/// Return whether `Var` was changed in `LoopStmt`.
53static bool isChanged(const Stmt *LoopStmt, const VarDecl *Var,54ASTContext *Context) {55if (const auto *ForLoop = dyn_cast<ForStmt>(LoopStmt))56return (ForLoop->getInc() &&57ExprMutationAnalyzer(*ForLoop->getInc(), *Context)58.isMutated(Var)) ||59(ForLoop->getBody() &&60ExprMutationAnalyzer(*ForLoop->getBody(), *Context)61.isMutated(Var)) ||62(ForLoop->getCond() &&63ExprMutationAnalyzer(*ForLoop->getCond(), *Context).isMutated(Var));64
65return ExprMutationAnalyzer(*LoopStmt, *Context).isMutated(Var);66}
67
68/// Return whether `Cond` is a variable that is possibly changed in `LoopStmt`.
69static bool isVarThatIsPossiblyChanged(const Decl *Func, const Stmt *LoopStmt,70const Stmt *Cond, ASTContext *Context) {71if (const auto *DRE = dyn_cast<DeclRefExpr>(Cond)) {72if (const auto *Var = dyn_cast<VarDecl>(DRE->getDecl())) {73if (!Var->isLocalVarDeclOrParm())74return true;75
76if (Var->getType().isVolatileQualified())77return true;78
79if (!Var->getType().getTypePtr()->isIntegerType())80return true;81
82return hasPtrOrReferenceInFunc(Func, Var) ||83isChanged(LoopStmt, Var, Context);84// FIXME: Track references.85}86} else if (isa<MemberExpr, CallExpr,87ObjCIvarRefExpr, ObjCPropertyRefExpr, ObjCMessageExpr>(Cond)) {88// FIXME: Handle MemberExpr.89return true;90} else if (const auto *CE = dyn_cast<CastExpr>(Cond)) {91QualType T = CE->getType();92while (true) {93if (T.isVolatileQualified())94return true;95
96if (!T->isAnyPointerType() && !T->isReferenceType())97break;98
99T = T->getPointeeType();100}101}102
103return false;104}
105
106/// Return whether at least one variable of `Cond` changed in `LoopStmt`.
107static bool isAtLeastOneCondVarChanged(const Decl *Func, const Stmt *LoopStmt,108const Stmt *Cond, ASTContext *Context) {109if (isVarThatIsPossiblyChanged(Func, LoopStmt, Cond, Context))110return true;111
112for (const Stmt *Child : Cond->children()) {113if (!Child)114continue;115
116if (isAtLeastOneCondVarChanged(Func, LoopStmt, Child, Context))117return true;118}119return false;120}
121
122/// Return the variable names in `Cond`.
123static std::string getCondVarNames(const Stmt *Cond) {124if (const auto *DRE = dyn_cast<DeclRefExpr>(Cond)) {125if (const auto *Var = dyn_cast<VarDecl>(DRE->getDecl()))126return std::string(Var->getName());127}128
129std::string Result;130for (const Stmt *Child : Cond->children()) {131if (!Child)132continue;133
134std::string NewNames = getCondVarNames(Child);135if (!Result.empty() && !NewNames.empty())136Result += ", ";137Result += NewNames;138}139return Result;140}
141
142static bool isKnownToHaveValue(const Expr &Cond, const ASTContext &Ctx,143bool ExpectedValue) {144if (Cond.isValueDependent()) {145if (const auto *BinOp = dyn_cast<BinaryOperator>(&Cond)) {146// Conjunctions (disjunctions) can still be handled if at least one147// conjunct (disjunct) is known to be false (true).148if (!ExpectedValue && BinOp->getOpcode() == BO_LAnd)149return isKnownToHaveValue(*BinOp->getLHS(), Ctx, false) ||150isKnownToHaveValue(*BinOp->getRHS(), Ctx, false);151if (ExpectedValue && BinOp->getOpcode() == BO_LOr)152return isKnownToHaveValue(*BinOp->getLHS(), Ctx, true) ||153isKnownToHaveValue(*BinOp->getRHS(), Ctx, true);154if (BinOp->getOpcode() == BO_Comma)155return isKnownToHaveValue(*BinOp->getRHS(), Ctx, ExpectedValue);156} else if (const auto *UnOp = dyn_cast<UnaryOperator>(&Cond)) {157if (UnOp->getOpcode() == UO_LNot)158return isKnownToHaveValue(*UnOp->getSubExpr(), Ctx, !ExpectedValue);159} else if (const auto *Paren = dyn_cast<ParenExpr>(&Cond))160return isKnownToHaveValue(*Paren->getSubExpr(), Ctx, ExpectedValue);161else if (const auto *ImplCast = dyn_cast<ImplicitCastExpr>(&Cond))162return isKnownToHaveValue(*ImplCast->getSubExpr(), Ctx, ExpectedValue);163return false;164}165bool Result = false;166if (Cond.EvaluateAsBooleanCondition(Result, Ctx))167return Result == ExpectedValue;168return false;169}
170
171/// populates the set `Callees` with all function (and objc method) declarations
172/// called in `StmtNode` if all visited call sites have resolved call targets.
173///
174/// \return true iff all `CallExprs` visited have callees; false otherwise
175/// indicating there is an unresolved indirect call.
176static bool populateCallees(const Stmt *StmtNode,177llvm::SmallSet<const Decl *, 16> &Callees) {178if (const auto *Call = dyn_cast<CallExpr>(StmtNode)) {179const Decl *Callee = Call->getDirectCallee();180
181if (!Callee)182return false; // unresolved call183Callees.insert(Callee->getCanonicalDecl());184}185if (const auto *Call = dyn_cast<ObjCMessageExpr>(StmtNode)) {186const Decl *Callee = Call->getMethodDecl();187
188if (!Callee)189return false; // unresolved call190Callees.insert(Callee->getCanonicalDecl());191}192for (const Stmt *Child : StmtNode->children())193if (Child && !populateCallees(Child, Callees))194return false;195return true;196}
197
198/// returns true iff `SCC` contains `Func` and its' function set overlaps with
199/// `Callees`
200static bool overlap(ArrayRef<CallGraphNode *> SCC,201const llvm::SmallSet<const Decl *, 16> &Callees,202const Decl *Func) {203bool ContainsFunc = false, Overlap = false;204
205for (const CallGraphNode *GNode : SCC) {206const Decl *CanDecl = GNode->getDecl()->getCanonicalDecl();207
208ContainsFunc = ContainsFunc || (CanDecl == Func);209Overlap = Overlap || Callees.contains(CanDecl);210if (ContainsFunc && Overlap)211return true;212}213return false;214}
215
216/// returns true iff `Cond` involves at least one static local variable.
217static bool hasStaticLocalVariable(const Stmt *Cond) {218if (const auto *DRE = dyn_cast<DeclRefExpr>(Cond))219if (const auto *VD = dyn_cast<VarDecl>(DRE->getDecl()))220if (VD->isStaticLocal())221return true;222for (const Stmt *Child : Cond->children())223if (Child && hasStaticLocalVariable(Child))224return true;225return false;226}
227
228/// Tests if the loop condition `Cond` involves static local variables and
229/// the enclosing function `Func` is recursive.
230///
231/// \code
232/// void f() {
233/// static int i = 10;
234/// i--;
235/// while (i >= 0) f();
236/// }
237/// \endcode
238/// The example above is NOT an infinite loop.
239static bool hasRecursionOverStaticLoopCondVariables(const Expr *Cond,240const Stmt *LoopStmt,241const Decl *Func,242const ASTContext *Ctx) {243if (!hasStaticLocalVariable(Cond))244return false;245
246llvm::SmallSet<const Decl *, 16> CalleesInLoop;247
248if (!populateCallees(LoopStmt, CalleesInLoop)) {249// If there are unresolved indirect calls, we assume there could250// be recursion so to avoid false alarm.251return true;252}253if (CalleesInLoop.empty())254return false;255
256TranslationUnitDecl *TUDecl = Ctx->getTranslationUnitDecl();257CallGraph CG;258
259CG.addToCallGraph(TUDecl);260// For each `SCC` containing `Func`, if functions in the `SCC`261// overlap with `CalleesInLoop`, there is a recursive call in `LoopStmt`.262for (llvm::scc_iterator<CallGraph *> SCCI = llvm::scc_begin(&CG),263SCCE = llvm::scc_end(&CG);264SCCI != SCCE; ++SCCI) {265if (!SCCI.hasCycle()) // We only care about cycles, not standalone nodes.266continue;267// `SCC`s are mutually disjoint, so there will be no redundancy in268// comparing `SCC` with the callee set one by one.269if (overlap(*SCCI, CalleesInLoop, Func->getCanonicalDecl()))270return true;271}272return false;273}
274
275void InfiniteLoopCheck::registerMatchers(MatchFinder *Finder) {276const auto LoopCondition = allOf(277hasCondition(278expr(forCallable(decl().bind("func"))).bind("condition")),279unless(hasBody(hasDescendant(280loopEndingStmt(forCallable(equalsBoundNode("func")))))));281
282Finder->addMatcher(mapAnyOf(whileStmt, doStmt, forStmt)283.with(LoopCondition)284.bind("loop-stmt"),285this);286}
287
288void InfiniteLoopCheck::check(const MatchFinder::MatchResult &Result) {289const auto *Cond = Result.Nodes.getNodeAs<Expr>("condition");290const auto *LoopStmt = Result.Nodes.getNodeAs<Stmt>("loop-stmt");291const auto *Func = Result.Nodes.getNodeAs<Decl>("func");292
293if (isKnownToHaveValue(*Cond, *Result.Context, false))294return;295
296bool ShouldHaveConditionVariables = true;297if (const auto *While = dyn_cast<WhileStmt>(LoopStmt)) {298if (const VarDecl *LoopVarDecl = While->getConditionVariable()) {299if (const Expr *Init = LoopVarDecl->getInit()) {300ShouldHaveConditionVariables = false;301Cond = Init;302}303}304}305
306if (ExprMutationAnalyzer::isUnevaluated(LoopStmt, *LoopStmt, *Result.Context))307return;308
309if (isAtLeastOneCondVarChanged(Func, LoopStmt, Cond, Result.Context))310return;311if (hasRecursionOverStaticLoopCondVariables(Cond, LoopStmt, Func,312Result.Context))313return;314
315std::string CondVarNames = getCondVarNames(Cond);316if (ShouldHaveConditionVariables && CondVarNames.empty())317return;318
319if (CondVarNames.empty()) {320diag(LoopStmt->getBeginLoc(),321"this loop is infinite; it does not check any variables in the"322" condition");323} else {324diag(LoopStmt->getBeginLoc(),325"this loop is infinite; none of its condition variables (%0)"326" are updated in the loop body")327<< CondVarNames;328}329}
330
331} // namespace tidy::bugprone332} // namespace clang333