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 /// Returns true if the given constructor is part of a lvalue/rvalue reference
109 /// pair, i.e. `Param` is of lvalue reference type, and there exists another
110 /// constructor such that:
111 ///  - it has the same number of parameters as `Ctor`.
112 ///  - the parameter at the same index as `Param` is an rvalue reference
113 ///    of the same pointee type
114 ///  - all other parameters have the same type as the corresponding parameter in
115 ///    `Ctor` or are rvalue references with the same pointee type.
116 /// Examples:
117 ///  A::A(const B& Param)
118 ///  A::A(B&&)
119 ///
120 ///  A::A(const B& Param, const C&)
121 ///  A::A(B&& Param, C&&)
122 ///
123 ///  A::A(const B&, const C& Param)
124 ///  A::A(B&&, C&& Param)
125 ///
126 ///  A::A(const B&, const C& Param)
127 ///  A::A(const B&, C&& Param)
128 ///
129 ///  A::A(const B& Param, int)
130 ///  A::A(B&& Param, int)
hasRValueOverload(const CXXConstructorDecl * Ctor,const ParmVarDecl * Param)131 static bool hasRValueOverload(const CXXConstructorDecl *Ctor,
132                               const ParmVarDecl *Param) {
133   if (!Param->getType().getCanonicalType()->isLValueReferenceType()) {
134     // The parameter is passed by value.
135     return false;
136   }
137   const int ParamIdx = Param->getFunctionScopeIndex();
138   const CXXRecordDecl *Record = Ctor->getParent();
139 
140   // Check whether a ctor `C` forms a pair with `Ctor` under the aforementionned
141   // rules.
142   const auto IsRValueOverload = [&Ctor, ParamIdx](const CXXConstructorDecl *C) {
143     if (C == Ctor || C->isDeleted() ||
144         C->getNumParams() != Ctor->getNumParams())
145       return false;
146     for (int I = 0, E = C->getNumParams(); I < E; ++I) {
147       const clang::QualType CandidateParamType =
148           C->parameters()[I]->getType().getCanonicalType();
149       const clang::QualType CtorParamType =
150           Ctor->parameters()[I]->getType().getCanonicalType();
151       const bool IsLValueRValuePair =
152           CtorParamType->isLValueReferenceType() &&
153           CandidateParamType->isRValueReferenceType() &&
154           CandidateParamType->getPointeeType()->getUnqualifiedDesugaredType() ==
155               CtorParamType->getPointeeType()->getUnqualifiedDesugaredType();
156       if (I == ParamIdx) {
157         // The parameter of interest must be paired.
158         if (!IsLValueRValuePair)
159           return false;
160       } else {
161         // All other parameters can be similar or paired.
162         if (!(CandidateParamType == CtorParamType || IsLValueRValuePair))
163           return false;
164       }
165     }
166     return true;
167   };
168 
169   for (const auto *Candidate : Record->ctors()) {
170     if (IsRValueOverload(Candidate)) {
171       return true;
172     }
173   }
174   return false;
175 }
176 
177 /// Find all references to \p ParamDecl across all of the
178 /// redeclarations of \p Ctor.
179 static SmallVector<const ParmVarDecl *, 2>
collectParamDecls(const CXXConstructorDecl * Ctor,const ParmVarDecl * ParamDecl)180 collectParamDecls(const CXXConstructorDecl *Ctor,
181                   const ParmVarDecl *ParamDecl) {
182   SmallVector<const ParmVarDecl *, 2> Results;
183   unsigned ParamIdx = ParamDecl->getFunctionScopeIndex();
184 
185   for (const FunctionDecl *Redecl : Ctor->redecls())
186     Results.push_back(Redecl->getParamDecl(ParamIdx));
187   return Results;
188 }
189 
PassByValueCheck(StringRef Name,ClangTidyContext * Context)190 PassByValueCheck::PassByValueCheck(StringRef Name, ClangTidyContext *Context)
191     : ClangTidyCheck(Name, Context),
192       Inserter(Options.getLocalOrGlobal("IncludeStyle",
193                                         utils::IncludeSorter::IS_LLVM),
194                areDiagsSelfContained()),
195       ValuesOnly(Options.get("ValuesOnly", false)) {}
196 
storeOptions(ClangTidyOptions::OptionMap & Opts)197 void PassByValueCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) {
198   Options.store(Opts, "IncludeStyle", Inserter.getStyle());
199   Options.store(Opts, "ValuesOnly", ValuesOnly);
200 }
201 
registerMatchers(MatchFinder * Finder)202 void PassByValueCheck::registerMatchers(MatchFinder *Finder) {
203   Finder->addMatcher(
204       traverse(
205           TK_AsIs,
206           cxxConstructorDecl(
207               forEachConstructorInitializer(
208                   cxxCtorInitializer(
209                       unless(isBaseInitializer()),
210                       // Clang builds a CXXConstructExpr only when it knows
211                       // which constructor will be called. In dependent contexts
212                       // a ParenListExpr is generated instead of a
213                       // CXXConstructExpr, filtering out templates automatically
214                       // for us.
215                       withInitializer(cxxConstructExpr(
216                           has(ignoringParenImpCasts(declRefExpr(to(
217                               parmVarDecl(
218                                   hasType(qualType(
219                                       // Match only const-ref or a non-const
220                                       // value parameters. Rvalues,
221                                       // TemplateSpecializationValues and
222                                       // const-values shouldn't be modified.
223                                       ValuesOnly
224                                           ? nonConstValueType()
225                                           : anyOf(notTemplateSpecConstRefType(),
226                                                   nonConstValueType()))))
227                                   .bind("Param"))))),
228                           hasDeclaration(cxxConstructorDecl(
229                               isCopyConstructor(), unless(isDeleted()),
230                               hasDeclContext(
231                                   cxxRecordDecl(isMoveConstructible())))))))
232                       .bind("Initializer")))
233               .bind("Ctor")),
234       this);
235 }
236 
registerPPCallbacks(const SourceManager & SM,Preprocessor * PP,Preprocessor * ModuleExpanderPP)237 void PassByValueCheck::registerPPCallbacks(const SourceManager &SM,
238                                            Preprocessor *PP,
239                                            Preprocessor *ModuleExpanderPP) {
240   Inserter.registerPreprocessor(PP);
241 }
242 
check(const MatchFinder::MatchResult & Result)243 void PassByValueCheck::check(const MatchFinder::MatchResult &Result) {
244   const auto *Ctor = Result.Nodes.getNodeAs<CXXConstructorDecl>("Ctor");
245   const auto *ParamDecl = Result.Nodes.getNodeAs<ParmVarDecl>("Param");
246   const auto *Initializer =
247       Result.Nodes.getNodeAs<CXXCtorInitializer>("Initializer");
248   SourceManager &SM = *Result.SourceManager;
249 
250   // If the parameter is used or anything other than the copy, do not apply
251   // the changes.
252   if (!paramReferredExactlyOnce(Ctor, ParamDecl))
253     return;
254 
255   // If the parameter is trivial to copy, don't move it. Moving a trivially
256   // copyable type will cause a problem with performance-move-const-arg
257   if (ParamDecl->getType().getNonReferenceType().isTriviallyCopyableType(
258           *Result.Context))
259     return;
260 
261   // Do not trigger if we find a paired constructor with an rvalue.
262   if (hasRValueOverload(Ctor, ParamDecl))
263     return;
264 
265   auto Diag = diag(ParamDecl->getBeginLoc(), "pass by value and use std::move");
266 
267   // If we received a `const&` type, we need to rewrite the function
268   // declarations.
269   if (ParamDecl->getType()->isLValueReferenceType()) {
270     // Check if we can succesfully rewrite all declarations of the constructor.
271     for (const ParmVarDecl *ParmDecl : collectParamDecls(Ctor, ParamDecl)) {
272       TypeLoc ParamTL = ParmDecl->getTypeSourceInfo()->getTypeLoc();
273       ReferenceTypeLoc RefTL = ParamTL.getAs<ReferenceTypeLoc>();
274       if (RefTL.isNull()) {
275         // We cannot rewrite this instance. The type is probably hidden behind
276         // some `typedef`. Do not offer a fix-it in this case.
277         return;
278       }
279     }
280     // Rewrite all declarations.
281     for (const ParmVarDecl *ParmDecl : collectParamDecls(Ctor, ParamDecl)) {
282       TypeLoc ParamTL = ParmDecl->getTypeSourceInfo()->getTypeLoc();
283       ReferenceTypeLoc RefTL = ParamTL.getAs<ReferenceTypeLoc>();
284 
285       TypeLoc ValueTL = RefTL.getPointeeLoc();
286       CharSourceRange TypeRange = CharSourceRange::getTokenRange(
287           ParmDecl->getBeginLoc(), ParamTL.getEndLoc());
288       std::string ValueStr =
289           Lexer::getSourceText(
290               CharSourceRange::getTokenRange(ValueTL.getSourceRange()), SM,
291               getLangOpts())
292               .str();
293       ValueStr += ' ';
294       Diag << FixItHint::CreateReplacement(TypeRange, ValueStr);
295     }
296   }
297 
298   // Use std::move in the initialization list.
299   Diag << FixItHint::CreateInsertion(Initializer->getRParenLoc(), ")")
300        << FixItHint::CreateInsertion(
301               Initializer->getLParenLoc().getLocWithOffset(1), "std::move(")
302        << Inserter.createIncludeInsertion(
303               Result.SourceManager->getFileID(Initializer->getSourceLocation()),
304               "<utility>");
305 }
306 
307 } // namespace modernize
308 } // namespace tidy
309 } // namespace clang
310