|  | //===--- UseEqualsDefaultCheck.cpp - clang-tidy----------------------------===// | 
|  | // | 
|  | //                     The LLVM Compiler Infrastructure | 
|  | // | 
|  | // This file is distributed under the University of Illinois Open Source | 
|  | // License. See LICENSE.TXT for details. | 
|  | // | 
|  | //===----------------------------------------------------------------------===// | 
|  |  | 
|  | #include "UseEqualsDefaultCheck.h" | 
|  | #include "clang/AST/ASTContext.h" | 
|  | #include "clang/ASTMatchers/ASTMatchFinder.h" | 
|  | #include "clang/Lex/Lexer.h" | 
|  |  | 
|  | using namespace clang::ast_matchers; | 
|  |  | 
|  | namespace clang { | 
|  | namespace tidy { | 
|  | namespace modernize { | 
|  |  | 
|  | static const char SpecialFunction[] = "SpecialFunction"; | 
|  |  | 
|  | /// \brief Finds all the named non-static fields of \p Record. | 
|  | static std::set<const FieldDecl *> | 
|  | getAllNamedFields(const CXXRecordDecl *Record) { | 
|  | std::set<const FieldDecl *> Result; | 
|  | for (const auto *Field : Record->fields()) { | 
|  | // Static data members are not in this range. | 
|  | if (Field->isUnnamedBitfield()) | 
|  | continue; | 
|  | Result.insert(Field); | 
|  | } | 
|  | return Result; | 
|  | } | 
|  |  | 
|  | /// \brief Returns the names of the direct bases of \p Record, both virtual and | 
|  | /// non-virtual. | 
|  | static std::set<const Type *> getAllDirectBases(const CXXRecordDecl *Record) { | 
|  | std::set<const Type *> Result; | 
|  | for (auto Base : Record->bases()) { | 
|  | // CXXBaseSpecifier. | 
|  | const auto *BaseType = Base.getTypeSourceInfo()->getType().getTypePtr(); | 
|  | Result.insert(BaseType); | 
|  | } | 
|  | return Result; | 
|  | } | 
|  |  | 
|  | /// \brief Returns a matcher that matches member expressions where the base is | 
|  | /// the variable declared as \p Var and the accessed member is the one declared | 
|  | /// as \p Field. | 
|  | internal::Matcher<Expr> accessToFieldInVar(const FieldDecl *Field, | 
|  | const ValueDecl *Var) { | 
|  | return ignoringImpCasts( | 
|  | memberExpr(hasObjectExpression(declRefExpr(to(varDecl(equalsNode(Var))))), | 
|  | member(fieldDecl(equalsNode(Field))))); | 
|  | } | 
|  |  | 
|  | /// \brief Check that the given constructor has copy signature and that it | 
|  | /// copy-initializes all its bases and members. | 
|  | static bool isCopyConstructorAndCanBeDefaulted(ASTContext *Context, | 
|  | const CXXConstructorDecl *Ctor) { | 
|  | // An explicitly-defaulted constructor cannot have default arguments. | 
|  | if (Ctor->getMinRequiredArguments() != 1) | 
|  | return false; | 
|  |  | 
|  | const auto *Record = Ctor->getParent(); | 
|  | const auto *Param = Ctor->getParamDecl(0); | 
|  |  | 
|  | // Base classes and members that have to be copied. | 
|  | auto BasesToInit = getAllDirectBases(Record); | 
|  | auto FieldsToInit = getAllNamedFields(Record); | 
|  |  | 
|  | // Ensure that all the bases are copied. | 
|  | for (const auto *Base : BasesToInit) { | 
|  | // The initialization of a base class should be a call to a copy | 
|  | // constructor of the base. | 
|  | if (match( | 
|  | cxxConstructorDecl(forEachConstructorInitializer(cxxCtorInitializer( | 
|  | isBaseInitializer(), | 
|  | withInitializer(cxxConstructExpr(allOf( | 
|  | hasType(equalsNode(Base)), | 
|  | hasDeclaration(cxxConstructorDecl(isCopyConstructor())), | 
|  | argumentCountIs(1), | 
|  | hasArgument( | 
|  | 0, declRefExpr(to(varDecl(equalsNode(Param))))))))))), | 
|  | *Ctor, *Context) | 
|  | .empty()) | 
|  | return false; | 
|  | } | 
|  |  | 
|  | // Ensure that all the members are copied. | 
|  | for (const auto *Field : FieldsToInit) { | 
|  | auto AccessToFieldInParam = accessToFieldInVar(Field, Param); | 
|  | // The initialization is a CXXConstructExpr for class types. | 
|  | if (match( | 
|  | cxxConstructorDecl(forEachConstructorInitializer(cxxCtorInitializer( | 
|  | isMemberInitializer(), forField(equalsNode(Field)), | 
|  | withInitializer(anyOf( | 
|  | AccessToFieldInParam, | 
|  | initListExpr(has(AccessToFieldInParam)), | 
|  | cxxConstructExpr(allOf( | 
|  | hasDeclaration(cxxConstructorDecl(isCopyConstructor())), | 
|  | argumentCountIs(1), | 
|  | hasArgument(0, AccessToFieldInParam)))))))), | 
|  | *Ctor, *Context) | 
|  | .empty()) | 
|  | return false; | 
|  | } | 
|  |  | 
|  | // Ensure that we don't do anything else, like initializing an indirect base. | 
|  | return Ctor->getNumCtorInitializers() == | 
|  | BasesToInit.size() + FieldsToInit.size(); | 
|  | } | 
|  |  | 
|  | /// \brief Checks that the given method is an overloading of the assignment | 
|  | /// operator, has copy signature, returns a reference to "*this" and copies | 
|  | /// all its members and subobjects. | 
|  | static bool isCopyAssignmentAndCanBeDefaulted(ASTContext *Context, | 
|  | const CXXMethodDecl *Operator) { | 
|  | const auto *Record = Operator->getParent(); | 
|  | const auto *Param = Operator->getParamDecl(0); | 
|  |  | 
|  | // Base classes and members that have to be copied. | 
|  | auto BasesToInit = getAllDirectBases(Record); | 
|  | auto FieldsToInit = getAllNamedFields(Record); | 
|  |  | 
|  | const auto *Compound = cast<CompoundStmt>(Operator->getBody()); | 
|  |  | 
|  | // The assignment operator definition has to end with the following return | 
|  | // statement: | 
|  | //   return *this; | 
|  | if (Compound->body_empty() || | 
|  | match(returnStmt(has(ignoringParenImpCasts(unaryOperator( | 
|  | hasOperatorName("*"), hasUnaryOperand(cxxThisExpr()))))), | 
|  | *Compound->body_back(), *Context) | 
|  | .empty()) | 
|  | return false; | 
|  |  | 
|  | // Ensure that all the bases are copied. | 
|  | for (const auto *Base : BasesToInit) { | 
|  | // Assignment operator of a base class: | 
|  | //   Base::operator=(Other); | 
|  | // | 
|  | // Clang translates this into: | 
|  | //   ((Base*)this)->operator=((Base)Other); | 
|  | // | 
|  | // So we are looking for a member call that fulfills: | 
|  | if (match(compoundStmt(has(ignoringParenImpCasts(cxxMemberCallExpr(allOf( | 
|  | // - The object is an implicit cast of 'this' to a pointer to | 
|  | //   a base class. | 
|  | onImplicitObjectArgument( | 
|  | implicitCastExpr(hasImplicitDestinationType( | 
|  | pointsTo(type(equalsNode(Base)))), | 
|  | hasSourceExpression(cxxThisExpr()))), | 
|  | // - The called method is the operator=. | 
|  | callee(cxxMethodDecl(isCopyAssignmentOperator())), | 
|  | // - The argument is (an implicit cast to a Base of) the | 
|  | // argument taken by "Operator". | 
|  | argumentCountIs(1), | 
|  | hasArgument(0, | 
|  | declRefExpr(to(varDecl(equalsNode(Param)))))))))), | 
|  | *Compound, *Context) | 
|  | .empty()) | 
|  | return false; | 
|  | } | 
|  |  | 
|  | // Ensure that all the members are copied. | 
|  | for (const auto *Field : FieldsToInit) { | 
|  | // The assignment of data members: | 
|  | //   Field = Other.Field; | 
|  | // Is a BinaryOperator in non-class types, and a CXXOperatorCallExpr | 
|  | // otherwise. | 
|  | auto LHS = memberExpr(hasObjectExpression(cxxThisExpr()), | 
|  | member(fieldDecl(equalsNode(Field)))); | 
|  | auto RHS = accessToFieldInVar(Field, Param); | 
|  | if (match( | 
|  | compoundStmt(has(ignoringParenImpCasts(stmt(anyOf( | 
|  | binaryOperator(hasOperatorName("="), hasLHS(LHS), hasRHS(RHS)), | 
|  | cxxOperatorCallExpr(hasOverloadedOperatorName("="), | 
|  | argumentCountIs(2), hasArgument(0, LHS), | 
|  | hasArgument(1, RHS))))))), | 
|  | *Compound, *Context) | 
|  | .empty()) | 
|  | return false; | 
|  | } | 
|  |  | 
|  | // Ensure that we don't do anything else. | 
|  | return Compound->size() == BasesToInit.size() + FieldsToInit.size() + 1; | 
|  | } | 
|  |  | 
|  | /// \brief Returns false if the body has any non-whitespace character. | 
|  | static bool bodyEmpty(const ASTContext *Context, const CompoundStmt *Body) { | 
|  | bool Invalid = false; | 
|  | StringRef Text = Lexer::getSourceText( | 
|  | CharSourceRange::getCharRange(Body->getLBracLoc().getLocWithOffset(1), | 
|  | Body->getRBracLoc()), | 
|  | Context->getSourceManager(), Context->getLangOpts(), &Invalid); | 
|  | return !Invalid && std::strspn(Text.data(), " \t\r\n") == Text.size(); | 
|  | } | 
|  |  | 
|  | UseEqualsDefaultCheck::UseEqualsDefaultCheck(StringRef Name, | 
|  | ClangTidyContext *Context) | 
|  | : ClangTidyCheck(Name, Context), | 
|  | IgnoreMacros(Options.getLocalOrGlobal("IgnoreMacros", true) != 0) {} | 
|  |  | 
|  | void UseEqualsDefaultCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) { | 
|  | Options.store(Opts, "IgnoreMacros", IgnoreMacros); | 
|  | } | 
|  |  | 
|  | void UseEqualsDefaultCheck::registerMatchers(MatchFinder *Finder) { | 
|  | if (!getLangOpts().CPlusPlus) | 
|  | return; | 
|  |  | 
|  | // Destructor. | 
|  | Finder->addMatcher(cxxDestructorDecl(isDefinition()).bind(SpecialFunction), | 
|  | this); | 
|  | Finder->addMatcher( | 
|  | cxxConstructorDecl( | 
|  | isDefinition(), | 
|  | anyOf( | 
|  | // Default constructor. | 
|  | allOf(unless(hasAnyConstructorInitializer(isWritten())), | 
|  | parameterCountIs(0)), | 
|  | // Copy constructor. | 
|  | allOf(isCopyConstructor(), | 
|  | // Discard constructors that can be used as a copy | 
|  | // constructor because all the other arguments have | 
|  | // default values. | 
|  | parameterCountIs(1)))) | 
|  | .bind(SpecialFunction), | 
|  | this); | 
|  | // Copy-assignment operator. | 
|  | Finder->addMatcher( | 
|  | cxxMethodDecl(isDefinition(), isCopyAssignmentOperator(), | 
|  | // isCopyAssignmentOperator() allows the parameter to be | 
|  | // passed by value, and in this case it cannot be | 
|  | // defaulted. | 
|  | hasParameter(0, hasType(lValueReferenceType()))) | 
|  | .bind(SpecialFunction), | 
|  | this); | 
|  | } | 
|  |  | 
|  | void UseEqualsDefaultCheck::check(const MatchFinder::MatchResult &Result) { | 
|  | std::string SpecialFunctionName; | 
|  |  | 
|  | // Both CXXConstructorDecl and CXXDestructorDecl inherit from CXXMethodDecl. | 
|  | const auto *SpecialFunctionDecl = | 
|  | Result.Nodes.getNodeAs<CXXMethodDecl>(SpecialFunction); | 
|  |  | 
|  | if (IgnoreMacros && SpecialFunctionDecl->getLocation().isMacroID()) | 
|  | return; | 
|  |  | 
|  | // Discard explicitly deleted/defaulted special member functions and those | 
|  | // that are not user-provided (automatically generated). | 
|  | if (SpecialFunctionDecl->isDeleted() || | 
|  | SpecialFunctionDecl->isExplicitlyDefaulted() || | 
|  | SpecialFunctionDecl->isLateTemplateParsed() || | 
|  | SpecialFunctionDecl->isTemplateInstantiation() || | 
|  | !SpecialFunctionDecl->isUserProvided() || !SpecialFunctionDecl->hasBody()) | 
|  | return; | 
|  |  | 
|  | const auto *Body = dyn_cast<CompoundStmt>(SpecialFunctionDecl->getBody()); | 
|  | if (!Body) | 
|  | return; | 
|  |  | 
|  | // If there is code inside the body, don't warn. | 
|  | if (!SpecialFunctionDecl->isCopyAssignmentOperator() && !Body->body_empty()) | 
|  | return; | 
|  |  | 
|  | // If there are comments inside the body, don't do the change. | 
|  | bool ApplyFix = SpecialFunctionDecl->isCopyAssignmentOperator() || | 
|  | bodyEmpty(Result.Context, Body); | 
|  |  | 
|  | std::vector<FixItHint> RemoveInitializers; | 
|  |  | 
|  | if (const auto *Ctor = dyn_cast<CXXConstructorDecl>(SpecialFunctionDecl)) { | 
|  | if (Ctor->getNumParams() == 0) { | 
|  | SpecialFunctionName = "default constructor"; | 
|  | } else { | 
|  | if (!isCopyConstructorAndCanBeDefaulted(Result.Context, Ctor)) | 
|  | return; | 
|  | SpecialFunctionName = "copy constructor"; | 
|  | // If there are constructor initializers, they must be removed. | 
|  | for (const auto *Init : Ctor->inits()) { | 
|  | RemoveInitializers.emplace_back( | 
|  | FixItHint::CreateRemoval(Init->getSourceRange())); | 
|  | } | 
|  | } | 
|  | } else if (isa<CXXDestructorDecl>(SpecialFunctionDecl)) { | 
|  | SpecialFunctionName = "destructor"; | 
|  | } else { | 
|  | if (!isCopyAssignmentAndCanBeDefaulted(Result.Context, SpecialFunctionDecl)) | 
|  | return; | 
|  | SpecialFunctionName = "copy-assignment operator"; | 
|  | } | 
|  |  | 
|  | // The location of the body is more useful inside a macro as spelling and | 
|  | // expansion locations are reported. | 
|  | SourceLocation Location = SpecialFunctionDecl->getLocation(); | 
|  | if (Location.isMacroID()) | 
|  | Location = Body->getLocStart(); | 
|  |  | 
|  | auto Diag = diag(Location, "use '= default' to define a trivial " + | 
|  | SpecialFunctionName); | 
|  |  | 
|  | if (ApplyFix) | 
|  | Diag << FixItHint::CreateReplacement(Body->getSourceRange(), "= default;") | 
|  | << RemoveInitializers; | 
|  | } | 
|  |  | 
|  | } // namespace modernize | 
|  | } // namespace tidy | 
|  | } // namespace clang |