1 //===--- PassByValueCheck.cpp - clang-tidy---------------------------------===//
2 //
3 // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4 // See https://llvm.org/LICENSE.txt for license information.
5 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6 //
7 //===----------------------------------------------------------------------===//
8
9 #include "PassByValueCheck.h"
10 #include "clang/AST/ASTContext.h"
11 #include "clang/AST/RecursiveASTVisitor.h"
12 #include "clang/ASTMatchers/ASTMatchFinder.h"
13 #include "clang/ASTMatchers/ASTMatchers.h"
14 #include "clang/Frontend/CompilerInstance.h"
15 #include "clang/Lex/Lexer.h"
16 #include "clang/Lex/Preprocessor.h"
17
18 using namespace clang::ast_matchers;
19 using namespace llvm;
20
21 namespace clang {
22 namespace tidy {
23 namespace modernize {
24
25 namespace {
26 /// Matches move-constructible classes.
27 ///
28 /// Given
29 /// \code
30 /// // POD types are trivially move constructible.
31 /// struct Foo { int a; };
32 ///
33 /// struct Bar {
34 /// Bar(Bar &&) = deleted;
35 /// int a;
36 /// };
37 /// \endcode
38 /// recordDecl(isMoveConstructible())
39 /// matches "Foo".
AST_MATCHER(CXXRecordDecl,isMoveConstructible)40 AST_MATCHER(CXXRecordDecl, isMoveConstructible) {
41 for (const CXXConstructorDecl *Ctor : Node.ctors()) {
42 if (Ctor->isMoveConstructor() && !Ctor->isDeleted())
43 return true;
44 }
45 return false;
46 }
47 } // namespace
48
notTemplateSpecConstRefType()49 static TypeMatcher notTemplateSpecConstRefType() {
50 return lValueReferenceType(
51 pointee(unless(templateSpecializationType()), isConstQualified()));
52 }
53
nonConstValueType()54 static TypeMatcher nonConstValueType() {
55 return qualType(unless(anyOf(referenceType(), isConstQualified())));
56 }
57
58 /// Whether or not \p ParamDecl is used exactly one time in \p Ctor.
59 ///
60 /// Checks both in the init-list and the body of the constructor.
paramReferredExactlyOnce(const CXXConstructorDecl * Ctor,const ParmVarDecl * ParamDecl)61 static bool paramReferredExactlyOnce(const CXXConstructorDecl *Ctor,
62 const ParmVarDecl *ParamDecl) {
63 /// \c clang::RecursiveASTVisitor that checks that the given
64 /// \c ParmVarDecl is used exactly one time.
65 ///
66 /// \see ExactlyOneUsageVisitor::hasExactlyOneUsageIn()
67 class ExactlyOneUsageVisitor
68 : public RecursiveASTVisitor<ExactlyOneUsageVisitor> {
69 friend class RecursiveASTVisitor<ExactlyOneUsageVisitor>;
70
71 public:
72 ExactlyOneUsageVisitor(const ParmVarDecl *ParamDecl)
73 : ParamDecl(ParamDecl) {}
74
75 /// Whether or not the parameter variable is referred only once in
76 /// the
77 /// given constructor.
78 bool hasExactlyOneUsageIn(const CXXConstructorDecl *Ctor) {
79 Count = 0;
80 TraverseDecl(const_cast<CXXConstructorDecl *>(Ctor));
81 return Count == 1;
82 }
83
84 private:
85 /// Counts the number of references to a variable.
86 ///
87 /// Stops the AST traversal if more than one usage is found.
88 bool VisitDeclRefExpr(DeclRefExpr *D) {
89 if (const ParmVarDecl *To = dyn_cast<ParmVarDecl>(D->getDecl())) {
90 if (To == ParamDecl) {
91 ++Count;
92 if (Count > 1) {
93 // No need to look further, used more than once.
94 return false;
95 }
96 }
97 }
98 return true;
99 }
100
101 const ParmVarDecl *ParamDecl;
102 unsigned Count;
103 };
104
105 return ExactlyOneUsageVisitor(ParamDecl).hasExactlyOneUsageIn(Ctor);
106 }
107
108 /// Find all references to \p ParamDecl across all of the
109 /// redeclarations of \p Ctor.
110 static SmallVector<const ParmVarDecl *, 2>
collectParamDecls(const CXXConstructorDecl * Ctor,const ParmVarDecl * ParamDecl)111 collectParamDecls(const CXXConstructorDecl *Ctor,
112 const ParmVarDecl *ParamDecl) {
113 SmallVector<const ParmVarDecl *, 2> Results;
114 unsigned ParamIdx = ParamDecl->getFunctionScopeIndex();
115
116 for (const FunctionDecl *Redecl : Ctor->redecls())
117 Results.push_back(Redecl->getParamDecl(ParamIdx));
118 return Results;
119 }
120
PassByValueCheck(StringRef Name,ClangTidyContext * Context)121 PassByValueCheck::PassByValueCheck(StringRef Name, ClangTidyContext *Context)
122 : ClangTidyCheck(Name, Context),
123 Inserter(Options.getLocalOrGlobal("IncludeStyle",
124 utils::IncludeSorter::IS_LLVM)),
125 ValuesOnly(Options.get("ValuesOnly", false)) {}
126
storeOptions(ClangTidyOptions::OptionMap & Opts)127 void PassByValueCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) {
128 Options.store(Opts, "IncludeStyle", Inserter.getStyle());
129 Options.store(Opts, "ValuesOnly", ValuesOnly);
130 }
131
registerMatchers(MatchFinder * Finder)132 void PassByValueCheck::registerMatchers(MatchFinder *Finder) {
133 Finder->addMatcher(
134 traverse(
135 ast_type_traits::TK_AsIs,
136 cxxConstructorDecl(
137 forEachConstructorInitializer(
138 cxxCtorInitializer(
139 unless(isBaseInitializer()),
140 // Clang builds a CXXConstructExpr only when it knows
141 // which constructor will be called. In dependent contexts
142 // a ParenListExpr is generated instead of a
143 // CXXConstructExpr, filtering out templates automatically
144 // for us.
145 withInitializer(cxxConstructExpr(
146 has(ignoringParenImpCasts(declRefExpr(to(
147 parmVarDecl(
148 hasType(qualType(
149 // Match only const-ref or a non-const
150 // value parameters. Rvalues,
151 // TemplateSpecializationValues and
152 // const-values shouldn't be modified.
153 ValuesOnly
154 ? nonConstValueType()
155 : anyOf(notTemplateSpecConstRefType(),
156 nonConstValueType()))))
157 .bind("Param"))))),
158 hasDeclaration(cxxConstructorDecl(
159 isCopyConstructor(), unless(isDeleted()),
160 hasDeclContext(
161 cxxRecordDecl(isMoveConstructible())))))))
162 .bind("Initializer")))
163 .bind("Ctor")),
164 this);
165 }
166
registerPPCallbacks(const SourceManager & SM,Preprocessor * PP,Preprocessor * ModuleExpanderPP)167 void PassByValueCheck::registerPPCallbacks(const SourceManager &SM,
168 Preprocessor *PP,
169 Preprocessor *ModuleExpanderPP) {
170 Inserter.registerPreprocessor(PP);
171 }
172
check(const MatchFinder::MatchResult & Result)173 void PassByValueCheck::check(const MatchFinder::MatchResult &Result) {
174 const auto *Ctor = Result.Nodes.getNodeAs<CXXConstructorDecl>("Ctor");
175 const auto *ParamDecl = Result.Nodes.getNodeAs<ParmVarDecl>("Param");
176 const auto *Initializer =
177 Result.Nodes.getNodeAs<CXXCtorInitializer>("Initializer");
178 SourceManager &SM = *Result.SourceManager;
179
180 // If the parameter is used or anything other than the copy, do not apply
181 // the changes.
182 if (!paramReferredExactlyOnce(Ctor, ParamDecl))
183 return;
184
185 // If the parameter is trivial to copy, don't move it. Moving a trivivally
186 // copyable type will cause a problem with performance-move-const-arg
187 if (ParamDecl->getType().getNonReferenceType().isTriviallyCopyableType(
188 *Result.Context))
189 return;
190
191 auto Diag = diag(ParamDecl->getBeginLoc(), "pass by value and use std::move");
192
193 // Iterate over all declarations of the constructor.
194 for (const ParmVarDecl *ParmDecl : collectParamDecls(Ctor, ParamDecl)) {
195 auto ParamTL = ParmDecl->getTypeSourceInfo()->getTypeLoc();
196 auto RefTL = ParamTL.getAs<ReferenceTypeLoc>();
197
198 // Do not replace if it is already a value, skip.
199 if (RefTL.isNull())
200 continue;
201
202 TypeLoc ValueTL = RefTL.getPointeeLoc();
203 auto TypeRange = CharSourceRange::getTokenRange(ParmDecl->getBeginLoc(),
204 ParamTL.getEndLoc());
205 std::string ValueStr = Lexer::getSourceText(CharSourceRange::getTokenRange(
206 ValueTL.getSourceRange()),
207 SM, getLangOpts())
208 .str();
209 ValueStr += ' ';
210 Diag << FixItHint::CreateReplacement(TypeRange, ValueStr);
211 }
212
213 // Use std::move in the initialization list.
214 Diag << FixItHint::CreateInsertion(Initializer->getRParenLoc(), ")")
215 << FixItHint::CreateInsertion(
216 Initializer->getLParenLoc().getLocWithOffset(1), "std::move(")
217 << Inserter.createIncludeInsertion(
218 Result.SourceManager->getFileID(Initializer->getSourceLocation()),
219 "<utility>");
220 }
221
222 } // namespace modernize
223 } // namespace tidy
224 } // namespace clang
225