clang-tools 24.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;
18using namespace clang::tidy::matchers;
19
20namespace clang::tidy::modernize {
21
22static constexpr char SpecialFunction[] = "SpecialFunction";
23
24/// Finds all the named non-static fields of \p Record.
25static llvm::SmallPtrSet<const FieldDecl *, 0>
26getAllNamedFields(const CXXRecordDecl *Record) {
27 llvm::SmallPtrSet<const FieldDecl *, 0> 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.
39static llvm::SmallPtrSet<const Type *, 0>
40getAllDirectBases(const CXXRecordDecl *Record) {
41 llvm::SmallPtrSet<const Type *, 0> Result;
42 for (const 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.
53static ast_matchers::internal::Matcher<Expr>
54accessToFieldInVar(const FieldDecl *Field, 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.
62static 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 const auto BasesToInit = getAllDirectBases(Record);
73 const 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.
126static 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 const auto BasesToInit = getAllDirectBases(Record);
133 const 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 const auto LHS = memberExpr(hasObjectExpression(cxxThisExpr()),
188 member(fieldDecl(equalsNode(Field))));
189 const 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.
203static bool bodyEmpty(const ASTContext *Context, const CompoundStmt *Body) {
204 bool Invalid = false;
205 const StringRef Text = Lexer::getSourceText(
206 CharSourceRange::getCharRange(Body->getLBracLoc().getLocWithOffset(1),
207 Body->getRBracLoc()),
208 Context->getSourceManager(), Context->getLangOpts(), &Invalid);
209 return !Invalid && Text.ltrim(" \t\r\n").empty();
210}
211
213 ClangTidyContext *Context)
214 : ClangTidyCheck(Name, Context),
215 IgnoreMacros(Options.get("IgnoreMacros", true)) {}
216
218 Options.store(Opts, "IgnoreMacros", IgnoreMacros);
219}
220
222 // Skip unions/union-like classes since their constructors behave differently
223 // when defaulted vs. empty.
224 const auto IsUnionLikeClass = recordDecl(
225 anyOf(isUnion(),
226 has(fieldDecl(isImplicit(), hasType(cxxRecordDecl(isUnion()))))));
227
228 const LangOptions &LangOpts = getLangOpts();
229 auto IsPublicOrOutOfLineUntilCPP20 =
230 LangOpts.CPlusPlus20
231 ? cxxConstructorDecl()
232 : cxxConstructorDecl(anyOf(isOutOfLine(), isPublic()));
233
234 // Destructor.
235 Finder->addMatcher(
236 cxxDestructorDecl(isDefinition(), unless(ofClass(IsUnionLikeClass)))
237 .bind(SpecialFunction),
238 this);
239 // Constructor.
240 Finder->addMatcher(
241 cxxConstructorDecl(
242 isDefinition(), unless(ofClass(IsUnionLikeClass)),
243 unless(hasParent(functionTemplateDecl())),
244 anyOf(
245 // Default constructor.
246 allOf(parameterCountIs(0),
247 unless(hasAnyConstructorInitializer(
248 allOf(isWritten(),
249 unless(withInitializer(cxxConstructExpr(
250 argumentCountIs(0),
251 unless(requiresZeroInitialization()))))))),
252 unless(isVariadic()), 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(isDefinition(), isCopyAssignmentOperator(),
264 unless(ofClass(IsUnionLikeClass)),
265 unless(hasParent(functionTemplateDecl())),
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
279void 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 body contain any preprocesor derictives, don't warn.
306 Body->getSourceRange(), *Result.SourceManager,
307 Result.Context->getLangOpts()))
308 return;
309
310 // If there are comments inside the body, don't do the change.
311 const bool ApplyFix = SpecialFunctionDecl->isCopyAssignmentOperator() ||
312 bodyEmpty(Result.Context, Body);
313
314 std::vector<FixItHint> RemoveInitializers;
315 unsigned MemberType = 0;
316 if (const auto *Ctor = dyn_cast<CXXConstructorDecl>(SpecialFunctionDecl)) {
317 if (Ctor->getNumParams() == 0) {
318 MemberType = 0;
319 } else {
320 if (!isCopyConstructorAndCanBeDefaulted(Result.Context, Ctor))
321 return;
322 MemberType = 1;
323 }
324 // If there are constructor initializers, they must be removed.
325 for (const auto *Init : Ctor->inits()) {
326 if (!Init->isWritten())
327 continue;
328 RemoveInitializers.emplace_back(
329 FixItHint::CreateRemoval(Init->getSourceRange()));
330 }
331 } else if (isa<CXXDestructorDecl>(SpecialFunctionDecl)) {
332 MemberType = 2;
333 } else {
334 if (!isCopyAssignmentAndCanBeDefaulted(Result.Context, SpecialFunctionDecl))
335 return;
336 MemberType = 3;
337 }
338
339 // The location of the body is more useful inside a macro as spelling and
340 // expansion locations are reported.
341 SourceLocation Location = SpecialFunctionDecl->getLocation();
342 if (Location.isMacroID())
343 Location = Body->getBeginLoc();
344
345 const auto Diag = diag(
346 Location,
347 "use '= default' to define a trivial %select{default constructor|copy "
348 "constructor|destructor|copy-assignment operator}0");
349 Diag << MemberType;
350
351 if (ApplyFix) {
352 const SourceLocation UnifiedEnd = utils::lexer::getUnifiedEndLoc(
353 *Body, Result.Context->getSourceManager(),
354 Result.Context->getLangOpts());
355 // Skipping comments, check for a semicolon after Body->getSourceRange()
356 std::optional<Token> Token = utils::lexer::findNextTokenSkippingComments(
357 UnifiedEnd, Result.Context->getSourceManager(),
358 Result.Context->getLangOpts());
359 const StringRef Replacement =
360 Token && Token->is(tok::semi) ? "= default" : "= default;";
361 Diag << FixItHint::CreateReplacement(Body->getSourceRange(), Replacement)
362 << RemoveInitializers;
363 }
364}
365
366} // 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 ast_matchers::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 bool bodyEmpty(const ASTContext *Context, const CompoundStmt *Body)
Returns false if the body has any non-whitespace character.
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:106
llvm::StringMap< ClangTidyValue > OptionMap