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
23namespace {
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".
38AST_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
47static TypeMatcher notTemplateSpecConstRefType() {
48 return lValueReferenceType(
49 pointee(unless(elaboratedType(namesType(InnerMatcher: templateSpecializationType()))),
50 isConstQualified()));
51}
52
53static 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.
60static 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>(Val: 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)
130static 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 return false;
173}
174
175/// Find all references to \p ParamDecl across all of the
176/// redeclarations of \p Ctor.
177static SmallVector<const ParmVarDecl *, 2>
178collectParamDecls(const CXXConstructorDecl *Ctor,
179 const ParmVarDecl *ParamDecl) {
180 SmallVector<const ParmVarDecl *, 2> Results;
181 unsigned ParamIdx = ParamDecl->getFunctionScopeIndex();
182
183 for (const FunctionDecl *Redecl : Ctor->redecls())
184 Results.push_back(Redecl->getParamDecl(ParamIdx));
185 return Results;
186}
187
188PassByValueCheck::PassByValueCheck(StringRef Name, ClangTidyContext *Context)
189 : ClangTidyCheck(Name, Context),
190 Inserter(Options.getLocalOrGlobal(LocalName: "IncludeStyle",
191 Default: utils::IncludeSorter::IS_LLVM),
192 areDiagsSelfContained()),
193 ValuesOnly(Options.get(LocalName: "ValuesOnly", Default: false)) {}
194
195void PassByValueCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) {
196 Options.store(Options&: Opts, LocalName: "IncludeStyle", Value: Inserter.getStyle());
197 Options.store(Options&: Opts, LocalName: "ValuesOnly", Value: ValuesOnly);
198}
199
200void PassByValueCheck::registerMatchers(MatchFinder *Finder) {
201 Finder->addMatcher(
202 NodeMatch: traverse(
203 TK: TK_AsIs,
204 InnerMatcher: cxxConstructorDecl(
205 forEachConstructorInitializer(
206 InnerMatcher: cxxCtorInitializer(
207 unless(isBaseInitializer()),
208 // Clang builds a CXXConstructExpr only when it knows
209 // which constructor will be called. In dependent contexts
210 // a ParenListExpr is generated instead of a
211 // CXXConstructExpr, filtering out templates automatically
212 // for us.
213 withInitializer(InnerMatcher: cxxConstructExpr(
214 has(ignoringParenImpCasts(InnerMatcher: declRefExpr(to(
215 InnerMatcher: parmVarDecl(
216 hasType(InnerMatcher: qualType(
217 // Match only const-ref or a non-const
218 // value parameters. Rvalues,
219 // TemplateSpecializationValues and
220 // const-values shouldn't be modified.
221 ValuesOnly
222 ? nonConstValueType()
223 : anyOf(notTemplateSpecConstRefType(),
224 nonConstValueType()))))
225 .bind(ID: "Param"))))),
226 hasDeclaration(InnerMatcher: cxxConstructorDecl(
227 isCopyConstructor(), unless(isDeleted()),
228 hasDeclContext(
229 InnerMatcher: cxxRecordDecl(isMoveConstructible())))))))
230 .bind(ID: "Initializer")))
231 .bind(ID: "Ctor")),
232 Action: this);
233}
234
235void PassByValueCheck::registerPPCallbacks(const SourceManager &SM,
236 Preprocessor *PP,
237 Preprocessor *ModuleExpanderPP) {
238 Inserter.registerPreprocessor(PP);
239}
240
241void PassByValueCheck::check(const MatchFinder::MatchResult &Result) {
242 const auto *Ctor = Result.Nodes.getNodeAs<CXXConstructorDecl>(ID: "Ctor");
243 const auto *ParamDecl = Result.Nodes.getNodeAs<ParmVarDecl>(ID: "Param");
244 const auto *Initializer =
245 Result.Nodes.getNodeAs<CXXCtorInitializer>(ID: "Initializer");
246 SourceManager &SM = *Result.SourceManager;
247
248 // If the parameter is used or anything other than the copy, do not apply
249 // the changes.
250 if (!paramReferredExactlyOnce(Ctor, ParamDecl))
251 return;
252
253 // If the parameter is trivial to copy, don't move it. Moving a trivially
254 // copyable type will cause a problem with performance-move-const-arg
255 if (ParamDecl->getType().getNonReferenceType().isTriviallyCopyableType(
256 *Result.Context))
257 return;
258
259 // Do not trigger if we find a paired constructor with an rvalue.
260 if (hasRValueOverload(Ctor, Param: ParamDecl))
261 return;
262
263 auto Diag = diag(ParamDecl->getBeginLoc(), "pass by value and use std::move");
264
265 // If we received a `const&` type, we need to rewrite the function
266 // declarations.
267 if (ParamDecl->getType()->isLValueReferenceType()) {
268 // Check if we can succesfully rewrite all declarations of the constructor.
269 for (const ParmVarDecl *ParmDecl : collectParamDecls(Ctor, ParamDecl)) {
270 TypeLoc ParamTL = ParmDecl->getTypeSourceInfo()->getTypeLoc();
271 auto RefTL = ParamTL.getAs<ReferenceTypeLoc>();
272 if (RefTL.isNull()) {
273 // We cannot rewrite this instance. The type is probably hidden behind
274 // some `typedef`. Do not offer a fix-it in this case.
275 return;
276 }
277 }
278 // Rewrite all declarations.
279 for (const ParmVarDecl *ParmDecl : collectParamDecls(Ctor, ParamDecl)) {
280 TypeLoc ParamTL = ParmDecl->getTypeSourceInfo()->getTypeLoc();
281 auto RefTL = ParamTL.getAs<ReferenceTypeLoc>();
282
283 TypeLoc ValueTL = RefTL.getPointeeLoc();
284 CharSourceRange TypeRange = CharSourceRange::getTokenRange(
285 ParmDecl->getBeginLoc(), ParamTL.getEndLoc());
286 std::string ValueStr =
287 Lexer::getSourceText(
288 Range: CharSourceRange::getTokenRange(R: ValueTL.getSourceRange()), SM,
289 LangOpts: getLangOpts())
290 .str();
291 ValueStr += ' ';
292 Diag << FixItHint::CreateReplacement(RemoveRange: TypeRange, Code: ValueStr);
293 }
294 }
295
296 // Use std::move in the initialization list.
297 Diag << FixItHint::CreateInsertion(InsertionLoc: Initializer->getRParenLoc(), Code: ")")
298 << FixItHint::CreateInsertion(
299 InsertionLoc: Initializer->getLParenLoc().getLocWithOffset(Offset: 1), Code: "std::move(")
300 << Inserter.createIncludeInsertion(
301 FileID: Result.SourceManager->getFileID(SpellingLoc: Initializer->getSourceLocation()),
302 Header: "<utility>");
303}
304
305} // namespace clang::tidy::modernize
306

Provided by KDAB

Privacy Policy
Learn to use CMake with our Intro Training
Find out more

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