clang-tools 10.0.0

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
9 #include "UseEqualsDefaultCheck.h"
10 #include "clang/AST/ASTContext.h"
11 #include "clang/ASTMatchers/ASTMatchFinder.h"
12 #include "clang/Lex/Lexer.h"
13 #include "../utils/LexerUtils.h"
14
15 using namespace clang::ast_matchers;
16
17 namespace clang {
18 namespace tidy {
19 namespace 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 *>
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.
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.
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.
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 cxxConstructorDecl(forEachConstructorInitializer(cxxCtorInitializer(
79 isBaseInitializer(),
80 withInitializer(cxxConstructExpr(
81 hasType(equalsNode(Base)),
82 hasDeclaration(cxxConstructorDecl(isCopyConstructor())),
83 argumentCountIs(1),
84 hasArgument(
85 0, declRefExpr(to(varDecl(equalsNode(Param)))))))))),
86 *Ctor, *Context)
87 .empty())
88 return false;
89 }
90
91 // Ensure that all the members are copied.
92 for (const auto *Field : FieldsToInit) {
93 auto AccessToFieldInParam = accessToFieldInVar(Field, Param);
94 // The initialization is a CXXConstructExpr for class types.
95 if (match(
96 cxxConstructorDecl(forEachConstructorInitializer(cxxCtorInitializer(
97 isMemberInitializer(), forField(equalsNode(Field)),
98 withInitializer(anyOf(
99 AccessToFieldInParam,
100 initListExpr(has(AccessToFieldInParam)),
101 cxxConstructExpr(
102 hasDeclaration(cxxConstructorDecl(isCopyConstructor())),
103 argumentCountIs(1),
104 hasArgument(0, AccessToFieldInParam))))))),
105 *Ctor, *Context)
106 .empty())
107 return false;
108 }
109
110 // Ensure that we don't do anything else, like initializing an indirect base.
111 return Ctor->getNumCtorInitializers() ==
112 BasesToInit.size() + FieldsToInit.size();
113 }
114
115 /// Checks that the given method is an overloading of the assignment
116 /// operator, has copy signature, returns a reference to "*this" and copies
117 /// all its members and subobjects.
118 static bool isCopyAssignmentAndCanBeDefaulted(ASTContext *Context,
119 const CXXMethodDecl *Operator) {
120 const auto *Record = Operator->getParent();
121 const auto *Param = Operator->getParamDecl(0);
122
123 // Base classes and members that have to be copied.
124 auto BasesToInit = getAllDirectBases(Record);
125 auto FieldsToInit = getAllNamedFields(Record);
126
127 const auto *Compound = cast<CompoundStmt>(Operator->getBody());
128
129 // The assignment operator definition has to end with the following return
130 // statement:
131 // return *this;
132 if (Compound->body_empty() ||
133 match(returnStmt(has(ignoringParenImpCasts(unaryOperator(
134 hasOperatorName("*"), hasUnaryOperand(cxxThisExpr()))))),
135 *Compound->body_back(), *Context)
136 .empty())
137 return false;
138
139 // Ensure that all the bases are copied.
140 for (const auto *Base : BasesToInit) {
141 // Assignment operator of a base class:
142 // Base::operator=(Other);
143 //
144 // Clang translates this into:
145 // ((Base*)this)->operator=((Base)Other);
146 //
147 // So we are looking for a member call that fulfills:
148 if (match(
149 compoundStmt(has(ignoringParenImpCasts(cxxMemberCallExpr(
150 // - The object is an implicit cast of 'this' to a pointer to
151 // a base class.
152 onImplicitObjectArgument(
153 implicitCastExpr(hasImplicitDestinationType(
154 pointsTo(type(equalsNode(Base)))),
155 hasSourceExpression(cxxThisExpr()))),
156 // - The called method is the operator=.
157 callee(cxxMethodDecl(isCopyAssignmentOperator())),
158 // - The argument is (an implicit cast to a Base of) the
159 // argument taken by "Operator".
160 argumentCountIs(1),
161 hasArgument(0, declRefExpr(to(varDecl(equalsNode(Param))))))))),
162 *Compound, *Context)
163 .empty())
164 return false;
165 }
166
167 // Ensure that all the members are copied.
168 for (const auto *Field : FieldsToInit) {
169 // The assignment of data members:
170 // Field = Other.Field;
171 // Is a BinaryOperator in non-class types, and a CXXOperatorCallExpr
172 // otherwise.
173 auto LHS = memberExpr(hasObjectExpression(cxxThisExpr()),
174 member(fieldDecl(equalsNode(Field))));
175 auto RHS = accessToFieldInVar(Field, Param);
176 if (match(
177 compoundStmt(has(ignoringParenImpCasts(stmt(anyOf(
178 binaryOperator(hasOperatorName("="), hasLHS(LHS), hasRHS(RHS)),
179 cxxOperatorCallExpr(hasOverloadedOperatorName("="),
180 argumentCountIs(2), hasArgument(0, LHS),
181 hasArgument(1, RHS))))))),
182 *Compound, *Context)
183 .empty())
184 return false;
185 }
186
187 // Ensure that we don't do anything else.
188 return Compound->size() == BasesToInit.size() + FieldsToInit.size() + 1;
189 }
190
191 /// Returns false if the body has any non-whitespace character.
192 static bool bodyEmpty(const ASTContext *Context, const CompoundStmt *Body) {
193 bool Invalid = false;
194 StringRef Text = Lexer::getSourceText(
195 CharSourceRange::getCharRange(Body->getLBracLoc().getLocWithOffset(1),
196 Body->getRBracLoc()),
197 Context->getSourceManager(), Context->getLangOpts(), &Invalid);
198 return !Invalid && std::strspn(Text.data(), " \t\r\n") == Text.size();
199 }
200
201 UseEqualsDefaultCheck::UseEqualsDefaultCheck(StringRef Name,
202 ClangTidyContext *Context)
203 : ClangTidyCheck(Name, Context),
204 IgnoreMacros(Options.getLocalOrGlobal("IgnoreMacros", true) != 0) {}
205
206 void UseEqualsDefaultCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) {
207 Options.store(Opts, "IgnoreMacros", IgnoreMacros);
208 }
209
210 void UseEqualsDefaultCheck::registerMatchers(MatchFinder *Finder) {
211 if (!getLangOpts().CPlusPlus)
212 return;
213
214 // Destructor.
215 Finder->addMatcher(cxxDestructorDecl(isDefinition()).bind(SpecialFunction),
216 this);
217 Finder->addMatcher(
218 cxxConstructorDecl(
219 isDefinition(),
220 anyOf(
221 // Default constructor.
222 allOf(unless(hasAnyConstructorInitializer(isWritten())),
223 parameterCountIs(0)),
224 // Copy constructor.
225 allOf(isCopyConstructor(),
226 // Discard constructors that can be used as a copy
227 // constructor because all the other arguments have
228 // default values.
229 parameterCountIs(1))))
230 .bind(SpecialFunction),
231 this);
232 // Copy-assignment operator.
233 Finder->addMatcher(
234 cxxMethodDecl(isDefinition(), isCopyAssignmentOperator(),
235 // isCopyAssignmentOperator() allows the parameter to be
236 // passed by value, and in this case it cannot be
237 // defaulted.
238 hasParameter(0, hasType(lValueReferenceType())))
239 .bind(SpecialFunction),
240 this);
241 }
242
243 void UseEqualsDefaultCheck::check(const MatchFinder::MatchResult &Result) {
244 std::string SpecialFunctionName;
245
246 // Both CXXConstructorDecl and CXXDestructorDecl inherit from CXXMethodDecl.
247 const auto *SpecialFunctionDecl =
248 Result.Nodes.getNodeAs<CXXMethodDecl>(SpecialFunction);
249
250 if (IgnoreMacros && SpecialFunctionDecl->getLocation().isMacroID())
251 return;
252
253 // Discard explicitly deleted/defaulted special member functions and those
254 // that are not user-provided (automatically generated).
255 if (SpecialFunctionDecl->isDeleted() ||
256 SpecialFunctionDecl->isExplicitlyDefaulted() ||
257 SpecialFunctionDecl->isLateTemplateParsed() ||
258 SpecialFunctionDecl->isTemplateInstantiation() ||
259 !SpecialFunctionDecl->isUserProvided() || !SpecialFunctionDecl->hasBody())
260 return;
261
262 const auto *Body = dyn_cast<CompoundStmt>(SpecialFunctionDecl->getBody());
263 if (!Body)
264 return;
265
266 // If there is code inside the body, don't warn.
267 if (!SpecialFunctionDecl->isCopyAssignmentOperator() && !Body->body_empty())
268 return;
269
270 // If there are comments inside the body, don't do the change.
271 bool ApplyFix = SpecialFunctionDecl->isCopyAssignmentOperator() ||
272 bodyEmpty(Result.Context, Body);
273
274 std::vector<FixItHint> RemoveInitializers;
275
276 if (const auto *Ctor = dyn_cast<CXXConstructorDecl>(SpecialFunctionDecl)) {
277 if (Ctor->getNumParams() == 0) {
278 SpecialFunctionName = "default constructor";
279 } else {
280 if (!isCopyConstructorAndCanBeDefaulted(Result.Context, Ctor))
281 return;
282 SpecialFunctionName = "copy constructor";
283 // If there are constructor initializers, they must be removed.
284 for (const auto *Init : Ctor->inits()) {
285 RemoveInitializers.emplace_back(
286 FixItHint::CreateRemoval(Init->getSourceRange()));
287 }
288 }
289 } else if (isa<CXXDestructorDecl>(SpecialFunctionDecl)) {
290 SpecialFunctionName = "destructor";
291 } else {
292 if (!isCopyAssignmentAndCanBeDefaulted(Result.Context, SpecialFunctionDecl))
293 return;
294 SpecialFunctionName = "copy-assignment operator";
295 }
296
297 // The location of the body is more useful inside a macro as spelling and
298 // expansion locations are reported.
299 SourceLocation Location = SpecialFunctionDecl->getLocation();
300 if (Location.isMacroID())
301 Location = Body->getBeginLoc();
302
303 auto Diag = diag(Location, "use '= default' to define a trivial " +
304 SpecialFunctionName);
305
306 if (ApplyFix) {
307 // Skipping comments, check for a semicolon after Body->getSourceRange()
308 Optional<Token> Token = utils::lexer::findNextTokenSkippingComments(
309 Body->getSourceRange().getEnd().getLocWithOffset(1),
310 Result.Context->getSourceManager(), Result.Context->getLangOpts());
311 StringRef Replacement =
312 Token && Token->is(tok::semi) ? "= default" : "= default;";
313 Diag << FixItHint::CreateReplacement(Body->getSourceRange(), Replacement)
314 << RemoveInitializers;
315 }
316 }
317
318 } // namespace modernize
319 } // namespace tidy
320 } // namespace clang
clang::tidy::modernize::isCopyAssignmentAndCanBeDefaulted
static bool isCopyAssignmentAndCanBeDefaulted(ASTContext *Context, const CXXMethodDecl *Operator)
Checks that the given method is an overloading of the assignment operator, has copy signature...
Definition: UseEqualsDefaultCheck.cpp:118
clang::ast_matchers
Definition: AbseilMatcher.h:14
clang::tidy::modernize::SpecialFunction
static const char SpecialFunction[]
Definition: UseEqualsDefaultCheck.cpp:21
Text
std::string Text
Definition: HTMLGenerator.cpp:80
clang::doc::Record
llvm::SmallVector< uint64_t, 1024 > Record
Definition: BitcodeReader.cpp:18
clang::tidy::ClangTidyCheck
Base class for all clang-tidy checks.
Definition: ClangTidyCheck.h:47
clang::tidy::ClangTidyCheck::getLangOpts
const LangOptions & getLangOpts() const
Returns the language options from the context.
Definition: ClangTidyCheck.h:188
clang::tidy::ClangTidyCheck::Options
OptionsView Options
Definition: ClangTidyCheck.h:184
clang::tidy::modernize::bodyEmpty
static bool bodyEmpty(const ASTContext *Context, const CompoundStmt *Body)
Returns false if the body has any non-whitespace character.
Definition: UseEqualsDefaultCheck.cpp:192
clang::tidy::modernize::UseEqualsDefaultCheck::registerMatchers
void registerMatchers(ast_matchers::MatchFinder *Finder) override
Override this to register AST matchers with Finder.
Definition: UseEqualsDefaultCheck.cpp:210
clang::clangd::match
std::vector< std::string > match(const SymbolIndex &I, const FuzzyFindRequest &Req, bool *Incomplete)
Definition: TestIndex.cpp:94
clang::tidy::ClangTidyCheck::OptionsView::store
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.
Definition: ClangTidyCheck.cpp:58
Name
static constexpr llvm::StringLiteral Name
Definition: UppercaseLiteralSuffixCheck.cpp:27
clang::tidy::ClangTidyOptions::OptionMap
std::map< std::string, std::string > OptionMap
Definition: ClangTidyOptions.h:97
clang::tidy::utils::lexer::findNextTokenSkippingComments
Optional< Token > findNextTokenSkippingComments(SourceLocation Start, const SourceManager &SM, const LangOptions &LangOpts)
Definition: LexerUtils.cpp:78
UseEqualsDefaultCheck.h
clang
===– Representation.cpp - ClangDoc Representation --------—*- C++ -*-===//
Definition: ApplyReplacements.h:27
clang::tidy::modernize::accessToFieldInVar
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 ...
Definition: UseEqualsDefaultCheck.cpp:51
clang::tidy::ClangTidyContext
Every ClangTidyCheck reports errors through a DiagnosticsEngine provided by this context.
Definition: ClangTidyDiagnosticConsumer.h:77
clang::tidy::modernize::UseEqualsDefaultCheck::storeOptions
void storeOptions(ClangTidyOptions::OptionMap &Opts) override
Should store all options supported by this check with their current values or default values for opti...
Definition: UseEqualsDefaultCheck.cpp:206
clang::tidy::modernize::isCopyConstructorAndCanBeDefaulted
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...
Definition: UseEqualsDefaultCheck.cpp:60
clang::tidy::modernize::getAllDirectBases
static std::set< const Type * > getAllDirectBases(const CXXRecordDecl *Record)
Returns the names of the direct bases of Record, both virtual and non-virtual.
Definition: UseEqualsDefaultCheck.cpp:38
clang::tidy::modernize::getAllNamedFields
static std::set< const FieldDecl * > getAllNamedFields(const CXXRecordDecl *Record)
Finds all the named non-static fields of Record.
Definition: UseEqualsDefaultCheck.cpp:25
Base
std::unique_ptr< GlobalCompilationDatabase > Base
Definition: GlobalCompilationDatabaseTests.cpp:83
clang::tidy::ClangTidyCheck::diag
DiagnosticBuilder diag(SourceLocation Loc, StringRef Description, DiagnosticIDs::Level Level=DiagnosticIDs::Warning)
Add a diagnostic with the check&#39;s name.
Definition: ClangTidyCheck.cpp:21
clang::tidy::modernize::UseEqualsDefaultCheck::check
void check(const ast_matchers::MatchFinder::MatchResult &Result) override
ClangTidyChecks that register ASTMatchers should do the actual work in here.
Definition: UseEqualsDefaultCheck.cpp:243
Location
Definition: Modularize.cpp:383