@@ -22,11 +22,15 @@ namespace clang::tidy::performance {
2222ForRangeCopyCheck::ForRangeCopyCheck (StringRef Name, ClangTidyContext *Context)
2323 : ClangTidyCheck(Name, Context),
2424 WarnOnAllAutoCopies (Options.get(" WarnOnAllAutoCopies" , false )),
25+ WarnOnModificationOfCopiedLoopVariable(
26+ Options.get(" WarnOnModificationOfCopiedLoopVariable" , false )),
2527 AllowedTypes(
2628 utils::options::parseStringList (Options.get(" AllowedTypes" , " " ))) {}
2729
2830void ForRangeCopyCheck::storeOptions (ClangTidyOptions::OptionMap &Opts) {
2931 Options.store (Opts, " WarnOnAllAutoCopies" , WarnOnAllAutoCopies);
32+ Options.store (Opts, " WarnOnModificationOfCopiedLoopVariable" ,
33+ WarnOnModificationOfCopiedLoopVariable);
3034 Options.store (Opts, " AllowedTypes" ,
3135 utils::options::serializeStringList (AllowedTypes));
3236}
@@ -64,9 +68,11 @@ void ForRangeCopyCheck::check(const MatchFinder::MatchResult &Result) {
6468 // Ignore code in macros since we can't place the fixes correctly.
6569 if (Var->getBeginLoc ().isMacroID ())
6670 return ;
71+ const auto *ForRange = Result.Nodes .getNodeAs <CXXForRangeStmt>(" forRange" );
72+ if (copiedLoopVarIsMutated (*Var, *ForRange, *Result.Context ))
73+ return ;
6774 if (handleConstValueCopy (*Var, *Result.Context ))
6875 return ;
69- const auto *ForRange = Result.Nodes .getNodeAs <CXXForRangeStmt>(" forRange" );
7076 handleCopyIsOnlyConstReferenced (*Var, *ForRange, *Result.Context );
7177}
7278
@@ -79,10 +85,15 @@ bool ForRangeCopyCheck::handleConstValueCopy(const VarDecl &LoopVar,
7985 } else if (!LoopVar.getType ().isConstQualified ()) {
8086 return false ;
8187 }
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
8292 std::optional<bool > Expensive =
8393 utils::type_traits::isExpensiveToCopy (LoopVar.getType (), Context);
8494 if (!Expensive || !*Expensive)
8595 return false ;
96+
8697 auto Diagnostic =
8798 diag (LoopVar.getLocation (),
8899 " 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,
105116 .empty ();
106117}
107118
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+
108140bool ForRangeCopyCheck::handleCopyIsOnlyConstReferenced (
109141 const VarDecl &LoopVar, const CXXForRangeStmt &ForRange,
110142 ASTContext &Context) {
111143 std::optional<bool > Expensive =
112144 utils::type_traits::isExpensiveToCopy (LoopVar.getType (), Context);
113145 if (LoopVar.getType ().isConstQualified () || !Expensive || !*Expensive)
114146 return false ;
147+
115148 // We omit the case where the loop variable is not used in the loop body. E.g.
116149 //
117150 // for (auto _ : benchmark_state) {
@@ -130,7 +163,6 @@ bool ForRangeCopyCheck::handleCopyIsOnlyConstReferenced(
130163 if (std::optional<FixItHint> Fix = utils::fixit::addQualifierToVarDecl (
131164 LoopVar, Context, Qualifiers::Const))
132165 Diag << *Fix << utils::fixit::changeVarDeclToReference (LoopVar, Context);
133-
134166 return true ;
135167 }
136168 return false ;
0 commit comments