@@ -22,11 +22,15 @@ namespace clang::tidy::performance {
22
22
ForRangeCopyCheck::ForRangeCopyCheck (StringRef Name, ClangTidyContext *Context)
23
23
: ClangTidyCheck(Name, Context),
24
24
WarnOnAllAutoCopies (Options.get(" WarnOnAllAutoCopies" , false )),
25
+ WarnOnModificationOfCopiedLoopVariable(
26
+ Options.get(" WarnOnModificationOfCopiedLoopVariable" , false )),
25
27
AllowedTypes(
26
28
utils::options::parseStringList (Options.get(" AllowedTypes" , " " ))) {}
27
29
28
30
void ForRangeCopyCheck::storeOptions (ClangTidyOptions::OptionMap &Opts) {
29
31
Options.store (Opts, " WarnOnAllAutoCopies" , WarnOnAllAutoCopies);
32
+ Options.store (Opts, " WarnOnModificationOfCopiedLoopVariable" ,
33
+ WarnOnModificationOfCopiedLoopVariable);
30
34
Options.store (Opts, " AllowedTypes" ,
31
35
utils::options::serializeStringList (AllowedTypes));
32
36
}
@@ -64,9 +68,11 @@ void ForRangeCopyCheck::check(const MatchFinder::MatchResult &Result) {
64
68
// Ignore code in macros since we can't place the fixes correctly.
65
69
if (Var->getBeginLoc ().isMacroID ())
66
70
return ;
71
+ const auto *ForRange = Result.Nodes .getNodeAs <CXXForRangeStmt>(" forRange" );
72
+ if (copiedLoopVarIsMutated (*Var, *ForRange, *Result.Context ))
73
+ return ;
67
74
if (handleConstValueCopy (*Var, *Result.Context ))
68
75
return ;
69
- const auto *ForRange = Result.Nodes .getNodeAs <CXXForRangeStmt>(" forRange" );
70
76
handleCopyIsOnlyConstReferenced (*Var, *ForRange, *Result.Context );
71
77
}
72
78
@@ -79,10 +85,15 @@ bool ForRangeCopyCheck::handleConstValueCopy(const VarDecl &LoopVar,
79
85
} else if (!LoopVar.getType ().isConstQualified ()) {
80
86
return false ;
81
87
}
88
+
89
+ // TODO: Check if this is actually needed for some cases? It seems redundant
90
+ // with the Expensive check in handleCopyIsOnlyConstReferenced but maybe
91
+ // I'm missing something
82
92
std::optional<bool > Expensive =
83
93
utils::type_traits::isExpensiveToCopy (LoopVar.getType (), Context);
84
94
if (!Expensive || !*Expensive)
85
95
return false ;
96
+
86
97
auto Diagnostic =
87
98
diag (LoopVar.getLocation (),
88
99
" the loop variable's type is not a reference type; this creates a "
@@ -105,13 +116,35 @@ static bool isReferenced(const VarDecl &LoopVar, const Stmt &Stmt,
105
116
.empty ();
106
117
}
107
118
119
+ bool ForRangeCopyCheck::copiedLoopVarIsMutated (const VarDecl &LoopVar,
120
+ const CXXForRangeStmt &ForRange,
121
+ ASTContext &Context) {
122
+ // If it's copied and mutated, there's a high chance that's a bug.
123
+ if (WarnOnModificationOfCopiedLoopVariable) {
124
+ if (ExprMutationAnalyzer (*ForRange.getBody (), Context)
125
+ .isMutated (&LoopVar)) {
126
+ auto Diag =
127
+ diag (LoopVar.getLocation (), " loop variable is copied and then "
128
+ " modified, which is likely a bug; you "
129
+ " probably want to modify the underlying "
130
+ " object and not this copy. If you "
131
+ " *did* intend to modify this copy, "
132
+ " please use an explicit copy inside the "
133
+ " body of the loop" );
134
+ return true ;
135
+ }
136
+ }
137
+ return false ;
138
+ }
139
+
108
140
bool ForRangeCopyCheck::handleCopyIsOnlyConstReferenced (
109
141
const VarDecl &LoopVar, const CXXForRangeStmt &ForRange,
110
142
ASTContext &Context) {
111
143
std::optional<bool > Expensive =
112
144
utils::type_traits::isExpensiveToCopy (LoopVar.getType (), Context);
113
145
if (LoopVar.getType ().isConstQualified () || !Expensive || !*Expensive)
114
146
return false ;
147
+
115
148
// We omit the case where the loop variable is not used in the loop body. E.g.
116
149
//
117
150
// for (auto _ : benchmark_state) {
@@ -130,7 +163,6 @@ bool ForRangeCopyCheck::handleCopyIsOnlyConstReferenced(
130
163
if (std::optional<FixItHint> Fix = utils::fixit::addQualifierToVarDecl (
131
164
LoopVar, Context, Qualifiers::Const))
132
165
Diag << *Fix << utils::fixit::changeVarDeclToReference (LoopVar, Context);
133
-
134
166
return true ;
135
167
}
136
168
return false ;
0 commit comments