clang-tools 19.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 "../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 const char SpecialFunction[] = "SpecialFunction";
22
23/// Finds all the named non-static fields of \p Record.
24static std::set<const FieldDecl *>
25getAllNamedFields(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.
38static 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.
51internal::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.
60static 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.
124static 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.
201static 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
211 ClangTidyContext *Context)
212 : ClangTidyCheck(Name, Context),
213 IgnoreMacros(Options.getLocalOrGlobal("IgnoreMacros", true)) {}
214
216 Options.store(Opts, "IgnoreMacros", IgnoreMacros);
217}
218
219namespace {
220AST_MATCHER(CXXMethodDecl, isOutOfLine) { return Node.isOutOfLine(); }
221} // namespace
222
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
277void 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.
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
llvm::SmallString< 256U > Name
std::string Text
const FieldDecl * Field
::clang::DynTypedNode Node
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.
Base class for all clang-tidy checks.
DiagnosticBuilder diag(SourceLocation Loc, StringRef Description, DiagnosticIDs::Level Level=DiagnosticIDs::Warning)
Add a diagnostic with the check's name.
const LangOptions & getLangOpts() const
Returns the language options from the context.
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
ClangTidyChecks that register ASTMatchers should do the actual work in here.
void registerMatchers(ast_matchers::MatchFinder *Finder) override
Override this to register AST matchers with Finder.
void storeOptions(ClangTidyOptions::OptionMap &Opts) override
Should store all options supported by this check with their current values or default values for opti...
AST_MATCHER(Decl, declHasNoReturnAttr)
matches a Decl if it has a "no return" attribute of any kind
static bool empty(SourceRange Range)
static std::set< const Type * > getAllDirectBases(const CXXRecordDecl *Record)
Returns the names of the direct bases of Record, both virtual and non-virtual.
static bool bodyEmpty(const ASTContext *Context, const CompoundStmt *Body)
Returns false if the body has any non-whitespace character.
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 std::set< const FieldDecl * > getAllNamedFields(const CXXRecordDecl *Record)
Finds all the named non-static fields of Record.
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 const char SpecialFunction[]
SourceLocation getUnifiedEndLoc(const Stmt &S, const SourceManager &SM, const LangOptions &LangOpts)
Stmt->getEndLoc does not always behave the same way depending on Token type.
Definition: LexerUtils.cpp:236
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,...
Definition: LexerUtils.cpp:125
std::optional< Token > findNextTokenSkippingComments(SourceLocation Start, const SourceManager &SM, const LangOptions &LangOpts)
Definition: LexerUtils.cpp:111
llvm::StringMap< ClangTidyValue > OptionMap