clang-tools  15.0.0git
UseEqualsDefaultCheck.cpp
Go to the documentation of this file.
1 //===--- UseEqualsDefaultCheck.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 
10 #include "../utils/LexerUtils.h"
11 #include "clang/AST/ASTContext.h"
12 #include "clang/ASTMatchers/ASTMatchFinder.h"
13 #include "clang/Lex/Lexer.h"
14 
15 using namespace clang::ast_matchers;
16 
17 namespace clang {
18 namespace tidy {
19 namespace modernize {
20 
21 static const char SpecialFunction[] = "SpecialFunction";
22 
23 /// Finds all the named non-static fields of \p Record.
24 static std::set<const FieldDecl *>
25 getAllNamedFields(const CXXRecordDecl *Record) {
26  std::set<const FieldDecl *> Result;
27  for (const auto *Field : Record->fields()) {
28  // Static data members are not in this range.
29  if (Field->isUnnamedBitfield())
30  continue;
31  Result.insert(Field);
32  }
33  return Result;
34 }
35 
36 /// Returns the names of the direct bases of \p Record, both virtual and
37 /// non-virtual.
38 static std::set<const Type *> getAllDirectBases(const CXXRecordDecl *Record) {
39  std::set<const Type *> Result;
40  for (auto Base : Record->bases()) {
41  // CXXBaseSpecifier.
42  const auto *BaseType = Base.getTypeSourceInfo()->getType().getTypePtr();
43  Result.insert(BaseType);
44  }
45  return Result;
46 }
47 
48 /// Returns a matcher that matches member expressions where the base is
49 /// the variable declared as \p Var and the accessed member is the one declared
50 /// as \p Field.
51 internal::Matcher<Expr> accessToFieldInVar(const FieldDecl *Field,
52  const ValueDecl *Var) {
53  return ignoringImpCasts(
54  memberExpr(hasObjectExpression(declRefExpr(to(varDecl(equalsNode(Var))))),
55  member(fieldDecl(equalsNode(Field)))));
56 }
57 
58 /// Check that the given constructor has copy signature and that it
59 /// copy-initializes all its bases and members.
60 static bool isCopyConstructorAndCanBeDefaulted(ASTContext *Context,
61  const CXXConstructorDecl *Ctor) {
62  // An explicitly-defaulted constructor cannot have default arguments.
63  if (Ctor->getMinRequiredArguments() != 1)
64  return false;
65 
66  const auto *Record = Ctor->getParent();
67  const auto *Param = Ctor->getParamDecl(0);
68 
69  // Base classes and members that have to be copied.
70  auto BasesToInit = getAllDirectBases(Record);
71  auto FieldsToInit = getAllNamedFields(Record);
72 
73  // Ensure that all the bases are copied.
74  for (const auto *Base : BasesToInit) {
75  // The initialization of a base class should be a call to a copy
76  // constructor of the base.
77  if (match(
78  traverse(TK_AsIs,
79  cxxConstructorDecl(
80  forEachConstructorInitializer(cxxCtorInitializer(
81  isBaseInitializer(),
82  withInitializer(cxxConstructExpr(
83  hasType(equalsNode(Base)),
84  hasDeclaration(
85  cxxConstructorDecl(isCopyConstructor())),
86  argumentCountIs(1),
87  hasArgument(0, declRefExpr(to(varDecl(
88  equalsNode(Param))))))))))),
89  *Ctor, *Context)
90  .empty())
91  return false;
92  }
93 
94  // Ensure that all the members are copied.
95  for (const auto *Field : FieldsToInit) {
96  auto AccessToFieldInParam = accessToFieldInVar(Field, Param);
97  // The initialization is a CXXConstructExpr for class types.
98  if (match(traverse(
99  TK_AsIs,
100  cxxConstructorDecl(
101  forEachConstructorInitializer(cxxCtorInitializer(
102  isMemberInitializer(), forField(equalsNode(Field)),
103  withInitializer(anyOf(
104  AccessToFieldInParam,
105  initListExpr(has(AccessToFieldInParam)),
106  cxxConstructExpr(
107  hasDeclaration(
108  cxxConstructorDecl(isCopyConstructor())),
109  argumentCountIs(1),
110  hasArgument(0, AccessToFieldInParam)))))))),
111  *Ctor, *Context)
112  .empty())
113  return false;
114  }
115 
116  // Ensure that we don't do anything else, like initializing an indirect base.
117  return Ctor->getNumCtorInitializers() ==
118  BasesToInit.size() + FieldsToInit.size();
119 }
120 
121 /// Checks that the given method is an overloading of the assignment
122 /// operator, has copy signature, returns a reference to "*this" and copies
123 /// all its members and subobjects.
124 static bool isCopyAssignmentAndCanBeDefaulted(ASTContext *Context,
125  const CXXMethodDecl *Operator) {
126  const auto *Record = Operator->getParent();
127  const auto *Param = Operator->getParamDecl(0);
128 
129  // Base classes and members that have to be copied.
130  auto BasesToInit = getAllDirectBases(Record);
131  auto FieldsToInit = getAllNamedFields(Record);
132 
133  const auto *Compound = cast<CompoundStmt>(Operator->getBody());
134 
135  // The assignment operator definition has to end with the following return
136  // statement:
137  // return *this;
138  if (Compound->body_empty() ||
139  match(traverse(
140  TK_AsIs,
141  returnStmt(has(ignoringParenImpCasts(unaryOperator(
142  hasOperatorName("*"), hasUnaryOperand(cxxThisExpr())))))),
143  *Compound->body_back(), *Context)
144  .empty())
145  return false;
146 
147  // Ensure that all the bases are copied.
148  for (const auto *Base : BasesToInit) {
149  // Assignment operator of a base class:
150  // Base::operator=(Other);
151  //
152  // Clang translates this into:
153  // ((Base*)this)->operator=((Base)Other);
154  //
155  // So we are looking for a member call that fulfills:
156  if (match(traverse(TK_AsIs,
157  compoundStmt(has(ignoringParenImpCasts(cxxMemberCallExpr(
158  // - The object is an implicit cast of 'this' to a
159  // pointer to
160  // a base class.
161  onImplicitObjectArgument(implicitCastExpr(
162  hasImplicitDestinationType(
163  pointsTo(type(equalsNode(Base)))),
164  hasSourceExpression(cxxThisExpr()))),
165  // - The called method is the operator=.
166  callee(cxxMethodDecl(isCopyAssignmentOperator())),
167  // - The argument is (an implicit cast to a Base of)
168  // the argument taken by "Operator".
169  argumentCountIs(1),
170  hasArgument(0, declRefExpr(to(varDecl(
171  equalsNode(Param)))))))))),
172  *Compound, *Context)
173  .empty())
174  return false;
175  }
176 
177  // Ensure that all the members are copied.
178  for (const auto *Field : FieldsToInit) {
179  // The assignment of data members:
180  // Field = Other.Field;
181  // Is a BinaryOperator in non-class types, and a CXXOperatorCallExpr
182  // otherwise.
183  auto LHS = memberExpr(hasObjectExpression(cxxThisExpr()),
184  member(fieldDecl(equalsNode(Field))));
185  auto RHS = accessToFieldInVar(Field, Param);
186  if (match(traverse(TK_AsIs,
187  compoundStmt(has(ignoringParenImpCasts(binaryOperation(
188  hasOperatorName("="), hasLHS(LHS), hasRHS(RHS)))))),
189  *Compound, *Context)
190  .empty())
191  return false;
192  }
193 
194  // Ensure that we don't do anything else.
195  return Compound->size() == BasesToInit.size() + FieldsToInit.size() + 1;
196 }
197 
198 /// Returns false if the body has any non-whitespace character.
199 static bool bodyEmpty(const ASTContext *Context, const CompoundStmt *Body) {
200  bool Invalid = false;
201  StringRef Text = Lexer::getSourceText(
202  CharSourceRange::getCharRange(Body->getLBracLoc().getLocWithOffset(1),
203  Body->getRBracLoc()),
204  Context->getSourceManager(), Context->getLangOpts(), &Invalid);
205  return !Invalid && std::strspn(Text.data(), " \t\r\n") == Text.size();
206 }
207 
208 UseEqualsDefaultCheck::UseEqualsDefaultCheck(StringRef Name,
209  ClangTidyContext *Context)
210  : ClangTidyCheck(Name, Context),
211  IgnoreMacros(Options.getLocalOrGlobal("IgnoreMacros", true)) {}
212 
214  Options.store(Opts, "IgnoreMacros", IgnoreMacros);
215 }
216 
217 void UseEqualsDefaultCheck::registerMatchers(MatchFinder *Finder) {
218  // Destructor.
219  Finder->addMatcher(cxxDestructorDecl(isDefinition()).bind(SpecialFunction),
220  this);
221  Finder->addMatcher(
222  cxxConstructorDecl(
223  isDefinition(),
224  anyOf(
225  // Default constructor.
226  allOf(unless(hasAnyConstructorInitializer(isWritten())),
227  parameterCountIs(0)),
228  // Copy constructor.
229  allOf(isCopyConstructor(),
230  // Discard constructors that can be used as a copy
231  // constructor because all the other arguments have
232  // default values.
233  parameterCountIs(1))))
234  .bind(SpecialFunction),
235  this);
236  // Copy-assignment operator.
237  Finder->addMatcher(
238  cxxMethodDecl(isDefinition(), isCopyAssignmentOperator(),
239  // isCopyAssignmentOperator() allows the parameter to be
240  // passed by value, and in this case it cannot be
241  // defaulted.
242  hasParameter(0, hasType(lValueReferenceType())))
243  .bind(SpecialFunction),
244  this);
245 }
246 
247 void UseEqualsDefaultCheck::check(const MatchFinder::MatchResult &Result) {
248  // Both CXXConstructorDecl and CXXDestructorDecl inherit from CXXMethodDecl.
249  const auto *SpecialFunctionDecl =
250  Result.Nodes.getNodeAs<CXXMethodDecl>(SpecialFunction);
251 
252  if (IgnoreMacros && SpecialFunctionDecl->getLocation().isMacroID())
253  return;
254 
255  // Discard explicitly deleted/defaulted special member functions and those
256  // that are not user-provided (automatically generated).
257  if (SpecialFunctionDecl->isDeleted() ||
258  SpecialFunctionDecl->isExplicitlyDefaulted() ||
259  SpecialFunctionDecl->isLateTemplateParsed() ||
260  SpecialFunctionDecl->isTemplateInstantiation() ||
261  !SpecialFunctionDecl->isUserProvided() || !SpecialFunctionDecl->hasBody())
262  return;
263 
264  const auto *Body = dyn_cast<CompoundStmt>(SpecialFunctionDecl->getBody());
265  if (!Body)
266  return;
267 
268  // If there is code inside the body, don't warn.
269  if (!SpecialFunctionDecl->isCopyAssignmentOperator() && !Body->body_empty())
270  return;
271 
272  // If there are comments inside the body, don't do the change.
273  bool ApplyFix = SpecialFunctionDecl->isCopyAssignmentOperator() ||
274  bodyEmpty(Result.Context, Body);
275 
276  std::vector<FixItHint> RemoveInitializers;
277  unsigned MemberType;
278  if (const auto *Ctor = dyn_cast<CXXConstructorDecl>(SpecialFunctionDecl)) {
279  if (Ctor->getNumParams() == 0) {
280  MemberType = 0;
281  } else {
282  if (!isCopyConstructorAndCanBeDefaulted(Result.Context, Ctor))
283  return;
284  MemberType = 1;
285  // If there are constructor initializers, they must be removed.
286  for (const auto *Init : Ctor->inits()) {
287  RemoveInitializers.emplace_back(
288  FixItHint::CreateRemoval(Init->getSourceRange()));
289  }
290  }
291  } else if (isa<CXXDestructorDecl>(SpecialFunctionDecl)) {
292  MemberType = 2;
293  } else {
294  if (!isCopyAssignmentAndCanBeDefaulted(Result.Context, SpecialFunctionDecl))
295  return;
296  MemberType = 3;
297  }
298 
299  // The location of the body is more useful inside a macro as spelling and
300  // expansion locations are reported.
301  SourceLocation Location = SpecialFunctionDecl->getLocation();
302  if (Location.isMacroID())
303  Location = Body->getBeginLoc();
304 
305  auto Diag = diag(
306  Location,
307  "use '= default' to define a trivial %select{default constructor|copy "
308  "constructor|destructor|copy-assignment operator}0");
309  Diag << MemberType;
310 
311  if (ApplyFix) {
312  // Skipping comments, check for a semicolon after Body->getSourceRange()
313  Optional<Token> Token = utils::lexer::findNextTokenSkippingComments(
314  Body->getSourceRange().getEnd().getLocWithOffset(1),
315  Result.Context->getSourceManager(), Result.Context->getLangOpts());
316  StringRef Replacement =
317  Token && Token->is(tok::semi) ? "= default" : "= default;";
318  Diag << FixItHint::CreateReplacement(Body->getSourceRange(), Replacement)
319  << RemoveInitializers;
320  }
321 }
322 
323 } // namespace modernize
324 } // namespace tidy
325 } // namespace clang
Base
std::unique_ptr< GlobalCompilationDatabase > Base
Definition: GlobalCompilationDatabaseTests.cpp:85
clang::tidy::ClangTidyOptions::OptionMap
llvm::StringMap< ClangTidyValue > OptionMap
Definition: ClangTidyOptions.h:115
clang::tidy::modernize::isCopyAssignmentAndCanBeDefaulted
static bool isCopyAssignmentAndCanBeDefaulted(ASTContext *Context, const CXXMethodDecl *Operator)
Checks that the given method is an overloading of the assignment operator, has copy signature,...
Definition: UseEqualsDefaultCheck.cpp:124
Location
Definition: Modularize.cpp:382
clang::tidy::cppcoreguidelines::getSourceText
static std::string getSourceText(const CXXDestructorDecl &Destructor)
Definition: VirtualClassDestructorCheck.cpp:112
Text
std::string Text
Definition: HTMLGenerator.cpp:80
clang::tidy::ClangTidyCheck
Base class for all clang-tidy checks.
Definition: ClangTidyCheck.h:53
clang::tidy::modernize::SpecialFunction
static const char SpecialFunction[]
Definition: UseEqualsDefaultCheck.cpp:21
clang::tidy::modernize::getAllDirectBases
static std::set< const Type * > getAllDirectBases(const CXXRecordDecl *Record)
Returns the names of the direct bases of Record, both virtual and non-virtual.
Definition: UseEqualsDefaultCheck.cpp:38
UseEqualsDefaultCheck.h
clang::ast_matchers
Definition: AbseilMatcher.h:14
clang::tidy::modernize::UseEqualsDefaultCheck::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: UseEqualsDefaultCheck.cpp:213
clang::clangd::match
std::vector< std::string > match(const SymbolIndex &I, const FuzzyFindRequest &Req, bool *Incomplete)
Definition: TestIndex.cpp:102
clang::tidy::modernize::getAllNamedFields
static std::set< const FieldDecl * > getAllNamedFields(const CXXRecordDecl *Record)
Finds all the named non-static fields of Record.
Definition: UseEqualsDefaultCheck.cpp:25
clang::tidy::ClangTidyCheck::Options
OptionsView Options
Definition: ClangTidyCheck.h:415
clang::tidy::modernize::bodyEmpty
static bool bodyEmpty(const ASTContext *Context, const CompoundStmt *Body)
Returns false if the body has any non-whitespace character.
Definition: UseEqualsDefaultCheck.cpp:199
clang::tidy::ClangTidyContext
Every ClangTidyCheck reports errors through a DiagnosticsEngine provided by this context.
Definition: ClangTidyDiagnosticConsumer.h:66
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::isCopyConstructorAndCanBeDefaulted
static bool isCopyConstructorAndCanBeDefaulted(ASTContext *Context, const CXXConstructorDecl *Ctor)
Check that the given constructor has copy signature and that it copy-initializes all its bases and me...
Definition: UseEqualsDefaultCheck.cpp:60
clang
===– Representation.cpp - ClangDoc Representation --------—*- C++ -*-===//
Definition: ApplyReplacements.h:27
clang::tidy::modernize::empty
static bool empty(SourceRange Range)
Definition: MacroToEnumCheck.cpp:532
clang::doc::Record
llvm::SmallVector< uint64_t, 1024 > Record
Definition: BitcodeReader.cpp:18
clang::tidy::modernize::UseEqualsDefaultCheck::check
void check(const ast_matchers::MatchFinder::MatchResult &Result) override
ClangTidyChecks that register ASTMatchers should do the actual work in here.
Definition: UseEqualsDefaultCheck.cpp:247
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
clang::tidy::modernize::UseEqualsDefaultCheck::registerMatchers
void registerMatchers(ast_matchers::MatchFinder *Finder) override
Override this to register AST matchers with Finder.
Definition: UseEqualsDefaultCheck.cpp:217
Field
const FieldDecl * Field
Definition: MemberwiseConstructor.cpp:260
clang::tidy::utils::lexer::findNextTokenSkippingComments
Optional< Token > findNextTokenSkippingComments(SourceLocation Start, const SourceManager &SM, const LangOptions &LangOpts)
Definition: LexerUtils.cpp:80
clang::tidy::modernize::accessToFieldInVar
internal::Matcher< Expr > accessToFieldInVar(const FieldDecl *Field, const ValueDecl *Var)
Returns a matcher that matches member expressions where the base is the variable declared as Var and ...
Definition: UseEqualsDefaultCheck.cpp:51