1 //===--- PassByValueCheck.cpp - clang-tidy---------------------------------===//
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
7 //===----------------------------------------------------------------------===//
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"
18 using namespace clang::ast_matchers
;
21 namespace clang::tidy::modernize
{
24 /// Matches move-constructible classes.
28 /// // POD types are trivially move constructible.
29 /// struct Foo { int a; };
32 /// Bar(Bar &&) = deleted;
36 /// recordDecl(isMoveConstructible())
38 AST_MATCHER(CXXRecordDecl
, isMoveConstructible
) {
39 for (const CXXConstructorDecl
*Ctor
: Node
.ctors()) {
40 if (Ctor
->isMoveConstructor() && !Ctor
->isDeleted())
47 static TypeMatcher
notTemplateSpecConstRefType() {
48 return lValueReferenceType(
49 pointee(unless(elaboratedType(namesType(templateSpecializationType()))),
53 static TypeMatcher
nonConstValueType() {
54 return qualType(unless(anyOf(referenceType(), isConstQualified())));
57 /// Whether or not \p ParamDecl is used exactly one time in \p Ctor.
59 /// Checks both in the init-list and the body of the constructor.
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.
65 /// \see ExactlyOneUsageVisitor::hasExactlyOneUsageIn()
66 class ExactlyOneUsageVisitor
67 : public RecursiveASTVisitor
<ExactlyOneUsageVisitor
> {
68 friend class RecursiveASTVisitor
<ExactlyOneUsageVisitor
>;
71 ExactlyOneUsageVisitor(const ParmVarDecl
*ParamDecl
)
72 : ParamDecl(ParamDecl
) {}
74 /// Whether or not the parameter variable is referred only once in
76 /// given constructor.
77 bool hasExactlyOneUsageIn(const CXXConstructorDecl
*Ctor
) {
79 TraverseDecl(const_cast<CXXConstructorDecl
*>(Ctor
));
84 /// Counts the number of references to a variable.
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
) {
92 // No need to look further, used more than once.
100 const ParmVarDecl
*ParamDecl
;
104 return ExactlyOneUsageVisitor(ParamDecl
).hasExactlyOneUsageIn(Ctor
);
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.
116 /// A::A(const B& Param)
119 /// A::A(const B& Param, const C&)
120 /// A::A(B&& Param, C&&)
122 /// A::A(const B&, const C& Param)
123 /// A::A(B&&, C&& Param)
125 /// A::A(const B&, const C& Param)
126 /// A::A(const B&, C&& Param)
128 /// A::A(const B& Param, int)
129 /// A::A(B&& Param, int)
130 static bool hasRValueOverload(const CXXConstructorDecl
*Ctor
,
131 const ParmVarDecl
*Param
) {
132 if (!Param
->getType().getCanonicalType()->isLValueReferenceType()) {
133 // The parameter is passed by value.
136 const int ParamIdx
= Param
->getFunctionScopeIndex();
137 const CXXRecordDecl
*Record
= Ctor
->getParent();
139 // Check whether a ctor `C` forms a pair with `Ctor` under the aforementioned
141 const auto IsRValueOverload
= [&Ctor
, ParamIdx
](const CXXConstructorDecl
*C
) {
142 if (C
== Ctor
|| C
->isDeleted() ||
143 C
->getNumParams() != Ctor
->getNumParams())
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();
156 // The parameter of interest must be paired.
157 if (!IsLValueRValuePair
)
160 // All other parameters can be similar or paired.
161 if (!(CandidateParamType
== CtorParamType
|| IsLValueRValuePair
))
168 for (const auto *Candidate
: Record
->ctors()) {
169 if (IsRValueOverload(Candidate
)) {
176 /// Find all references to \p ParamDecl across all of the
177 /// redeclarations of \p Ctor.
178 static SmallVector
<const ParmVarDecl
*, 2>
179 collectParamDecls(const CXXConstructorDecl
*Ctor
,
180 const ParmVarDecl
*ParamDecl
) {
181 SmallVector
<const ParmVarDecl
*, 2> Results
;
182 unsigned ParamIdx
= ParamDecl
->getFunctionScopeIndex();
184 for (const FunctionDecl
*Redecl
: Ctor
->redecls())
185 Results
.push_back(Redecl
->getParamDecl(ParamIdx
));
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)) {}
196 void PassByValueCheck::storeOptions(ClangTidyOptions::OptionMap
&Opts
) {
197 Options
.store(Opts
, "IncludeStyle", Inserter
.getStyle());
198 Options
.store(Opts
, "ValuesOnly", ValuesOnly
);
201 void PassByValueCheck::registerMatchers(MatchFinder
*Finder
) {
206 forEachConstructorInitializer(
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
214 withInitializer(cxxConstructExpr(
215 has(ignoringParenImpCasts(declRefExpr(to(
218 // Match only const-ref or a non-const
219 // value parameters. Rvalues,
220 // TemplateSpecializationValues and
221 // const-values shouldn't be modified.
223 ? nonConstValueType()
224 : anyOf(notTemplateSpecConstRefType(),
225 nonConstValueType()))))
227 hasDeclaration(cxxConstructorDecl(
228 isCopyConstructor(), unless(isDeleted()),
230 cxxRecordDecl(isMoveConstructible())))))))
231 .bind("Initializer")))
236 void PassByValueCheck::registerPPCallbacks(const SourceManager
&SM
,
238 Preprocessor
*ModuleExpanderPP
) {
239 Inserter
.registerPreprocessor(PP
);
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
;
249 // If the parameter is used or anything other than the copy, do not apply
251 if (!paramReferredExactlyOnce(Ctor
, ParamDecl
))
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(
260 // Do not trigger if we find a paired constructor with an rvalue.
261 if (hasRValueOverload(Ctor
, ParamDecl
))
264 auto Diag
= diag(ParamDecl
->getBeginLoc(), "pass by value and use std::move");
266 // If we received a `const&` type, we need to rewrite the function
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.
279 // Rewrite all declarations.
280 for (const ParmVarDecl
*ParmDecl
: collectParamDecls(Ctor
, ParamDecl
)) {
281 TypeLoc ParamTL
= ParmDecl
->getTypeSourceInfo()->getTypeLoc();
282 auto RefTL
= ParamTL
.getAs
<ReferenceTypeLoc
>();
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
,
293 Diag
<< FixItHint::CreateReplacement(TypeRange
, ValueStr
);
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()),
306 } // namespace clang::tidy::modernize