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