clang-tools 22.0.0git
UseEqualsDefaultCheck.cpp
Go to the documentation of this file.
1//===----------------------------------------------------------------------===//
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 "../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
17using namespace clang::ast_matchers;
18
19namespace clang::tidy::modernize {
20
21static constexpr char SpecialFunction[] = "SpecialFunction";
22
23/// Finds all the named non-static fields of \p Record.
24static llvm::SmallPtrSet<const FieldDecl *, 0>
25getAllNamedFields(const CXXRecordDecl *Record) {
26 llvm::SmallPtrSet<const FieldDecl *, 0> 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.
38static llvm::SmallPtrSet<const Type *, 0>
39getAllDirectBases(const CXXRecordDecl *Record) {
40 llvm::SmallPtrSet<const Type *, 0> 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.
52static 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.
61static 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.
125static 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.
202static bool bodyEmpty(const ASTContext *Context, const CompoundStmt *Body) {
203 bool Invalid = false;
204 const StringRef Text = Lexer::getSourceText(
205 CharSourceRange::getCharRange(Body->getLBracLoc().getLocWithOffset(1),
206 Body->getRBracLoc()),
207 Context->getSourceManager(), Context->getLangOpts(), &Invalid);
208 return !Invalid && Text.ltrim(" \t\r\n").empty();
209}
210
212 ClangTidyContext *Context)
213 : ClangTidyCheck(Name, Context),
214 IgnoreMacros(Options.get("IgnoreMacros", true)) {}
215
217 Options.store(Opts, "IgnoreMacros", IgnoreMacros);
218}
219
220namespace {
221AST_MATCHER(CXXMethodDecl, isOutOfLine) { return Node.isOutOfLine(); }
222} // namespace
223
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(isDefinition(), unless(ofClass(IsUnionLikeClass)))
240 .bind(SpecialFunction),
241 this);
242 // Constructor.
243 Finder->addMatcher(
244 cxxConstructorDecl(
245 isDefinition(), unless(ofClass(IsUnionLikeClass)),
246 unless(hasParent(functionTemplateDecl())),
247 anyOf(
248 // Default constructor.
249 allOf(parameterCountIs(0),
250 unless(hasAnyConstructorInitializer(isWritten())),
251 unless(isVariadic()), IsPublicOrOutOfLineUntilCPP20),
252 // Copy constructor.
253 allOf(isCopyConstructor(),
254 // Discard constructors that can be used as a copy
255 // constructor because all the other arguments have
256 // default values.
257 parameterCountIs(1))))
258 .bind(SpecialFunction),
259 this);
260 // Copy-assignment operator.
261 Finder->addMatcher(
262 cxxMethodDecl(isDefinition(), isCopyAssignmentOperator(),
263 unless(ofClass(IsUnionLikeClass)),
264 unless(hasParent(functionTemplateDecl())),
265 // isCopyAssignmentOperator() allows the parameter to be
266 // passed by value, and in this case it cannot be
267 // defaulted.
268 hasParameter(0, hasType(lValueReferenceType())),
269 // isCopyAssignmentOperator() allows non lvalue reference
270 // return types, and in this case it cannot be defaulted.
271 returns(qualType(hasCanonicalType(
272 allOf(lValueReferenceType(pointee(type())),
273 unless(matchers::isReferenceToConst()))))))
274 .bind(SpecialFunction),
275 this);
276}
277
278void UseEqualsDefaultCheck::check(const MatchFinder::MatchResult &Result) {
279 // Both CXXConstructorDecl and CXXDestructorDecl inherit from CXXMethodDecl.
280 const auto *SpecialFunctionDecl =
281 Result.Nodes.getNodeAs<CXXMethodDecl>(SpecialFunction);
282
283 if (IgnoreMacros && SpecialFunctionDecl->getLocation().isMacroID())
284 return;
285
286 // Discard explicitly deleted/defaulted special member functions and those
287 // that are not user-provided (automatically generated).
288 if (SpecialFunctionDecl->isDeleted() ||
289 SpecialFunctionDecl->isExplicitlyDefaulted() ||
290 SpecialFunctionDecl->isLateTemplateParsed() ||
291 SpecialFunctionDecl->isTemplateInstantiation() ||
292 !SpecialFunctionDecl->isUserProvided() || !SpecialFunctionDecl->hasBody())
293 return;
294
295 const auto *Body = dyn_cast<CompoundStmt>(SpecialFunctionDecl->getBody());
296 if (!Body)
297 return;
298
299 // If there is code inside the body, don't warn.
300 if (!SpecialFunctionDecl->isCopyAssignmentOperator() && !Body->body_empty())
301 return;
302
303 // If body contain any preprocesor derictives, don't warn.
305 Body->getSourceRange(), *Result.SourceManager,
306 Result.Context->getLangOpts()))
307 return;
308
309 // If there are comments inside the body, don't do the change.
310 const bool ApplyFix = SpecialFunctionDecl->isCopyAssignmentOperator() ||
311 bodyEmpty(Result.Context, Body);
312
313 std::vector<FixItHint> RemoveInitializers;
314 unsigned MemberType = 0;
315 if (const auto *Ctor = dyn_cast<CXXConstructorDecl>(SpecialFunctionDecl)) {
316 if (Ctor->getNumParams() == 0) {
317 MemberType = 0;
318 } else {
319 if (!isCopyConstructorAndCanBeDefaulted(Result.Context, Ctor))
320 return;
321 MemberType = 1;
322 // If there are constructor initializers, they must be removed.
323 for (const auto *Init : Ctor->inits()) {
324 RemoveInitializers.emplace_back(
325 FixItHint::CreateRemoval(Init->getSourceRange()));
326 }
327 }
328 } else if (isa<CXXDestructorDecl>(SpecialFunctionDecl)) {
329 MemberType = 2;
330 } else {
331 if (!isCopyAssignmentAndCanBeDefaulted(Result.Context, SpecialFunctionDecl))
332 return;
333 MemberType = 3;
334 }
335
336 // The location of the body is more useful inside a macro as spelling and
337 // expansion locations are reported.
338 SourceLocation Location = SpecialFunctionDecl->getLocation();
339 if (Location.isMacroID())
340 Location = Body->getBeginLoc();
341
342 auto Diag = diag(
343 Location,
344 "use '= default' to define a trivial %select{default constructor|copy "
345 "constructor|destructor|copy-assignment operator}0");
346 Diag << MemberType;
347
348 if (ApplyFix) {
349 const SourceLocation UnifiedEnd = utils::lexer::getUnifiedEndLoc(
350 *Body, Result.Context->getSourceManager(),
351 Result.Context->getLangOpts());
352 // Skipping comments, check for a semicolon after Body->getSourceRange()
353 std::optional<Token> Token = utils::lexer::findNextTokenSkippingComments(
354 UnifiedEnd, Result.Context->getSourceManager(),
355 Result.Context->getLangOpts());
356 const StringRef Replacement =
357 Token && Token->is(tok::semi) ? "= default" : "= default;";
358 Diag << FixItHint::CreateReplacement(Body->getSourceRange(), Replacement)
359 << RemoveInitializers;
360 }
361}
362
363} // namespace clang::tidy::modernize
Every ClangTidyCheck reports errors through a DiagnosticsEngine provided by this context.
UseEqualsDefaultCheck(StringRef Name, ClangTidyContext *Context)
void check(const ast_matchers::MatchFinder::MatchResult &Result) override
void registerMatchers(ast_matchers::MatchFinder *Finder) override
void storeOptions(ClangTidyOptions::OptionMap &Opts) override
static bool empty(SourceRange Range)
static bool bodyEmpty(const ASTContext *Context, const CompoundStmt *Body)
Returns false if the body has any non-whitespace character.
static 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 ...
static constexpr char SpecialFunction[]
static llvm::SmallPtrSet< const Type *, 0 > getAllDirectBases(const CXXRecordDecl *Record)
Returns the names of the direct bases of Record, both virtual and non-virtual.
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...
static bool isCopyAssignmentAndCanBeDefaulted(ASTContext *Context, const CXXMethodDecl *Operator)
Checks that the given method is an overloading of the assignment operator, has copy signature,...
static llvm::SmallPtrSet< const FieldDecl *, 0 > getAllNamedFields(const CXXRecordDecl *Record)
Finds all the named non-static fields of Record.
SourceLocation getUnifiedEndLoc(const Stmt &S, const SourceManager &SM, const LangOptions &LangOpts)
Stmt->getEndLoc does not always behave the same way depending on Token type.
bool rangeContainsExpansionsOrDirectives(SourceRange Range, const SourceManager &SM, const LangOptions &LangOpts)
Re-lex the provide Range and return false if either a macro spans multiple tokens,...
std::optional< Token > findNextTokenSkippingComments(SourceLocation Start, const SourceManager &SM, const LangOptions &LangOpts)
Definition LexerUtils.h:101
llvm::StringMap< ClangTidyValue > OptionMap