xref: /llvm-project/clang-tools-extra/clang-tidy/performance/ForRangeCopyCheck.cpp (revision f4fdc8aba00d8a8508af7ed86cab2dbc7f17ed08)
1 //===--- ForRangeCopyCheck.cpp - clang-tidy--------------------------------===//
2 //
3 //                     The LLVM Compiler Infrastructure
4 //
5 // This file is distributed under the University of Illinois Open Source
6 // License. See LICENSE.TXT for details.
7 //
8 //===----------------------------------------------------------------------===//
9 
10 #include "ForRangeCopyCheck.h"
11 #include "../utils/LexerUtils.h"
12 #include "../utils/TypeTraits.h"
13 #include "clang/Lex/Lexer.h"
14 #include "llvm/ADT/SmallPtrSet.h"
15 
16 namespace clang {
17 namespace tidy {
18 namespace performance {
19 
20 using namespace ::clang::ast_matchers;
21 using llvm::SmallPtrSet;
22 
23 namespace {
24 
25 template <typename S> bool isSetDifferenceEmpty(const S &S1, const S &S2) {
26   for (const auto &E : S1)
27     if (S2.count(E) == 0)
28       return false;
29   return true;
30 }
31 
32 // Extracts all Nodes keyed by ID from Matches and inserts them into Nodes.
33 template <typename Node>
34 void extractNodesByIdTo(ArrayRef<BoundNodes> Matches, StringRef ID,
35                         SmallPtrSet<const Node *, 16> &Nodes) {
36   for (const auto &Match : Matches)
37     Nodes.insert(Match.getNodeAs<Node>(ID));
38 }
39 
40 // Finds all DeclRefExprs to VarDecl in Stmt.
41 SmallPtrSet<const DeclRefExpr *, 16>
42 declRefExprs(const VarDecl &VarDecl, const Stmt &Stmt, ASTContext &Context) {
43   auto Matches = match(
44       findAll(declRefExpr(to(varDecl(equalsNode(&VarDecl)))).bind("declRef")),
45       Stmt, Context);
46   SmallPtrSet<const DeclRefExpr *, 16> DeclRefs;
47   extractNodesByIdTo(Matches, "declRef", DeclRefs);
48   return DeclRefs;
49 }
50 
51 // Finds all DeclRefExprs where a const method is called on VarDecl or VarDecl
52 // is the a const reference or value argument to a CallExpr or CXXConstructExpr.
53 SmallPtrSet<const DeclRefExpr *, 16>
54 constReferenceDeclRefExprs(const VarDecl &VarDecl, const Stmt &Stmt,
55                            ASTContext &Context) {
56   auto DeclRefToVar =
57       declRefExpr(to(varDecl(equalsNode(&VarDecl)))).bind("declRef");
58   auto ConstMethodCallee = callee(cxxMethodDecl(isConst()));
59   // Match method call expressions where the variable is referenced as the this
60   // implicit object argument and opertor call expression for member operators
61   // where the variable is the 0-th argument.
62   auto Matches = match(
63       findAll(expr(anyOf(cxxMemberCallExpr(ConstMethodCallee, on(DeclRefToVar)),
64                          cxxOperatorCallExpr(ConstMethodCallee,
65                                              hasArgument(0, DeclRefToVar))))),
66       Stmt, Context);
67   SmallPtrSet<const DeclRefExpr *, 16> DeclRefs;
68   extractNodesByIdTo(Matches, "declRef", DeclRefs);
69   auto ConstReferenceOrValue =
70       qualType(anyOf(referenceType(pointee(qualType(isConstQualified()))),
71                      unless(anyOf(referenceType(), pointerType()))));
72   auto UsedAsConstRefOrValueArg = forEachArgumentWithParam(
73       DeclRefToVar, parmVarDecl(hasType(ConstReferenceOrValue)));
74   Matches = match(findAll(callExpr(UsedAsConstRefOrValueArg)), Stmt, Context);
75   extractNodesByIdTo(Matches, "declRef", DeclRefs);
76   Matches =
77       match(findAll(cxxConstructExpr(UsedAsConstRefOrValueArg)), Stmt, Context);
78   extractNodesByIdTo(Matches, "declRef", DeclRefs);
79   return DeclRefs;
80 }
81 
82 // Modifies VarDecl to be a reference.
83 FixItHint createAmpersandFix(const VarDecl &VarDecl, ASTContext &Context) {
84   SourceLocation AmpLocation = VarDecl.getLocation();
85   auto Token = lexer_utils::getPreviousNonCommentToken(Context, AmpLocation);
86   if (!Token.is(tok::unknown))
87     AmpLocation = Token.getLocation().getLocWithOffset(Token.getLength());
88   return FixItHint::CreateInsertion(AmpLocation, "&");
89 }
90 
91 // Modifies VarDecl to be const.
92 FixItHint createConstFix(const VarDecl &VarDecl) {
93   return FixItHint::CreateInsertion(VarDecl.getTypeSpecStartLoc(), "const ");
94 }
95 
96 } // namespace
97 
98 ForRangeCopyCheck::ForRangeCopyCheck(StringRef Name, ClangTidyContext *Context)
99     : ClangTidyCheck(Name, Context),
100       WarnOnAllAutoCopies(Options.get("WarnOnAllAutoCopies", 0)) {}
101 
102 void ForRangeCopyCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) {
103   Options.store(Opts, "WarnOnAllAutoCopies", WarnOnAllAutoCopies);
104 }
105 
106 void ForRangeCopyCheck::registerMatchers(MatchFinder *Finder) {
107   // Match loop variables that are not references or pointers or are already
108   // initialized through MaterializeTemporaryExpr which indicates a type
109   // conversion.
110   auto LoopVar = varDecl(
111       hasType(hasCanonicalType(unless(anyOf(referenceType(), pointerType())))),
112       unless(hasInitializer(expr(hasDescendant(materializeTemporaryExpr())))));
113   Finder->addMatcher(cxxForRangeStmt(hasLoopVariable(LoopVar.bind("loopVar")))
114                          .bind("forRange"),
115                      this);
116 }
117 
118 void ForRangeCopyCheck::check(const MatchFinder::MatchResult &Result) {
119   const auto *Var = Result.Nodes.getNodeAs<VarDecl>("loopVar");
120   // Ignore code in macros since we can't place the fixes correctly.
121   if (Var->getLocStart().isMacroID())
122     return;
123   if (handleConstValueCopy(*Var, *Result.Context))
124     return;
125   const auto *ForRange = Result.Nodes.getNodeAs<CXXForRangeStmt>("forRange");
126   handleCopyIsOnlyConstReferenced(*Var, *ForRange, *Result.Context);
127 }
128 
129 bool ForRangeCopyCheck::handleConstValueCopy(const VarDecl &LoopVar,
130                                              ASTContext &Context) {
131   if (WarnOnAllAutoCopies) {
132     // For aggressive check just test that loop variable has auto type.
133     if (!isa<AutoType>(LoopVar.getType()))
134       return false;
135   } else if (!LoopVar.getType().isConstQualified()) {
136     return false;
137   }
138   llvm::Optional<bool> Expensive =
139       type_traits::isExpensiveToCopy(LoopVar.getType(), Context);
140   if (!Expensive || !*Expensive)
141     return false;
142   auto Diagnostic =
143       diag(LoopVar.getLocation(),
144            "the loop variable's type is not a reference type; this creates a "
145            "copy in each iteration; consider making this a reference")
146       << createAmpersandFix(LoopVar, Context);
147   if (!LoopVar.getType().isConstQualified())
148     Diagnostic << createConstFix(LoopVar);
149   return true;
150 }
151 
152 bool ForRangeCopyCheck::handleCopyIsOnlyConstReferenced(
153     const VarDecl &LoopVar, const CXXForRangeStmt &ForRange,
154     ASTContext &Context) {
155   llvm::Optional<bool> Expensive =
156       type_traits::isExpensiveToCopy(LoopVar.getType(), Context);
157   if (LoopVar.getType().isConstQualified() || !Expensive || !*Expensive) {
158     return false;
159   }
160   // Collect all DeclRefExprs to the loop variable and all CallExprs and
161   // CXXConstructExprs where the loop variable is used as argument to a const
162   // reference parameter.
163   // If the difference is empty it is safe for the loop variable to be a const
164   // reference.
165   auto AllDeclRefs = declRefExprs(LoopVar, *ForRange.getBody(), Context);
166   auto ConstReferenceDeclRefs =
167       constReferenceDeclRefExprs(LoopVar, *ForRange.getBody(), Context);
168   if (AllDeclRefs.empty() ||
169       !isSetDifferenceEmpty(AllDeclRefs, ConstReferenceDeclRefs))
170     return false;
171   diag(LoopVar.getLocation(),
172        "loop variable is copied but only used as const reference; consider "
173        "making it a const reference")
174       << createConstFix(LoopVar) << createAmpersandFix(LoopVar, Context);
175   return true;
176 }
177 
178 } // namespace performance
179 } // namespace tidy
180 } // namespace clang
181