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

source code of clang-tools-extra/clang-tidy/modernize/PassByValueCheck.cpp