| //===--- 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 |