Skip to content

Commit a4da9f3

Browse files
committed
Add a mode to performance-for-range-copy that alerts when a loop
variable is copied and modified. This is a bugprone pattern because the programmer in this case often assumes they are modifying the original value instead of a copy.
1 parent ddf9b91 commit a4da9f3

File tree

2 files changed

+117
-8
lines changed

2 files changed

+117
-8
lines changed

clang-tools-extra/clang-tidy/performance/ForRangeCopyCheck.cpp

Lines changed: 29 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -79,10 +79,15 @@ bool ForRangeCopyCheck::handleConstValueCopy(const VarDecl &LoopVar,
7979
} else if (!LoopVar.getType().isConstQualified()) {
8080
return false;
8181
}
82-
std::optional<bool> Expensive =
83-
utils::type_traits::isExpensiveToCopy(LoopVar.getType(), Context);
84-
if (!Expensive || !*Expensive)
85-
return false;
82+
83+
// TODO: Check if this is actually needed for some cases? It seems redundant
84+
// with the Expensive check in handleCopyIsOnlyConstReferenced but maybe
85+
// I'm missing something
86+
//std::optional<bool> Expensive =
87+
// utils::type_traits::isExpensiveToCopy(LoopVar.getType(), Context);
88+
//if (!Expensive || !*Expensive)
89+
// return false;
90+
8691
auto Diagnostic =
8792
diag(LoopVar.getLocation(),
8893
"the loop variable's type is not a reference type; this creates a "
@@ -108,10 +113,16 @@ static bool isReferenced(const VarDecl &LoopVar, const Stmt &Stmt,
108113
bool ForRangeCopyCheck::handleCopyIsOnlyConstReferenced(
109114
const VarDecl &LoopVar, const CXXForRangeStmt &ForRange,
110115
ASTContext &Context) {
111-
std::optional<bool> Expensive =
112-
utils::type_traits::isExpensiveToCopy(LoopVar.getType(), Context);
113-
if (LoopVar.getType().isConstQualified() || !Expensive || !*Expensive)
114-
return false;
116+
117+
// TODO: Add some toggle here for caring about Expensive. Legacy checks
118+
// care about this but our new check does not.
119+
// Simply disabling the Expensive check altogether for now so we can test the
120+
// new check.
121+
//std::optional<bool> Expensive =
122+
// utils::type_traits::isExpensiveToCopy(LoopVar.getType(), Context);
123+
//if (LoopVar.getType().isConstQualified() || !Expensive || !*Expensive)
124+
// return false;
125+
115126
// We omit the case where the loop variable is not used in the loop body. E.g.
116127
//
117128
// for (auto _ : benchmark_state) {
@@ -133,6 +144,16 @@ bool ForRangeCopyCheck::handleCopyIsOnlyConstReferenced(
133144

134145
return true;
135146
}
147+
148+
// If it's copied and mutated, there's a high chance that's a bug.
149+
if (ExprMutationAnalyzer(*ForRange.getBody(), Context).isMutated(&LoopVar)) {
150+
auto Diag = diag(LoopVar.getLocation(),
151+
"loop variable is copied and then modified, which is likely a bug; you "
152+
"probably want to modify the underlying object and not this copy. If you "
153+
"*did* intend to modify this copy, please use an explicit copy inside the "
154+
"body of the loop");
155+
return true;
156+
}
136157
return false;
137158
}
138159

playground/test.cpp

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
#include <iostream>
2+
#include <vector>
3+
#include <string.h>
4+
5+
void ModifyString(std::string *s) {
6+
s->append("!");
7+
}
8+
9+
class DataCache {
10+
public:
11+
// This member function declaration will also be matched.
12+
// Okay my code ONLY matched this one.
13+
// But God that's so much better than nothing.
14+
const std::vector<int>& get_cached_items() const;
15+
16+
private:
17+
std::vector<int> items_;
18+
};
19+
20+
const std::vector<std::string>& get_global_strings();
21+
22+
const std::vector<double>& get_default_values() {
23+
static std::vector<double> defaults = {3.14, 2.71, 1.61};
24+
return defaults;
25+
}
26+
27+
int main() {
28+
std::cout << "Initializing some_strings.\n";
29+
std::vector<std::string> some_strings;
30+
some_strings.push_back("one");
31+
some_strings.push_back("two");
32+
some_strings.push_back("three");
33+
// Three tools:
34+
// * suspicious-copy-in-range-loop (as written right now)
35+
// * performance-for-range-copy with no options
36+
// * performance-for-range-copy with WarnOnAllAutoCopies enabled
37+
38+
std::cout << "Modify copies; print them as you modify them.\n";
39+
// Case 1.
40+
// These strings are being COPIED and then MODIFIED, so:
41+
// SHOULD WARN? YES
42+
// DOES WARN?
43+
// suspicious-copy-in-range-loop: YES
44+
// ~ performance-for-range-copy with no options: NO
45+
// performance-for-range-copy with WarnOnAllAutoCopies enabled: YES
46+
for (auto x : some_strings) {
47+
ModifyString(&x);
48+
std::cout << x << "\n";
49+
//std::cout << &x << "\n";
50+
}
51+
52+
// Case 2.
53+
// These strings are being COPIED but left UNMODIFIED, so:
54+
// SHOULD WARN? NO
55+
// DOES WARN?
56+
// ~ suspicious-copy-in-range-loop: YES
57+
// performance-for-range-copy with no options: NO
58+
// ~ performance-for-range-copy with WarnOnAllAutoCopies enabled: YES
59+
for (auto x : some_strings) {
60+
std::cout << "hi\n";
61+
std::cout << x << "\n";
62+
//std::cout << &x << "\n";
63+
}
64+
65+
// Case 3.
66+
// These strings are being NEITHER COPIED, NOR MODIFIED, so
67+
// SHOULD WARN? NO
68+
// DOES WARN?
69+
// suspicious-copy-in-range-loop: NO
70+
// performance-for-range-copy with no options: NO
71+
// performance-for-range-copy with WarnOnAllAutoCopies enabled: NO
72+
for (auto& x : some_strings) {
73+
std::cout << x << "\n";
74+
std::cout << &x << "\n";
75+
}
76+
77+
// Case 4.
78+
// These strings are being NOT COPIED but referenced, and MODIFIED, so
79+
// SHOULD WARN? NO
80+
// DOES WARN?
81+
// suspicious-copy-in-range-loop: NO
82+
// performance-for-range-copy with no options: NO
83+
// performance-for-range-copy with WarnOnAllAutoCopies enabled: NO
84+
for (auto& x : some_strings) {
85+
x = "modd'd!";
86+
std::cout << x << "\n";
87+
}
88+
}

0 commit comments

Comments
 (0)