Skip to content

Commit be9fc41

Browse files
committed
[clang-tidy] bugprone-infinite-loop: Add support for tuple structured bindings
Tuple structured bindings introduce implicit variable declarations under `BindingDecl` nodes which are currently ignored by the infinite loop checker. This PR adds support for these bindings to this checker through the following changes: 1. The pattern matcher in `ExprMutationAnalyzer` has been updated to match against `DeclRefExpr` nodes that refer to these implicit variables via `BindingDecl` nodes. 2. Enumeration of a loop's condition's variables for mutation analysis has been updated to recognize these implicit variables so they can be checked for mutation. 3. Enumeration of the names of a loop's condition's variables for error reporting has been similarly updated. The changes have been tested against a mock tuple implementation lifted from `clang/unittests/Analysis/FlowSensitive/TransferTest.cpp`
1 parent fdec9fd commit be9fc41

File tree

4 files changed

+126
-23
lines changed

4 files changed

+126
-23
lines changed

clang-tools-extra/clang-tidy/bugprone/InfiniteLoopCheck.cpp

Lines changed: 49 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -64,24 +64,53 @@ static bool isChanged(const Stmt *LoopStmt, const VarDecl *Var,
6464
return ExprMutationAnalyzer(*LoopStmt, *Context).isMutated(Var);
6565
}
6666

