xref: /llvm-project/clang-tools-extra/clang-tidy/modernize/UseEqualsDefaultCheck.cpp (revision 3d56ea05b6c746a7144f643bef2ebd599f605b8b)
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 
9 #include "UseEqualsDefaultCheck.h"
10 #include "../utils/LexerUtils.h"
11 #include "../utils/Matchers.h"
12 #include "clang/AST/ASTContext.h"
13 #include "clang/ASTMatchers/ASTMatchFinder.h"
14 #include "clang/Lex/Lexer.h"
15 #include <optional>
16 
17 using namespace clang::ast_matchers;
18 
19 namespace clang::tidy::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 *>
getAllNamedFields(const CXXRecordDecl * Record)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.
getAllDirectBases(const CXXRecordDecl * Record)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.
accessToFieldInVar(const FieldDecl * Field,const ValueDecl * Var)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.
isCopyConstructorAndCanBeDefaulted(ASTContext * Context,const CXXConstructorDecl * Ctor)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.
isCopyAssignmentAndCanBeDefaulted(ASTContext * Context,const CXXMethodDecl * Operator)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(
157                   TK_AsIs,
158                   compoundStmt(has(ignoringParenImpCasts(cxxMemberCallExpr(
159                       // - The object is an implicit cast of 'this' to a
160                       // pointer to
161                       //   a base class.
162                       onImplicitObjectArgument(implicitCastExpr(
163                           hasImplicitDestinationType(hasCanonicalType(pointsTo(
164                               type(equalsNode(Base->getCanonicalTypeInternal()
165                                                   .getTypePtr()))))),
166                           hasSourceExpression(cxxThisExpr()))),
167                       // - The called method is the operator=.
168                       callee(cxxMethodDecl(isCopyAssignmentOperator())),
169                       // - The argument is (an implicit cast to a Base of)
170                       // the argument taken by "Operator".
171                       argumentCountIs(1),
172                       hasArgument(
173                           0, declRefExpr(to(varDecl(equalsNode(Param)))))))))),
174               *Compound, *Context)
175             .empty())
176       return false;
177   }
178 
179   // Ensure that all the members are copied.
180   for (const auto *Field : FieldsToInit) {
181     // The assignment of data members:
182     //   Field = Other.Field;
183     // Is a BinaryOperator in non-class types, and a CXXOperatorCallExpr
184     // otherwise.
185     auto LHS = memberExpr(hasObjectExpression(cxxThisExpr()),
186                           member(fieldDecl(equalsNode(Field))));
187     auto RHS = accessToFieldInVar(Field, Param);
188     if (match(traverse(TK_AsIs,
189                        compoundStmt(has(ignoringParenImpCasts(binaryOperation(
190                            hasOperatorName("="), hasLHS(LHS), hasRHS(RHS)))))),
191               *Compound, *Context)
192             .empty())
193       return false;
194   }
195 
196   // Ensure that we don't do anything else.
197   return Compound->size() == BasesToInit.size() + FieldsToInit.size() + 1;
198 }
199 
200 /// Returns false if the body has any non-whitespace character.
bodyEmpty(const ASTContext * Context,const CompoundStmt * Body)201 static bool bodyEmpty(const ASTContext *Context, const CompoundStmt *Body) {
202   bool Invalid = false;
203   StringRef Text = Lexer::getSourceText(
204       CharSourceRange::getCharRange(Body->getLBracLoc().getLocWithOffset(1),
205                                     Body->getRBracLoc()),
206       Context->getSourceManager(), Context->getLangOpts(), &Invalid);
207   return !Invalid && std::strspn(Text.data(), " \t\r\n") == Text.size();
208 }
209 
UseEqualsDefaultCheck(StringRef Name,ClangTidyContext * Context)210 UseEqualsDefaultCheck::UseEqualsDefaultCheck(StringRef Name,
211                                              ClangTidyContext *Context)
212     : ClangTidyCheck(Name, Context),
213       IgnoreMacros(Options.getLocalOrGlobal("IgnoreMacros", true)) {}
214 
storeOptions(ClangTidyOptions::OptionMap & Opts)215 void UseEqualsDefaultCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) {
216   Options.store(Opts, "IgnoreMacros", IgnoreMacros);
217 }
218 
219 namespace {
AST_MATCHER(CXXMethodDecl,isOutOfLine)220 AST_MATCHER(CXXMethodDecl, isOutOfLine) { return Node.isOutOfLine(); }
221 } // namespace
222 
registerMatchers(MatchFinder * Finder)223 void UseEqualsDefaultCheck::registerMatchers(MatchFinder *Finder) {
224   // Skip unions/union-like classes since their constructors behave differently
225   // when defaulted vs. empty.
226   auto IsUnionLikeClass = recordDecl(
227       anyOf(isUnion(),
228             has(fieldDecl(isImplicit(), hasType(cxxRecordDecl(isUnion()))))));
229 
230   const LangOptions &LangOpts = getLangOpts();
231   auto IsPublicOrOutOfLineUntilCPP20 =
232       LangOpts.CPlusPlus20
233           ? cxxConstructorDecl()
234           : cxxConstructorDecl(anyOf(isOutOfLine(), isPublic()));
235 
236   // Destructor.
237   Finder->addMatcher(
238       cxxDestructorDecl(isDefinition(), unless(ofClass(IsUnionLikeClass)))
239           .bind(SpecialFunction),
240       this);
241   // Constructor.
242   Finder->addMatcher(
243       cxxConstructorDecl(
244           isDefinition(), unless(ofClass(IsUnionLikeClass)),
245           unless(hasParent(functionTemplateDecl())),
246           anyOf(
247               // Default constructor.
248               allOf(parameterCountIs(0),
249                     unless(hasAnyConstructorInitializer(isWritten())),
250                     unless(isVariadic()), IsPublicOrOutOfLineUntilCPP20),
251               // Copy constructor.
252               allOf(isCopyConstructor(),
253                     // Discard constructors that can be used as a copy
254                     // constructor because all the other arguments have
255                     // default values.
256                     parameterCountIs(1))))
257           .bind(SpecialFunction),
258       this);
259   // Copy-assignment operator.
260   Finder->addMatcher(
261       cxxMethodDecl(isDefinition(), isCopyAssignmentOperator(),
262                     unless(ofClass(IsUnionLikeClass)),
263                     unless(hasParent(functionTemplateDecl())),
264                     // isCopyAssignmentOperator() allows the parameter to be
265                     // passed by value, and in this case it cannot be
266                     // defaulted.
267                     hasParameter(0, hasType(lValueReferenceType())),
268                     // isCopyAssignmentOperator() allows non lvalue reference
269                     // return types, and in this case it cannot be defaulted.
270                     returns(qualType(hasCanonicalType(
271                         allOf(lValueReferenceType(pointee(type())),
272                               unless(matchers::isReferenceToConst()))))))
273           .bind(SpecialFunction),
274       this);
275 }
276 
check(const MatchFinder::MatchResult & Result)277 void UseEqualsDefaultCheck::check(const MatchFinder::MatchResult &Result) {
278   // Both CXXConstructorDecl and CXXDestructorDecl inherit from CXXMethodDecl.
279   const auto *SpecialFunctionDecl =
280       Result.Nodes.getNodeAs<CXXMethodDecl>(SpecialFunction);
281 
282   if (IgnoreMacros && SpecialFunctionDecl->getLocation().isMacroID())
283     return;
284 
285   // Discard explicitly deleted/defaulted special member functions and those
286   // that are not user-provided (automatically generated).
287   if (SpecialFunctionDecl->isDeleted() ||
288       SpecialFunctionDecl->isExplicitlyDefaulted() ||
289       SpecialFunctionDecl->isLateTemplateParsed() ||
290       SpecialFunctionDecl->isTemplateInstantiation() ||
291       !SpecialFunctionDecl->isUserProvided() || !SpecialFunctionDecl->hasBody())
292     return;
293 
294   const auto *Body = dyn_cast<CompoundStmt>(SpecialFunctionDecl->getBody());
295   if (!Body)
296     return;
297 
298   // If there is code inside the body, don't warn.
299   if (!SpecialFunctionDecl->isCopyAssignmentOperator() && !Body->body_empty())
300     return;
301 
302   // If body contain any preprocesor derictives, don't warn.
303   if (IgnoreMacros && utils::lexer::rangeContainsExpansionsOrDirectives(
304                           Body->getSourceRange(), *Result.SourceManager,
305                           Result.Context->getLangOpts()))
306     return;
307 
308   // If there are comments inside the body, don't do the change.
309   bool ApplyFix = SpecialFunctionDecl->isCopyAssignmentOperator() ||
310                   bodyEmpty(Result.Context, Body);
311 
312   std::vector<FixItHint> RemoveInitializers;
313   unsigned MemberType = 0;
314   if (const auto *Ctor = dyn_cast<CXXConstructorDecl>(SpecialFunctionDecl)) {
315     if (Ctor->getNumParams() == 0) {
316       MemberType = 0;
317     } else {
318       if (!isCopyConstructorAndCanBeDefaulted(Result.Context, Ctor))
319         return;
320       MemberType = 1;
321       // If there are constructor initializers, they must be removed.
322       for (const auto *Init : Ctor->inits()) {
323         RemoveInitializers.emplace_back(
324             FixItHint::CreateRemoval(Init->getSourceRange()));
325       }
326     }
327   } else if (isa<CXXDestructorDecl>(SpecialFunctionDecl)) {
328     MemberType = 2;
329   } else {
330     if (!isCopyAssignmentAndCanBeDefaulted(Result.Context, SpecialFunctionDecl))
331       return;
332     MemberType = 3;
333   }
334 
335   // The location of the body is more useful inside a macro as spelling and
336   // expansion locations are reported.
337   SourceLocation Location = SpecialFunctionDecl->getLocation();
338   if (Location.isMacroID())
339     Location = Body->getBeginLoc();
340 
341   auto Diag = diag(
342       Location,
343       "use '= default' to define a trivial %select{default constructor|copy "
344       "constructor|destructor|copy-assignment operator}0");
345   Diag << MemberType;
346 
347   if (ApplyFix) {
348     SourceLocation UnifiedEnd = utils::lexer::getUnifiedEndLoc(
349         *Body, Result.Context->getSourceManager(),
350         Result.Context->getLangOpts());
351     // Skipping comments, check for a semicolon after Body->getSourceRange()
352     std::optional<Token> Token = utils::lexer::findNextTokenSkippingComments(
353         UnifiedEnd, Result.Context->getSourceManager(),
354         Result.Context->getLangOpts());
355     StringRef Replacement =
356         Token && Token->is(tok::semi) ? "= default" : "= default;";
357     Diag << FixItHint::CreateReplacement(Body->getSourceRange(), Replacement)
358          << RemoveInitializers;
359   }
360 }
361 
362 } // namespace clang::tidy::modernize
363