Skip to content

Commit 5f01da1

Browse files
aviralgflovent
andauthored
[CherryPick][clang-tidy] Improve bugprone-infinite-loop check by adding handing for structured bindings (llvm#144213) (#11104)
Before this patch, this check only handles `VarDecl` as varaibles declaration in statement, but this will ignore variables in structured bindings (`BindingDecl` in AST), which leads to false positives. rdar://152083639 Co-authored-by: flovent <[email protected]>
1 parent 20e32d1 commit 5f01da1

File tree

5 files changed

+254
-26
lines changed

5 files changed

+254
-26
lines changed

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

Lines changed: 40 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ loopEndingStmt(internal::Matcher<Stmt> Internal) {
5050
}
5151

5252
/// Return whether `Var` was changed in `LoopStmt`.
53-
static bool isChanged(const Stmt *LoopStmt, const VarDecl *Var,
53+
static bool isChanged(const Stmt *LoopStmt, const ValueDecl *Var,
5454
ASTContext *Context) {
5555
if (const auto *ForLoop = dyn_cast<ForStmt>(LoopStmt))
5656
return (ForLoop->getInc() &&
@@ -65,26 +65,37 @@ static bool isChanged(const Stmt *LoopStmt, const VarDecl *Var,
6565
return ExprMutationAnalyzer(*LoopStmt, *Context).isMutated(Var);
6666
}
6767

68+
static bool isVarPossiblyChanged(const Decl *Func, const Stmt *LoopStmt,
69+
const ValueDecl *VD, ASTContext *Context) {
70+
const VarDecl *Var = nullptr;
71+
if (const auto *VarD = dyn_cast<VarDecl>(VD)) {
72+
Var = VarD;
73+
} else if (const auto *BD = dyn_cast<BindingDecl>(VD)) {
74+
if (const auto *DD = dyn_cast<DecompositionDecl>(BD->getDecomposedDecl()))
75+
Var = DD;
76+
}
77+
78+
if (!Var)
79+
return false;
80+
81+
if (!Var->isLocalVarDeclOrParm() || Var->getType().isVolatileQualified())
82+
return true;
83+
84+
if (!VD->getType().getTypePtr()->isIntegerType())
85+
return true;
86+
87+
return hasPtrOrReferenceInFunc(Func, VD) || isChanged(LoopStmt, VD, Context);
88+
// FIXME: Track references.
89+
}
90+
6891
/// Return whether `Cond` is a variable that is possibly changed in `LoopStmt`.
6992
static bool isVarThatIsPossiblyChanged(const Decl *Func, const Stmt *LoopStmt,
7093
const Stmt *Cond, ASTContext *Context) {
7194
if (const auto *DRE = dyn_cast<DeclRefExpr>(Cond)) {
72-
if (const auto *Var = dyn_cast<VarDecl>(DRE->getDecl())) {
73-
if (!Var->isLocalVarDeclOrParm())
74-
return true;
75-
76-
if (Var->getType().isVolatileQualified())
77-
return true;
78-
79-
if (!Var->getType().getTypePtr()->isIntegerType())
80-
return true;
81-
82-
return hasPtrOrReferenceInFunc(Func, Var) ||
83-
isChanged(LoopStmt, Var, Context);
84-
// FIXME: Track references.
85-
}
86-
} else if (isa<MemberExpr, CallExpr,
87-
ObjCIvarRefExpr, ObjCPropertyRefExpr, ObjCMessageExpr>(Cond)) {
95+
if (const auto *VD = dyn_cast<ValueDecl>(DRE->getDecl()))
96+
return isVarPossiblyChanged(Func, LoopStmt, VD, Context);
97+
} else if (isa<MemberExpr, CallExpr, ObjCIvarRefExpr, ObjCPropertyRefExpr,
98+
ObjCMessageExpr>(Cond)) {
8899
// FIXME: Handle MemberExpr.
89100
return true;
90101
} else if (const auto *CE = dyn_cast<CastExpr>(Cond)) {
@@ -124,6 +135,10 @@ static std::string getCondVarNames(const Stmt *Cond) {
124135
if (const auto *DRE = dyn_cast<DeclRefExpr>(Cond)) {
125136
if (const auto *Var = dyn_cast<VarDecl>(DRE->getDecl()))
126137
return std::string(Var->getName());
138+
139+
if (const auto *BD = dyn_cast<BindingDecl>(DRE->getDecl())) {
140+
return std::string(BD->getName());
141+
}
127142
}
128143

129144
std::string Result;
@@ -215,10 +230,17 @@ static bool overlap(ArrayRef<CallGraphNode *> SCC,
215230

216231
/// returns true iff `Cond` involves at least one static local variable.
217232
static bool hasStaticLocalVariable(const Stmt *Cond) {
218-
if (const auto *DRE = dyn_cast<DeclRefExpr>(Cond))
233+
if (const auto *DRE = dyn_cast<DeclRefExpr>(Cond)) {
219234
if (const auto *VD = dyn_cast<VarDecl>(DRE->getDecl()))
220235
if (VD->isStaticLocal())
221236
return true;
237+
238+
if (const auto *BD = dyn_cast<BindingDecl>(DRE->getDecl()))
239+
if (const auto *DD = dyn_cast<DecompositionDecl>(BD->getDecomposedDecl()))
240+
if (DD->isStaticLocal())
241+
return true;
242+
}
243+
222244
for (const Stmt *Child : Cond->children())
223245
if (Child && hasStaticLocalVariable(Child))
224246
return true;

clang-tools-extra/clang-tidy/utils/Aliasing.cpp

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -14,24 +14,24 @@
1414
namespace clang::tidy::utils {
1515

1616
/// Return whether \p S is a reference to the declaration of \p Var.
17-
static bool isAccessForVar(const Stmt *S, const VarDecl *Var) {
17+
static bool isAccessForVar(const Stmt *S, const ValueDecl *Var) {
1818
if (const auto *DRE = dyn_cast<DeclRefExpr>(S))
1919
return DRE->getDecl() == Var;
2020

2121
return false;
2222
}
2323

24-
static bool capturesByRef(const CXXRecordDecl *RD, const VarDecl *Var) {
24+
static bool capturesByRef(const CXXRecordDecl *RD, const ValueDecl *Var) {
2525
return llvm::any_of(RD->captures(), [Var](const LambdaCapture &C) {
2626
return C.capturesVariable() && C.getCaptureKind() == LCK_ByRef &&
2727
C.getCapturedVar() == Var;
2828
});
2929
}
3030

3131
/// Return whether \p Var has a pointer or reference in \p S.
32-
static bool isPtrOrReferenceForVar(const Stmt *S, const VarDecl *Var) {
32+
static bool isPtrOrReferenceForVar(const Stmt *S, const ValueDecl *Var) {
3333
// Treat block capture by reference as a form of taking a reference.
34-
if (Var->isEscapingByref())
34+
if (const auto *VD = dyn_cast<VarDecl>(Var); VD && VD->isEscapingByref())
3535
return true;
3636

3737
if (const auto *DS = dyn_cast<DeclStmt>(S)) {
@@ -61,7 +61,7 @@ static bool isPtrOrReferenceForVar(const Stmt *S, const VarDecl *Var) {
6161
}
6262

6363
/// Return whether \p Var has a pointer or reference in \p S.
64-
static bool hasPtrOrReferenceInStmt(const Stmt *S, const VarDecl *Var) {
64+
static bool hasPtrOrReferenceInStmt(const Stmt *S, const ValueDecl *Var) {
6565
if (isPtrOrReferenceForVar(S, Var))
6666
return true;
6767

@@ -77,7 +77,7 @@ static bool hasPtrOrReferenceInStmt(const Stmt *S, const VarDecl *Var) {
7777
}
7878

7979
static bool refersToEnclosingLambdaCaptureByRef(const Decl *Func,
80-
const VarDecl *Var) {
80+
const ValueDecl *Var) {
8181
const auto *MD = dyn_cast<CXXMethodDecl>(Func);
8282
if (!MD)
8383
return false;
@@ -89,7 +89,7 @@ static bool refersToEnclosingLambdaCaptureByRef(const Decl *Func,
8989
return capturesByRef(RD, Var);
9090
}
9191

92-
bool hasPtrOrReferenceInFunc(const Decl *Func, const VarDecl *Var) {
92+
bool hasPtrOrReferenceInFunc(const Decl *Func, const ValueDecl *Var) {
9393
return hasPtrOrReferenceInStmt(Func->getBody(), Var) ||
9494
refersToEnclosingLambdaCaptureByRef(Func, Var);
9595
}

clang-tools-extra/clang-tidy/utils/Aliasing.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ namespace clang::tidy::utils {
2525
/// For `f()` and `n` the function returns ``true`` because `p` is a
2626
/// pointer to `n` created in `f()`.
2727

28-
bool hasPtrOrReferenceInFunc(const Decl *Func, const VarDecl *Var);
28+
bool hasPtrOrReferenceInFunc(const Decl *Func, const ValueDecl *Var);
2929

3030
} // namespace clang::tidy::utils
3131

clang-tools-extra/docs/ReleaseNotes.rst

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -564,6 +564,10 @@ Changes in existing checks
564564
usages of ``std::string_view::compare``. Added a `StringLikeClasses` option
565565
to detect usages of ``compare`` method in custom string-like classes.
566566

567+
- Improved :doc:`bugprone-infinite-loop
568+
<clang-tidy/checks/bugprone/infinite-loop>` check by adding detection for
569+
variables introduced by structured bindings.
570+
567571
Removed checks
568572
^^^^^^^^^^^^^^
569573

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

Lines changed: 202 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -711,3 +711,205 @@ void test_local_static_recursion() {
711711
while (i >= 0)
712712
p(0); // we don't know what p points to so no warning
713713
}
714+
715+
struct PairVal {
716+
int a;
717+
int b;
718+
PairVal(int a, int b) : a(a), b(b) {}
719+
};
720+
721+
void structured_binding_infinite_loop1() {
722+
auto [x, y] = PairVal(0, 0);
723+
while (x < 10) {
724+
// CHECK-MESSAGES: :[[@LINE-1]]:3: warning: this loop is infinite; none of its condition variables (x) are updated in the loop body [bugprone-infinite-loop]
725+
y++;
726+
}
727+
while (y < 10) {
728+
// CHECK-MESSAGES: :[[@LINE-1]]:3: warning: this loop is infinite; none of its condition variables (y) are updated in the loop body [bugprone-infinite-loop]
729+
x++;
730+
}
731+
}
732+
733+
void structured_binding_infinite_loop2() {
734+
auto [x, y] = PairVal(0, 0);
735+
while (x < 10) {
736+
// CHECK-MESSAGES: :[[@LINE-1]]:3: warning: this loop is infinite; none of its condition variables (x) are updated in the loop body [bugprone-infinite-loop]
737+
// No update to x or y
738+
}
739+
while (y < 10) {
740+
// CHECK-MESSAGES: :[[@LINE-1]]:3: warning: this loop is infinite; none of its condition variables (y) are updated in the loop body [bugprone-infinite-loop]
741+
// No update to x or y
742+
}
743+
}
744+
745+
void structured_binding_not_infinite1() {
746+
auto [x, y] = PairVal(0, 0);
747+
while (x < 10) {
748+
x++;
749+
}
750+
while (y < 10) {
751+
y++;
752+
}
753+
}
754+
755+
void volatile_structured_binding_in_condition() {
756+
volatile auto [x, y] = PairVal(0, 0);
757+
while (!x) {}
758+
}
759+
760+
void test_local_static_structured_binding_recursion() {
761+
static auto [i, _] = PairVal(0, 0);
762+
int j = 0;
763+
764+
i--;
765+
while (i >= 0)
766+
test_local_static_structured_binding_recursion(); // no warning, recursively decrement i
767+
for (; i >= 0;)
768+
test_local_static_structured_binding_recursion(); // no warning, recursively decrement i
769+
for (; i + j >= 0;)
770+
test_local_static_structured_binding_recursion(); // no warning, recursively decrement i
771+
for (; i >= 0; i--)
772+
; // no warning, i decrements
773+
while (j >= 0)
774+
// CHECK-MESSAGES: :[[@LINE-1]]:3: warning: this loop is infinite; none of its condition variables (j) are updated in the loop body [bugprone-infinite-loop]
775+
test_local_static_structured_binding_recursion();
776+
777+
int (*p)(int) = 0;
778+
779+
while (i >= 0)
780+
// CHECK-MESSAGES: :[[@LINE-1]]:3: warning: this loop is infinite; none of its condition variables (i) are updated in the loop body [bugprone-infinite-loop]
781+
p = 0;
782+
while (i >= 0)
783+
p(0); // we don't know what p points to so no warning
784+
}
785+
786+
struct S { int a; };
787+
void issue_138842_reduced() {
788+
int x = 10;
789+
auto [y] = S{1};
790+
791+
while (y < x) {
792+
y++;
793+
}
794+
}
795+
796+
namespace std {
797+
template <typename T, typename U>
798+
struct pair {
799+
T first;
800+
U second;
801+
802+
pair(T a, U b) : first(a), second(b) {}
803+
};
804+
}
805+
806+
template <typename T, typename U>
807+
void structured_binding_in_template_byval(T a, U b) {
808+
auto [c, d] = std::pair<T, U>(a,b);
809+
810+
while (c < 10) {
811+
// CHECK-MESSAGES: :[[@LINE-1]]:3: warning: this loop is infinite; none of its condition variables (c) are updated in the loop body [bugprone-infinite-loop]
812+
d++;
813+
}
814+
815+
while (c < 10) {
816+
c++; // no warning
817+
}
818+
}
819+
820+
template <typename T, typename U>
821+
void structured_binding_in_template_bylref(T a, U b) {
822+
auto p = std::pair<T, U>(a,b);
823+
auto& [c, d] = p;
824+
825+
while (c < 10) {
826+
// CHECK-MESSAGES: :[[@LINE-1]]:3: warning: this loop is infinite; none of its condition variables (c) are updated in the loop body [bugprone-infinite-loop]
827+
d++;
828+
}
829+
830+
while (c < 10) {
831+
c++; // no warning
832+
}
833+
}
834+
835+
template <typename T, typename U>
836+
void structured_binding_in_template_byrref(T a, U b) {
837+
auto p = std::pair<T, U>(a,b);
838+
auto&& [c, d] = p;
839+
840+
while (c < 10) {
841+
// CHECK-MESSAGES: :[[@LINE-1]]:3: warning: this loop is infinite; none of its condition variables (c) are updated in the loop body [bugprone-infinite-loop]
842+
d++;
843+
}
844+
845+
while (c < 10) {
846+
c++; // no warning
847+
}
848+
}
849+
850+
void structured_binding_in_template_instantiation(int b) {
851+
structured_binding_in_template_byval(b, 0);
852+
structured_binding_in_template_bylref(b, 0);
853+
structured_binding_in_template_byrref(b, 0);
854+
}
855+
856+
void array_structured_binding() {
857+
int arr[2] = {0, 0};
858+
auto [x, y] = arr;
859+
860+
while (x < 10) {
861+
// CHECK-MESSAGES: :[[@LINE-1]]:3: warning: this loop is infinite; none of its condition variables (x) are updated in the loop body [bugprone-infinite-loop]
862+
y++;
863+
}
864+
865+
while (y < 10) {
866+
y++; // no warning
867+
}
868+
}
869+
870+
namespace std {
871+
using size_t = int;
872+
template <class> struct tuple_size;
873+
template <std::size_t, class> struct tuple_element;
874+
template <class...> class tuple;
875+
876+
namespace {
877+
template <class T, T v>
878+
struct size_helper { static const T value = v; };
879+
} // namespace
880+
881+
template <class... T>
882+
struct tuple_size<tuple<T...>> : size_helper<std::size_t, sizeof...(T)> {};
883+
884+
template <std::size_t I, class... T>
885+
struct tuple_element<I, tuple<T...>> {
886+
using type = __type_pack_element<I, T...>;
887+
};
888+
889+
template <class...> class tuple {};
890+
891+
template <std::size_t I, class... T>
892+
typename tuple_element<I, tuple<T...>>::type get(tuple<T...>);
893+
} // namespace std
894+
895+
std::tuple<int*, int> &get_chunk();
896+
897+
void test_structured_bindings_tuple() {
898+
auto [buffer, size ] = get_chunk();
899+
int maxLen = 8;
900+
901+
while (size < maxLen) {
902+
// No warning. The loop is finite because 'size' is being incremented in each iteration and compared against 'maxLen' for termination
903+
buffer[size++] = 2;
904+
}
905+
}
906+
907+
void test_structured_bindings_tuple_ref() {
908+
auto& [buffer, size ] = get_chunk();
909+
int maxLen = 8;
910+
911+
while (size < maxLen) {
912+
// No warning. The loop is finite because 'size' is being incremented in each iteration and compared against 'maxLen' for termination
913+
buffer[size++] = 2;
914+
}
915+
}

0 commit comments

Comments
 (0)