67+
bool isVolatileOrNonIntegerType(QualType QT) {
68+
69+
if (QT.isVolatileQualified())
70+
return true;
71+
72+
const Type *T = QT.getTypePtr();
73+
74+
if (T->isIntegerType())
75+
return false;
76+
77+
if (T->isRValueReferenceType()) {
78+
QT = dyn_cast<RValueReferenceType>(T)->getPointeeType();
79+
return isVolatileOrNonIntegerType(QT);
80+
}
81+
82+
return true;
83+
}
84+
85+
static bool isVarDeclPossiblyChanged(const Decl *Func, const Stmt *LoopStmt,
86+
const VarDecl *Var, ASTContext *Context) {
87+
if (!Var->isLocalVarDeclOrParm())
88+
return true;
89+
90+
if (isVolatileOrNonIntegerType(Var->getType()))
91+
return true;
92+
93+
return hasPtrOrReferenceInFunc(Func, Var) ||
94+
isChanged(LoopStmt, Var, Context);
95+
}
96+
6797
/// Return whether `Cond` is a variable that is possibly changed in `LoopStmt`.
6898
static bool isVarThatIsPossiblyChanged(const Decl *Func, const Stmt *LoopStmt,
6999
const Stmt *Cond, ASTContext *Context) {
70100
if (const auto *DRE = dyn_cast<DeclRefExpr>(Cond)) {
71-
if (const auto *Var = dyn_cast<VarDecl>(DRE->getDecl())) {
72-
if (!Var->isLocalVarDeclOrParm())
73-
return true;
101+
const ValueDecl *VD = DRE->getDecl();
74102

75-
if (Var->getType().isVolatileQualified())
76-
return true;
77-
78-
if (!Var->getType().getTypePtr()->isIntegerType())
79-
return true;
103+
if (const auto *Var = dyn_cast<VarDecl>(VD)) {
104+
return isVarDeclPossiblyChanged(Func, LoopStmt, Var, Context);
105+
}
80106

81-
return hasPtrOrReferenceInFunc(Func, Var) ||
82-
isChanged(LoopStmt, Var, Context);
83-
// FIXME: Track references.
107+
if (const auto *BD = dyn_cast<BindingDecl>(VD)) {
108+
if (const auto *Var = BD->getHoldingVar()) {
109+
return isVarDeclPossiblyChanged(Func, LoopStmt, Var, Context);
110+
}
84111
}
112+
113+
// FIXME: Track references.
85114
} else if (isa<MemberExpr, CallExpr, ObjCIvarRefExpr, ObjCPropertyRefExpr,
86115
ObjCMessageExpr>(Cond)) {
87116
// FIXME: Handle MemberExpr.
@@ -121,8 +150,16 @@ static bool isAtLeastOneCondVarChanged(const Decl *Func, const Stmt *LoopStmt,
121150
/// Return the variable names in `Cond`.
122151
static std::string getCondVarNames(const Stmt *Cond) {
123152
if (const auto *DRE = dyn_cast<DeclRefExpr>(Cond)) {
124-
if (const auto *Var = dyn_cast<VarDecl>(DRE->getDecl()))
153+
const ValueDecl *VD = DRE->getDecl();
154+
155+
if (const auto *Var = dyn_cast<VarDecl>(VD))
125156
return std::string(Var->getName());
157+
158+
if (const auto *BD = dyn_cast<BindingDecl>(VD)) {
159+
if (const auto *Var = BD->getHoldingVar()) {
160+
return Var->getName().str();
161+
}
162+
}
126163
}
127164

128165
std::string Result;

clang-tools-extra/test/clang-tidy/checkers/bugprone/infinite-loop.cpp

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -592,6 +592,53 @@ void test_structured_bindings_bad() {
592592
}
593593
}
594594

595+
namespace std {
596+
using size_t = int;
597+
template <class> struct tuple_size;
598+
template <std::size_t, class> struct tuple_element;
599+
template <class...> class tuple;
600+
601+
namespace {
602+
template <class T, T v>
603+
struct size_helper { static const T value = v; };
604+
} // namespace
605+
606+
template <class... T>
607+
struct tuple_size<tuple<T...>> : size_helper<std::size_t, sizeof...(T)> {};
608+
609+
template <std::size_t I, class... T>
610+
struct tuple_element<I, tuple<T...>> {
611+
using type = __type_pack_element<I, T...>;
612+
};
613+
614+
template <class...> class tuple {};
615+
616+
template <std::size_t I, class... T>
617+
typename tuple_element<I, tuple<T...>>::type get(tuple<T...>);
618+
} // namespace std
619+
620+
std::tuple<int*, int> &get_chunk();
621+
622+
void test_structured_bindings_tuple() {
623+
auto [buffer, size ] = get_chunk();
624+
int maxLen = 8;
625+
626+
while (size < maxLen) {
627+
// No warning. The loop is finite because 'size' is being incremented in each iteration and compared against 'maxLen' for termination
628+
buffer[size++] = 2;
629+
}
630+
}
631+
632+
void test_structured_bindings_tuple_ref() {
633+
auto& [buffer, size ] = get_chunk();
634+
int maxLen = 8;
635+
636+
while (size < maxLen) {
637+
// No warning. The loop is finite because 'size' is being incremented in each iteration and compared against 'maxLen' for termination
638+
buffer[size++] = 2;
639+
}
640+
}
641+
595642
void test_volatile_cast() {
596643
// This is a no-op cast. Clang ignores the qualifier, we should too.
597644
for (int i = 0; (volatile int)i < 10;) {

clang/include/clang/Analysis/Analyses/ExprMutationAnalyzer.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,8 @@ class ExprMutationAnalyzer {
5454
const Stmt *findMutationMemoized(const Expr *Exp,
5555
llvm::ArrayRef<MutationFinder> Finders,
5656
Memoized::ResultMap &MemoizedResults);
57+
const ast_matchers::internal::BindableMatcher<Stmt>
58+
makeDeclRefExprMatcher(const Decl *Dec);
5759
const Stmt *tryEachDeclRef(const Decl *Dec, MutationFinder Finder);
5860

5961
const Stmt *findExprMutation(ArrayRef<ast_matchers::BoundNodes> Matches);

clang/lib/Analysis/ExprMutationAnalyzer.cpp

Lines changed: 28 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,14 @@ AST_MATCHER_P(Stmt, canResolveToExpr, const Stmt *, Inner) {
113113
return canExprResolveTo(Exp, Target);
114114
}
115115

116+
AST_MATCHER_P(BindingDecl, hasHoldingVar,
117+
ast_matchers::internal::Matcher<VarDecl>, InnerMatcher) {
118+
if (const VarDecl *HoldingVar = Node.getHoldingVar()) {
119+
return InnerMatcher.matches(*HoldingVar, Finder, Builder);
120+
}
121+
return false;
122+
}
123+
116124
// use class member to store data can reduce stack usage to avoid stack overflow
117125
// when recursive call.
118126
class ExprPointeeResolve {
@@ -310,21 +318,30 @@ const Stmt *ExprMutationAnalyzer::Analyzer::findMutationMemoized(
310318
return nullptr;
311319
}
312320

321+
const ast_matchers::internal::BindableMatcher<Stmt>
322+
ExprMutationAnalyzer::Analyzer::makeDeclRefExprMatcher(const Decl *Dec) {
323+
324+
// For VarDecl created implicitly via structured bindings, create a matcher
325+
// for DeclRefExpr nodes which refer to this VarDecl via BindingDecl nodes.
326+
if (const auto *VD = dyn_cast<VarDecl>(Dec)) {
327+
if (VD->isImplicit()) {
328+
return declRefExpr(to(bindingDecl(hasHoldingVar(equalsNode(VD)))));
329+
}
330+
}
331+
332+
return declRefExpr(to(
333+
// `Dec` or a binding if `Dec` is a decomposition.
334+
anyOf(equalsNode(Dec), bindingDecl(forDecomposition(equalsNode(Dec))))));
335+
}
336+
313337
const Stmt *
314338
ExprMutationAnalyzer::Analyzer::tryEachDeclRef(const Decl *Dec,
315339
MutationFinder Finder) {
316-
const auto Refs = match(
317-
findAll(
318-
declRefExpr(to(
319-
// `Dec` or a binding if `Dec` is a decomposition.
320-
anyOf(equalsNode(Dec),
321-
bindingDecl(forDecomposition(equalsNode(Dec))))
322-
//
323-
))
324-
.bind(NodeID<Expr>::value)),
325-
Stm, Context);
340+
const auto matcher = makeDeclRefExprMatcher(Dec);
341+
const auto nodeId = NodeID<Expr>::value;
342+
const auto Refs = match(findAll(matcher.bind(nodeId)), Stm, Context);
326343
for (const auto &RefNodes : Refs) {
327-
const auto *E = RefNodes.getNodeAs<Expr>(NodeID<Expr>::value);
344+
const auto *E = RefNodes.getNodeAs<Expr>(nodeId);
328345
if ((this->*Finder)(E))
329346
return E;
330347
}

0 commit comments

Comments
 (0)