363 lines · cpp
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-exception6//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#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 and37/// 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 is49/// the variable declared as \p Var and the accessed member is the one declared50/// as \p Field.51static 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 it59/// 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 copy76 // 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 assignment122/// operator, has copy signature, returns a reference to "*this" and copies123/// 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 return136 // 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 a160 // pointer to161 // 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 CXXOperatorCallExpr184 // 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 const StringRef Text = Lexer::getSourceText(204 CharSourceRange::getCharRange(Body->getLBracLoc().getLocWithOffset(1),205 Body->getRBracLoc()),206 Context->getSourceManager(), Context->getLangOpts(), &Invalid);207 return !Invalid && Text.ltrim(" \t\r\n").empty();208}209 210UseEqualsDefaultCheck::UseEqualsDefaultCheck(StringRef Name,211 ClangTidyContext *Context)212 : ClangTidyCheck(Name, Context),213 IgnoreMacros(Options.get("IgnoreMacros", true)) {}214 215void UseEqualsDefaultCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) {216 Options.store(Opts, "IgnoreMacros", IgnoreMacros);217}218 219namespace {220AST_MATCHER(CXXMethodDecl, isOutOfLine) { return Node.isOutOfLine(); }221} // namespace222 223void UseEqualsDefaultCheck::registerMatchers(MatchFinder *Finder) {224 // Skip unions/union-like classes since their constructors behave differently225 // 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.CPlusPlus20233 ? 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 copy254 // constructor because all the other arguments have255 // 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 be265 // passed by value, and in this case it cannot be266 // defaulted.267 hasParameter(0, hasType(lValueReferenceType())),268 // isCopyAssignmentOperator() allows non lvalue reference269 // 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 those286 // 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.303 if (IgnoreMacros && utils::lexer::rangeContainsExpansionsOrDirectives(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 const 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 and336 // 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 const 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 const 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::modernize363