blob: 310fbec72a50086a126408cf3a88607e7f517314 [file] [log] [blame]
Fangrui Song3352bdf2019-09-24 09:06:31 +00001//===--- 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"
Adam Balogh4da65c22020-06-08 12:41:36 +020013#include "../utils/Aliasing.h"
Fangrui Song3352bdf2019-09-24 09:06:31 +000014
15using namespace clang::ast_matchers;
Adam Balogh4da65c22020-06-08 12:41:36 +020016using clang::tidy::utils::hasPtrOrReferenceInFunc;
Fangrui Song3352bdf2019-09-24 09:06:31 +000017
18namespace clang {
19namespace tidy {
20namespace bugprone {
21
22static internal::Matcher<Stmt>
23loopEndingStmt(internal::Matcher<Stmt> Internal) {
24 return stmt(anyOf(breakStmt(Internal), returnStmt(Internal),
25 gotoStmt(Internal), cxxThrowExpr(Internal),
26 callExpr(Internal, callee(functionDecl(isNoReturn())))));
27}
28
Adam Balogh1c571432019-10-02 07:14:11 +000029/// Return whether `Var` was changed in `LoopStmt`.
Fangrui Song3352bdf2019-09-24 09:06:31 +000030static bool isChanged(const Stmt *LoopStmt, const VarDecl *Var,
31 ASTContext *Context) {
32 if (const auto *ForLoop = dyn_cast<ForStmt>(LoopStmt))
33 return (ForLoop->getInc() &&
34 ExprMutationAnalyzer(*ForLoop->getInc(), *Context)
35 .isMutated(Var)) ||
36 (ForLoop->getBody() &&
37 ExprMutationAnalyzer(*ForLoop->getBody(), *Context)
38 .isMutated(Var)) ||
39 (ForLoop->getCond() &&
40 ExprMutationAnalyzer(*ForLoop->getCond(), *Context).isMutated(Var));
41
42 return ExprMutationAnalyzer(*LoopStmt, *Context).isMutated(Var);
43}
44
Adam Balogh1c571432019-10-02 07:14:11 +000045/// Return whether `Cond` is a variable that is possibly changed in `LoopStmt`.
Fangrui Song3352bdf2019-09-24 09:06:31 +000046static bool isVarThatIsPossiblyChanged(const FunctionDecl *Func,
47 const Stmt *LoopStmt, const Stmt *Cond,
48 ASTContext *Context) {
49 if (const auto *DRE = dyn_cast<DeclRefExpr>(Cond)) {
50 if (const auto *Var = dyn_cast<VarDecl>(DRE->getDecl())) {
51 if (!Var->isLocalVarDeclOrParm())
52 return true;
53
54 if (Var->getType().isVolatileQualified())
55 return true;
56
57 if (!Var->getType().getTypePtr()->isIntegerType())
58 return true;
59
60 return hasPtrOrReferenceInFunc(Func, Var) ||
61 isChanged(LoopStmt, Var, Context);
62 // FIXME: Track references.
63 }
64 } else if (isa<MemberExpr>(Cond) || isa<CallExpr>(Cond)) {
65 // FIXME: Handle MemberExpr.
66 return true;
67 }
68
69 return false;
70}
71
Adam Balogh1c571432019-10-02 07:14:11 +000072/// Return whether at least one variable of `Cond` changed in `LoopStmt`.
Fangrui Song3352bdf2019-09-24 09:06:31 +000073static bool isAtLeastOneCondVarChanged(const FunctionDecl *Func,
74 const Stmt *LoopStmt, const Stmt *Cond,
75 ASTContext *Context) {
76 if (isVarThatIsPossiblyChanged(Func, LoopStmt, Cond, Context))
77 return true;
78
79 for (const Stmt *Child : Cond->children()) {
80 if (!Child)
81 continue;
82
83 if (isAtLeastOneCondVarChanged(Func, LoopStmt, Child, Context))
84 return true;
85 }
86 return false;
87}
88
Adam Balogh1c571432019-10-02 07:14:11 +000089/// Return the variable names in `Cond`.
Fangrui Song3352bdf2019-09-24 09:06:31 +000090static std::string getCondVarNames(const Stmt *Cond) {
91 if (const auto *DRE = dyn_cast<DeclRefExpr>(Cond)) {
92 if (const auto *Var = dyn_cast<VarDecl>(DRE->getDecl()))
Benjamin Krameradcd0262020-01-28 20:23:46 +010093 return std::string(Var->getName());
Fangrui Song3352bdf2019-09-24 09:06:31 +000094 }
95
96 std::string Result;
97 for (const Stmt *Child : Cond->children()) {
98 if (!Child)
99 continue;
100
101 std::string NewNames = getCondVarNames(Child);
102 if (!Result.empty() && !NewNames.empty())
103 Result += ", ";
104 Result += NewNames;
105 }
106 return Result;
107}
108
Nathan Jamesc69ec642020-02-11 19:35:41 +0000109static bool isKnownFalse(const Expr &Cond, const ASTContext &Ctx) {
Nathan James8c4cf232020-02-13 20:20:37 +0000110 if (Cond.isValueDependent())
111 return false;
Nathan Jamesc69ec642020-02-11 19:35:41 +0000112 bool Result = false;
113 if (Cond.EvaluateAsBooleanCondition(Result, Ctx))
114 return !Result;
115 return false;
116}
117
Fangrui Song3352bdf2019-09-24 09:06:31 +0000118void InfiniteLoopCheck::registerMatchers(MatchFinder *Finder) {
119 const auto LoopCondition = allOf(
120 hasCondition(
121 expr(forFunction(functionDecl().bind("func"))).bind("condition")),
122 unless(hasBody(hasDescendant(
123 loopEndingStmt(forFunction(equalsBoundNode("func")))))));
124
125 Finder->addMatcher(stmt(anyOf(whileStmt(LoopCondition), doStmt(LoopCondition),
126 forStmt(LoopCondition)))
127 .bind("loop-stmt"),
128 this);
129}
130
131void InfiniteLoopCheck::check(const MatchFinder::MatchResult &Result) {
132 const auto *Cond = Result.Nodes.getNodeAs<Expr>("condition");
133 const auto *LoopStmt = Result.Nodes.getNodeAs<Stmt>("loop-stmt");
134 const auto *Func = Result.Nodes.getNodeAs<FunctionDecl>("func");
135
Nathan Jamesc69ec642020-02-11 19:35:41 +0000136 if (isKnownFalse(*Cond, *Result.Context))
137 return;
138
Adam Balogh70f4c6e2020-01-23 15:13:30 +0100139 bool ShouldHaveConditionVariables = true;
140 if (const auto *While = dyn_cast<WhileStmt>(LoopStmt)) {
141 if (const VarDecl *LoopVarDecl = While->getConditionVariable()) {
142 if (const Expr *Init = LoopVarDecl->getInit()) {
143 ShouldHaveConditionVariables = false;
144 Cond = Init;
145 }
146 }
147 }
148
Fangrui Song3352bdf2019-09-24 09:06:31 +0000149 if (isAtLeastOneCondVarChanged(Func, LoopStmt, Cond, Result.Context))
150 return;
151
152 std::string CondVarNames = getCondVarNames(Cond);
Adam Balogh70f4c6e2020-01-23 15:13:30 +0100153 if (ShouldHaveConditionVariables && CondVarNames.empty())
Fangrui Song3352bdf2019-09-24 09:06:31 +0000154 return;
155
Adam Balogh70f4c6e2020-01-23 15:13:30 +0100156 if (CondVarNames.empty()) {
157 diag(LoopStmt->getBeginLoc(),
158 "this loop is infinite; it does not check any variables in the"
159 " condition");
160 } else {
161 diag(LoopStmt->getBeginLoc(),
162 "this loop is infinite; none of its condition variables (%0)"
163 " are updated in the loop body")
Fangrui Song3352bdf2019-09-24 09:06:31 +0000164 << CondVarNames;
Adam Balogh70f4c6e2020-01-23 15:13:30 +0100165 }
Fangrui Song3352bdf2019-09-24 09:06:31 +0000166}
167
168} // namespace bugprone
169} // namespace tidy
170} // namespace clang