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