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