blob: a293cfee760a6539fb23fdbae88459deba6efb81 [file] [log] [blame]
Alexander Kornienko5aebfe22016-01-29 15:54:26 +00001//===--- 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
16namespace clang {
17namespace tidy {
18namespace performance {
19
20using namespace ::clang::ast_matchers;
21using llvm::SmallPtrSet;
22
23namespace {
24
25template <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.
33template <typename Node>
34void 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.
41SmallPtrSet<const DeclRefExpr *, 16>
42declRefExprs(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.
53SmallPtrSet<const DeclRefExpr *, 16>
54constReferenceDeclRefExprs(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.
83FixItHint 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.
92FixItHint createConstFix(const VarDecl &VarDecl) {
93 return FixItHint::CreateInsertion(VarDecl.getTypeSpecStartLoc(), "const ");
94}
95
96} // namespace
97
98ForRangeCopyCheck::ForRangeCopyCheck(StringRef Name, ClangTidyContext *Context)
99 : ClangTidyCheck(Name, Context),
100 WarnOnAllAutoCopies(Options.get("WarnOnAllAutoCopies", 0)) {}
101
102void ForRangeCopyCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) {
103 Options.store(Opts, "WarnOnAllAutoCopies", WarnOnAllAutoCopies);
104}
105
106void 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
118void 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
129bool 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 }
Felix Bergerf4fdc8a2016-02-15 03:36:23 +0000138 llvm::Optional<bool> Expensive =
139 type_traits::isExpensiveToCopy(LoopVar.getType(), Context);
140 if (!Expensive || !*Expensive)
Alexander Kornienko5aebfe22016-01-29 15:54:26 +0000141 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
152bool ForRangeCopyCheck::handleCopyIsOnlyConstReferenced(
153 const VarDecl &LoopVar, const CXXForRangeStmt &ForRange,
154 ASTContext &Context) {
Felix Bergerf4fdc8a2016-02-15 03:36:23 +0000155 llvm::Optional<bool> Expensive =
156 type_traits::isExpensiveToCopy(LoopVar.getType(), Context);
157 if (LoopVar.getType().isConstQualified() || !Expensive || !*Expensive) {
Alexander Kornienko5aebfe22016-01-29 15:54:26 +0000158 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