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