xref: /llvm-project/clang-tools-extra/clang-tidy/modernize/PassByValueCheck.cpp (revision 857f532fd5c9de92fe861374ad82e3a36beeb166)
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::tidy::modernize {
22 
23 namespace {
24 /// Matches move-constructible classes.
25 ///
26 /// Given
27 /// \code
28 ///   // POD types are trivially move constructible.
29 ///   struct Foo { int a; };
30 ///
31 ///   struct Bar {
32 ///     Bar(Bar &&) = deleted;
33 ///     int a;
34 ///   };
35 /// \endcode
36 /// recordDecl(isMoveConstructible())
37 ///   matches "Foo".
AST_MATCHER(CXXRecordDecl,isMoveConstructible)38 AST_MATCHER(CXXRecordDecl, isMoveConstructible) {
39   for (const CXXConstructorDecl *Ctor : Node.ctors()) {
40     if (Ctor->isMoveConstructor() && !Ctor->isDeleted())
41       return true;
42   }
43   return false;
44 }
45 } // namespace
46 
notTemplateSpecConstRefType()47 static TypeMatcher notTemplateSpecConstRefType() {
48   return lValueReferenceType(
49       pointee(unless(elaboratedType(namesType(templateSpecializationType()))),
50               isConstQualified()));
51 }
52 
nonConstValueType()53 static TypeMatcher nonConstValueType() {
54   return qualType(unless(anyOf(referenceType(), isConstQualified())));
55 }
56 
57 /// Whether or not \p ParamDecl is used exactly one time in \p Ctor.
58 ///
59 /// Checks both in the init-list and the body of the constructor.
paramReferredExactlyOnce(const CXXConstructorDecl * Ctor,const ParmVarDecl * ParamDecl)60 static bool paramReferredExactlyOnce(const CXXConstructorDecl *Ctor,
61                                      const ParmVarDecl *ParamDecl) {
62   /// \c clang::RecursiveASTVisitor that checks that the given
63   /// \c ParmVarDecl is used exactly one time.
64   ///
65   /// \see ExactlyOneUsageVisitor::hasExactlyOneUsageIn()
66   class ExactlyOneUsageVisitor
67       : public RecursiveASTVisitor<ExactlyOneUsageVisitor> {
68     friend class RecursiveASTVisitor<ExactlyOneUsageVisitor>;
69 
70   public:
71     ExactlyOneUsageVisitor(const ParmVarDecl *ParamDecl)
72         : ParamDecl(ParamDecl) {}
73 
74     /// Whether or not the parameter variable is referred only once in
75     /// the
76     /// given constructor.
77     bool hasExactlyOneUsageIn(const CXXConstructorDecl *Ctor) {
78       Count = 0U;
79       TraverseDecl(const_cast<CXXConstructorDecl *>(Ctor));
80       return Count == 1U;
81     }
82 
83   private:
84     /// Counts the number of references to a variable.
85     ///
86     /// Stops the AST traversal if more than one usage is found.
87     bool VisitDeclRefExpr(DeclRefExpr *D) {
88       if (const ParmVarDecl *To = dyn_cast<ParmVarDecl>(D->getDecl())) {
89         if (To == ParamDecl) {
90           ++Count;
91           if (Count > 1U) {
92             // No need to look further, used more than once.
93             return false;
94           }
95         }
96       }
97       return true;
98     }
99 
100     const ParmVarDecl *ParamDecl;
101     unsigned Count = 0U;
102   };
103 
104   return ExactlyOneUsageVisitor(ParamDecl).hasExactlyOneUsageIn(Ctor);
105 }
106 
107 /// Returns true if the given constructor is part of a lvalue/rvalue reference
108 /// pair, i.e. `Param` is of lvalue reference type, and there exists another
109 /// constructor such that:
110 ///  - it has the same number of parameters as `Ctor`.
111 ///  - the parameter at the same index as `Param` is an rvalue reference
112 ///    of the same pointee type
113 ///  - all other parameters have the same type as the corresponding parameter in
114 ///    `Ctor` or are rvalue references with the same pointee type.
115 /// Examples:
116 ///  A::A(const B& Param)
117 ///  A::A(B&&)
118 ///
119 ///  A::A(const B& Param, const C&)
120 ///  A::A(B&& Param, C&&)
121 ///
122 ///  A::A(const B&, const C& Param)
123 ///  A::A(B&&, C&& Param)
124 ///
125 ///  A::A(const B&, const C& Param)
126 ///  A::A(const B&, C&& Param)
127 ///
128 ///  A::A(const B& Param, int)
129 ///  A::A(B&& Param, int)
hasRValueOverload(const CXXConstructorDecl * Ctor,const ParmVarDecl * Param)130 static bool hasRValueOverload(const CXXConstructorDecl *Ctor,
131                               const ParmVarDecl *Param) {
132   if (!Param->getType().getCanonicalType()->isLValueReferenceType()) {
133     // The parameter is passed by value.
134     return false;
135   }
136   const int ParamIdx = Param->getFunctionScopeIndex();
137   const CXXRecordDecl *Record = Ctor->getParent();
138 
139   // Check whether a ctor `C` forms a pair with `Ctor` under the aforementioned
140   // rules.
141   const auto IsRValueOverload = [&Ctor, ParamIdx](const CXXConstructorDecl *C) {
142     if (C == Ctor || C->isDeleted() ||
143         C->getNumParams() != Ctor->getNumParams())
144       return false;
145     for (int I = 0, E = C->getNumParams(); I < E; ++I) {
146       const clang::QualType CandidateParamType =
147           C->parameters()[I]->getType().getCanonicalType();
148       const clang::QualType CtorParamType =
149           Ctor->parameters()[I]->getType().getCanonicalType();
150       const bool IsLValueRValuePair =
151           CtorParamType->isLValueReferenceType() &&
152           CandidateParamType->isRValueReferenceType() &&
153           CandidateParamType->getPointeeType()->getUnqualifiedDesugaredType() ==
154               CtorParamType->getPointeeType()->getUnqualifiedDesugaredType();
155       if (I == ParamIdx) {
156         // The parameter of interest must be paired.
157         if (!IsLValueRValuePair)
158           return false;
159       } else {
160         // All other parameters can be similar or paired.
161         if (!(CandidateParamType == CtorParamType || IsLValueRValuePair))
162           return false;
163       }
164     }
165     return true;
166   };
167 
168   for (const auto *Candidate : Record->ctors()) {
169     if (IsRValueOverload(Candidate)) {
170       return true;
171     }
172   }
173   return false;
174 }
175 
176 /// Find all references to \p ParamDecl across all of the
177 /// redeclarations of \p Ctor.
178 static SmallVector<const ParmVarDecl *, 2>
collectParamDecls(const CXXConstructorDecl * Ctor,const ParmVarDecl * ParamDecl)179 collectParamDecls(const CXXConstructorDecl *Ctor,
180                   const ParmVarDecl *ParamDecl) {
181   SmallVector<const ParmVarDecl *, 2> Results;
182   unsigned ParamIdx = ParamDecl->getFunctionScopeIndex();
183 
184   for (const FunctionDecl *Redecl : Ctor->redecls())
185     Results.push_back(Redecl->getParamDecl(ParamIdx));
186   return Results;
187 }
188 
PassByValueCheck(StringRef Name,ClangTidyContext * Context)189 PassByValueCheck::PassByValueCheck(StringRef Name, ClangTidyContext *Context)
190     : ClangTidyCheck(Name, Context),
191       Inserter(Options.getLocalOrGlobal("IncludeStyle",
192                                         utils::IncludeSorter::IS_LLVM),
193                areDiagsSelfContained()),
194       ValuesOnly(Options.get("ValuesOnly", false)) {}
195 
storeOptions(ClangTidyOptions::OptionMap & Opts)196 void PassByValueCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) {
197   Options.store(Opts, "IncludeStyle", Inserter.getStyle());
198   Options.store(Opts, "ValuesOnly", ValuesOnly);
199 }
200 
registerMatchers(MatchFinder * Finder)201 void PassByValueCheck::registerMatchers(MatchFinder *Finder) {
202   Finder->addMatcher(
203       traverse(
204           TK_AsIs,
205           cxxConstructorDecl(
206               forEachConstructorInitializer(
207                   cxxCtorInitializer(
208                       unless(isBaseInitializer()),
209                       // Clang builds a CXXConstructExpr only when it knows
210                       // which constructor will be called. In dependent contexts
211                       // a ParenListExpr is generated instead of a
212                       // CXXConstructExpr, filtering out templates automatically
213                       // for us.
214                       withInitializer(cxxConstructExpr(
215                           has(ignoringParenImpCasts(declRefExpr(to(
216                               parmVarDecl(
217                                   hasType(qualType(
218                                       // Match only const-ref or a non-const
219                                       // value parameters. Rvalues,
220                                       // TemplateSpecializationValues and
221                                       // const-values shouldn't be modified.
222                                       ValuesOnly
223                                           ? nonConstValueType()
224                                           : anyOf(notTemplateSpecConstRefType(),
225                                                   nonConstValueType()))))
226                                   .bind("Param"))))),
227                           hasDeclaration(cxxConstructorDecl(
228                               isCopyConstructor(), unless(isDeleted()),
229                               hasDeclContext(
230                                   cxxRecordDecl(isMoveConstructible())))))))
231                       .bind("Initializer")))
232               .bind("Ctor")),
233       this);
234 }
235 
registerPPCallbacks(const SourceManager & SM,Preprocessor * PP,Preprocessor * ModuleExpanderPP)236 void PassByValueCheck::registerPPCallbacks(const SourceManager &SM,
237                                            Preprocessor *PP,
238                                            Preprocessor *ModuleExpanderPP) {
239   Inserter.registerPreprocessor(PP);
240 }
241 
check(const MatchFinder::MatchResult & Result)242 void PassByValueCheck::check(const MatchFinder::MatchResult &Result) {
243   const auto *Ctor = Result.Nodes.getNodeAs<CXXConstructorDecl>("Ctor");
244   const auto *ParamDecl = Result.Nodes.getNodeAs<ParmVarDecl>("Param");
245   const auto *Initializer =
246       Result.Nodes.getNodeAs<CXXCtorInitializer>("Initializer");
247   SourceManager &SM = *Result.SourceManager;
248 
249   // If the parameter is used or anything other than the copy, do not apply
250   // the changes.
251   if (!paramReferredExactlyOnce(Ctor, ParamDecl))
252     return;
253 
254   // If the parameter is trivial to copy, don't move it. Moving a trivially
255   // copyable type will cause a problem with performance-move-const-arg
256   if (ParamDecl->getType().getNonReferenceType().isTriviallyCopyableType(
257           *Result.Context))
258     return;
259 
260   // Do not trigger if we find a paired constructor with an rvalue.
261   if (hasRValueOverload(Ctor, ParamDecl))
262     return;
263 
264   auto Diag = diag(ParamDecl->getBeginLoc(), "pass by value and use std::move");
265 
266   // If we received a `const&` type, we need to rewrite the function
267   // declarations.
268   if (ParamDecl->getType()->isLValueReferenceType()) {
269     // Check if we can succesfully rewrite all declarations of the constructor.
270     for (const ParmVarDecl *ParmDecl : collectParamDecls(Ctor, ParamDecl)) {
271       TypeLoc ParamTL = ParmDecl->getTypeSourceInfo()->getTypeLoc();
272       auto RefTL = ParamTL.getAs<ReferenceTypeLoc>();
273       if (RefTL.isNull()) {
274         // We cannot rewrite this instance. The type is probably hidden behind
275         // some `typedef`. Do not offer a fix-it in this case.
276         return;
277       }
278     }
279     // Rewrite all declarations.
280     for (const ParmVarDecl *ParmDecl : collectParamDecls(Ctor, ParamDecl)) {
281       TypeLoc ParamTL = ParmDecl->getTypeSourceInfo()->getTypeLoc();
282       auto RefTL = ParamTL.getAs<ReferenceTypeLoc>();
283 
284       TypeLoc ValueTL = RefTL.getPointeeLoc();
285       CharSourceRange TypeRange = CharSourceRange::getTokenRange(
286           ParmDecl->getBeginLoc(), ParamTL.getEndLoc());
287       std::string ValueStr =
288           Lexer::getSourceText(
289               CharSourceRange::getTokenRange(ValueTL.getSourceRange()), SM,
290               getLangOpts())
291               .str();
292       ValueStr += ' ';
293       Diag << FixItHint::CreateReplacement(TypeRange, ValueStr);
294     }
295   }
296 
297   // Use std::move in the initialization list.
298   Diag << FixItHint::CreateInsertion(Initializer->getRParenLoc(), ")")
299        << FixItHint::CreateInsertion(
300               Initializer->getLParenLoc().getLocWithOffset(1), "std::move(")
301        << Inserter.createIncludeInsertion(
302               Result.SourceManager->getFileID(Initializer->getSourceLocation()),
303               "<utility>");
304 }
305 
306 } // namespace clang::tidy::modernize
307