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