Fangrui Song | 3352bdf | 2019-09-24 09:06:31 +0000 | [diff] [blame] | 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 "clang/AST/ASTContext.h" |
| 11 | #include "clang/ASTMatchers/ASTMatchFinder.h" |
| 12 | #include "clang/Analysis/Analyses/ExprMutationAnalyzer.h" |
| 13 | |
| 14 | using namespace clang::ast_matchers; |
| 15 | |
| 16 | namespace clang { |
| 17 | namespace tidy { |
| 18 | namespace bugprone { |
| 19 | |
| 20 | static internal::Matcher<Stmt> |
| 21 | loopEndingStmt(internal::Matcher<Stmt> Internal) { |
| 22 | return stmt(anyOf(breakStmt(Internal), returnStmt(Internal), |
| 23 | gotoStmt(Internal), cxxThrowExpr(Internal), |
| 24 | callExpr(Internal, callee(functionDecl(isNoReturn()))))); |
| 25 | } |
| 26 | |
Adam Balogh | 1c57143 | 2019-10-02 07:14:11 +0000 | [diff] [blame] | 27 | /// Return whether `S` is a reference to the declaration of `Var`. |
Fangrui Song | 3352bdf | 2019-09-24 09:06:31 +0000 | [diff] [blame] | 28 | static bool isAccessForVar(const Stmt *S, const VarDecl *Var) { |
| 29 | if (const auto *DRE = dyn_cast<DeclRefExpr>(S)) |
| 30 | return DRE->getDecl() == Var; |
| 31 | |
| 32 | return false; |
| 33 | } |
| 34 | |
Adam Balogh | 1c57143 | 2019-10-02 07:14:11 +0000 | [diff] [blame] | 35 | /// Return whether `Var` has a pointer or reference in `S`. |
Fangrui Song | 3352bdf | 2019-09-24 09:06:31 +0000 | [diff] [blame] | 36 | static bool isPtrOrReferenceForVar(const Stmt *S, const VarDecl *Var) { |
| 37 | if (const auto *DS = dyn_cast<DeclStmt>(S)) { |
| 38 | for (const Decl *D : DS->getDeclGroup()) { |
| 39 | if (const auto *LeftVar = dyn_cast<VarDecl>(D)) { |
| 40 | if (LeftVar->hasInit() && LeftVar->getType()->isReferenceType()) { |
| 41 | return isAccessForVar(LeftVar->getInit(), Var); |
| 42 | } |
| 43 | } |
| 44 | } |
| 45 | } else if (const auto *UnOp = dyn_cast<UnaryOperator>(S)) { |
| 46 | if (UnOp->getOpcode() == UO_AddrOf) |
| 47 | return isAccessForVar(UnOp->getSubExpr(), Var); |
| 48 | } |
| 49 | |
| 50 | return false; |
| 51 | } |
| 52 | |
Adam Balogh | 1c57143 | 2019-10-02 07:14:11 +0000 | [diff] [blame] | 53 | /// Return whether `Var` has a pointer or reference in `S`. |
Fangrui Song | 3352bdf | 2019-09-24 09:06:31 +0000 | [diff] [blame] | 54 | static bool hasPtrOrReferenceInStmt(const Stmt *S, const VarDecl *Var) { |
| 55 | if (isPtrOrReferenceForVar(S, Var)) |
| 56 | return true; |
| 57 | |
| 58 | for (const Stmt *Child : S->children()) { |
| 59 | if (!Child) |
| 60 | continue; |
| 61 | |
| 62 | if (hasPtrOrReferenceInStmt(Child, Var)) |
| 63 | return true; |
| 64 | } |
| 65 | |
| 66 | return false; |
| 67 | } |
| 68 | |
Adam Balogh | 1c57143 | 2019-10-02 07:14:11 +0000 | [diff] [blame] | 69 | /// Return whether `Var` has a pointer or reference in `Func`. |
Fangrui Song | 3352bdf | 2019-09-24 09:06:31 +0000 | [diff] [blame] | 70 | static bool hasPtrOrReferenceInFunc(const FunctionDecl *Func, |
| 71 | const VarDecl *Var) { |
| 72 | return hasPtrOrReferenceInStmt(Func->getBody(), Var); |
| 73 | } |
| 74 | |
Adam Balogh | 1c57143 | 2019-10-02 07:14:11 +0000 | [diff] [blame] | 75 | /// Return whether `Var` was changed in `LoopStmt`. |
Fangrui Song | 3352bdf | 2019-09-24 09:06:31 +0000 | [diff] [blame] | 76 | static bool isChanged(const Stmt *LoopStmt, const VarDecl *Var, |
| 77 | ASTContext *Context) { |
| 78 | if (const auto *ForLoop = dyn_cast<ForStmt>(LoopStmt)) |
| 79 | return (ForLoop->getInc() && |
| 80 | ExprMutationAnalyzer(*ForLoop->getInc(), *Context) |
| 81 | .isMutated(Var)) || |
| 82 | (ForLoop->getBody() && |
| 83 | ExprMutationAnalyzer(*ForLoop->getBody(), *Context) |
| 84 | .isMutated(Var)) || |
| 85 | (ForLoop->getCond() && |
| 86 | ExprMutationAnalyzer(*ForLoop->getCond(), *Context).isMutated(Var)); |
| 87 | |
| 88 | return ExprMutationAnalyzer(*LoopStmt, *Context).isMutated(Var); |
| 89 | } |
| 90 | |
Adam Balogh | 1c57143 | 2019-10-02 07:14:11 +0000 | [diff] [blame] | 91 | /// Return whether `Cond` is a variable that is possibly changed in `LoopStmt`. |
Fangrui Song | 3352bdf | 2019-09-24 09:06:31 +0000 | [diff] [blame] | 92 | static bool isVarThatIsPossiblyChanged(const FunctionDecl *Func, |
| 93 | const Stmt *LoopStmt, const Stmt *Cond, |
| 94 | ASTContext *Context) { |
| 95 | if (const auto *DRE = dyn_cast<DeclRefExpr>(Cond)) { |
| 96 | if (const auto *Var = dyn_cast<VarDecl>(DRE->getDecl())) { |
| 97 | if (!Var->isLocalVarDeclOrParm()) |
| 98 | return true; |
| 99 | |
| 100 | if (Var->getType().isVolatileQualified()) |
| 101 | return true; |
| 102 | |
| 103 | if (!Var->getType().getTypePtr()->isIntegerType()) |
| 104 | return true; |
| 105 | |
| 106 | return hasPtrOrReferenceInFunc(Func, Var) || |
| 107 | isChanged(LoopStmt, Var, Context); |
| 108 | // FIXME: Track references. |
| 109 | } |
| 110 | } else if (isa<MemberExpr>(Cond) || isa<CallExpr>(Cond)) { |
| 111 | // FIXME: Handle MemberExpr. |
| 112 | return true; |
| 113 | } |
| 114 | |
| 115 | return false; |
| 116 | } |
| 117 | |
Adam Balogh | 1c57143 | 2019-10-02 07:14:11 +0000 | [diff] [blame] | 118 | /// Return whether at least one variable of `Cond` changed in `LoopStmt`. |
Fangrui Song | 3352bdf | 2019-09-24 09:06:31 +0000 | [diff] [blame] | 119 | static bool isAtLeastOneCondVarChanged(const FunctionDecl *Func, |
| 120 | const Stmt *LoopStmt, const Stmt *Cond, |
| 121 | ASTContext *Context) { |
| 122 | if (isVarThatIsPossiblyChanged(Func, LoopStmt, Cond, Context)) |
| 123 | return true; |
| 124 | |
| 125 | for (const Stmt *Child : Cond->children()) { |
| 126 | if (!Child) |
| 127 | continue; |
| 128 | |
| 129 | if (isAtLeastOneCondVarChanged(Func, LoopStmt, Child, Context)) |
| 130 | return true; |
| 131 | } |
| 132 | return false; |
| 133 | } |
| 134 | |
Adam Balogh | 1c57143 | 2019-10-02 07:14:11 +0000 | [diff] [blame] | 135 | /// Return the variable names in `Cond`. |
Fangrui Song | 3352bdf | 2019-09-24 09:06:31 +0000 | [diff] [blame] | 136 | static std::string getCondVarNames(const Stmt *Cond) { |
| 137 | if (const auto *DRE = dyn_cast<DeclRefExpr>(Cond)) { |
| 138 | if (const auto *Var = dyn_cast<VarDecl>(DRE->getDecl())) |
Benjamin Kramer | adcd026 | 2020-01-28 20:23:46 +0100 | [diff] [blame] | 139 | return std::string(Var->getName()); |
Fangrui Song | 3352bdf | 2019-09-24 09:06:31 +0000 | [diff] [blame] | 140 | } |
| 141 | |
| 142 | std::string Result; |
| 143 | for (const Stmt *Child : Cond->children()) { |
| 144 | if (!Child) |
| 145 | continue; |
| 146 | |
| 147 | std::string NewNames = getCondVarNames(Child); |
| 148 | if (!Result.empty() && !NewNames.empty()) |
| 149 | Result += ", "; |
| 150 | Result += NewNames; |
| 151 | } |
| 152 | return Result; |
| 153 | } |
| 154 | |
Nathan James | c69ec64 | 2020-02-11 19:35:41 +0000 | [diff] [blame] | 155 | static bool isKnownFalse(const Expr &Cond, const ASTContext &Ctx) { |
Nathan James | 8c4cf23 | 2020-02-13 20:20:37 +0000 | [diff] [blame] | 156 | if (Cond.isValueDependent()) |
| 157 | return false; |
Nathan James | c69ec64 | 2020-02-11 19:35:41 +0000 | [diff] [blame] | 158 | bool Result = false; |
| 159 | if (Cond.EvaluateAsBooleanCondition(Result, Ctx)) |
| 160 | return !Result; |
| 161 | return false; |
| 162 | } |
| 163 | |
Fangrui Song | 3352bdf | 2019-09-24 09:06:31 +0000 | [diff] [blame] | 164 | void InfiniteLoopCheck::registerMatchers(MatchFinder *Finder) { |
| 165 | const auto LoopCondition = allOf( |
| 166 | hasCondition( |
| 167 | expr(forFunction(functionDecl().bind("func"))).bind("condition")), |
| 168 | unless(hasBody(hasDescendant( |
| 169 | loopEndingStmt(forFunction(equalsBoundNode("func"))))))); |
| 170 | |
| 171 | Finder->addMatcher(stmt(anyOf(whileStmt(LoopCondition), doStmt(LoopCondition), |
| 172 | forStmt(LoopCondition))) |
| 173 | .bind("loop-stmt"), |
| 174 | this); |
| 175 | } |
| 176 | |
| 177 | void InfiniteLoopCheck::check(const MatchFinder::MatchResult &Result) { |
| 178 | const auto *Cond = Result.Nodes.getNodeAs<Expr>("condition"); |
| 179 | const auto *LoopStmt = Result.Nodes.getNodeAs<Stmt>("loop-stmt"); |
| 180 | const auto *Func = Result.Nodes.getNodeAs<FunctionDecl>("func"); |
| 181 | |
Nathan James | c69ec64 | 2020-02-11 19:35:41 +0000 | [diff] [blame] | 182 | if (isKnownFalse(*Cond, *Result.Context)) |
| 183 | return; |
| 184 | |
Adam Balogh | 70f4c6e | 2020-01-23 15:13:30 +0100 | [diff] [blame] | 185 | bool ShouldHaveConditionVariables = true; |
| 186 | if (const auto *While = dyn_cast<WhileStmt>(LoopStmt)) { |
| 187 | if (const VarDecl *LoopVarDecl = While->getConditionVariable()) { |
| 188 | if (const Expr *Init = LoopVarDecl->getInit()) { |
| 189 | ShouldHaveConditionVariables = false; |
| 190 | Cond = Init; |
| 191 | } |
| 192 | } |
| 193 | } |
| 194 | |
Fangrui Song | 3352bdf | 2019-09-24 09:06:31 +0000 | [diff] [blame] | 195 | if (isAtLeastOneCondVarChanged(Func, LoopStmt, Cond, Result.Context)) |
| 196 | return; |
| 197 | |
| 198 | std::string CondVarNames = getCondVarNames(Cond); |
Adam Balogh | 70f4c6e | 2020-01-23 15:13:30 +0100 | [diff] [blame] | 199 | if (ShouldHaveConditionVariables && CondVarNames.empty()) |
Fangrui Song | 3352bdf | 2019-09-24 09:06:31 +0000 | [diff] [blame] | 200 | return; |
| 201 | |
Adam Balogh | 70f4c6e | 2020-01-23 15:13:30 +0100 | [diff] [blame] | 202 | if (CondVarNames.empty()) { |
| 203 | diag(LoopStmt->getBeginLoc(), |
| 204 | "this loop is infinite; it does not check any variables in the" |
| 205 | " condition"); |
| 206 | } else { |
| 207 | diag(LoopStmt->getBeginLoc(), |
| 208 | "this loop is infinite; none of its condition variables (%0)" |
| 209 | " are updated in the loop body") |
Fangrui Song | 3352bdf | 2019-09-24 09:06:31 +0000 | [diff] [blame] | 210 | << CondVarNames; |
Adam Balogh | 70f4c6e | 2020-01-23 15:13:30 +0100 | [diff] [blame] | 211 | } |
Fangrui Song | 3352bdf | 2019-09-24 09:06:31 +0000 | [diff] [blame] | 212 | } |
| 213 | |
| 214 | } // namespace bugprone |
| 215 | } // namespace tidy |
| 216 | } // namespace clang |