| //===--- UseEqualsDefaultCheck.cpp - clang-tidy----------------------------===// | 
 | // | 
 | // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. | 
 | // See https://llvm.org/LICENSE.txt for license information. | 
 | // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception | 
 | // | 
 | //===----------------------------------------------------------------------===// | 
 |  | 
 | #include "UseEqualsDefaultCheck.h" | 
 | #include "../utils/LexerUtils.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"; | 
 |  | 
 | /// 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; | 
 | } | 
 |  | 
 | /// 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; | 
 | } | 
 |  | 
 | /// 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))))); | 
 | } | 
 |  | 
 | /// 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( | 
 |             traverse(TK_AsIs, | 
 |                      cxxConstructorDecl( | 
 |                          forEachConstructorInitializer(cxxCtorInitializer( | 
 |                              isBaseInitializer(), | 
 |                              withInitializer(cxxConstructExpr( | 
 |                                  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(traverse( | 
 |                   TK_AsIs, | 
 |                   cxxConstructorDecl( | 
 |                       forEachConstructorInitializer(cxxCtorInitializer( | 
 |                           isMemberInitializer(), forField(equalsNode(Field)), | 
 |                           withInitializer(anyOf( | 
 |                               AccessToFieldInParam, | 
 |                               initListExpr(has(AccessToFieldInParam)), | 
 |                               cxxConstructExpr( | 
 |                                   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(); | 
 | } | 
 |  | 
 | /// 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(traverse( | 
 |                 TK_AsIs, | 
 |                 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(traverse(TK_AsIs, | 
 |                        compoundStmt(has(ignoringParenImpCasts(cxxMemberCallExpr( | 
 |                            // - 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(traverse(TK_AsIs, | 
 |                        compoundStmt(has(ignoringParenImpCasts(binaryOperation( | 
 |                            hasOperatorName("="), hasLHS(LHS), hasRHS(RHS)))))), | 
 |               *Compound, *Context) | 
 |             .empty()) | 
 |       return false; | 
 |   } | 
 |  | 
 |   // Ensure that we don't do anything else. | 
 |   return Compound->size() == BasesToInit.size() + FieldsToInit.size() + 1; | 
 | } | 
 |  | 
 | /// 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)) {} | 
 |  | 
 | void UseEqualsDefaultCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) { | 
 |   Options.store(Opts, "IgnoreMacros", IgnoreMacros); | 
 | } | 
 |  | 
 | void UseEqualsDefaultCheck::registerMatchers(MatchFinder *Finder) { | 
 |   // 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) { | 
 |   // 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; | 
 |   unsigned MemberType; | 
 |   if (const auto *Ctor = dyn_cast<CXXConstructorDecl>(SpecialFunctionDecl)) { | 
 |     if (Ctor->getNumParams() == 0) { | 
 |       MemberType = 0; | 
 |     } else { | 
 |       if (!isCopyConstructorAndCanBeDefaulted(Result.Context, Ctor)) | 
 |         return; | 
 |       MemberType = 1; | 
 |       // 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)) { | 
 |     MemberType = 2; | 
 |   } else { | 
 |     if (!isCopyAssignmentAndCanBeDefaulted(Result.Context, SpecialFunctionDecl)) | 
 |       return; | 
 |     MemberType = 3; | 
 |   } | 
 |  | 
 |   // 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->getBeginLoc(); | 
 |  | 
 |   auto Diag = diag( | 
 |       Location, | 
 |       "use '= default' to define a trivial %select{default constructor|copy " | 
 |       "constructor|destructor|copy-assignment operator}0"); | 
 |   Diag << MemberType; | 
 |  | 
 |   if (ApplyFix) { | 
 |     // Skipping comments, check for a semicolon after Body->getSourceRange() | 
 |     Optional<Token> Token = utils::lexer::findNextTokenSkippingComments( | 
 |         Body->getSourceRange().getEnd().getLocWithOffset(1), | 
 |         Result.Context->getSourceManager(), Result.Context->getLangOpts()); | 
 |     StringRef Replacement = | 
 |         Token && Token->is(tok::semi) ? "= default" : "= default;"; | 
 |     Diag << FixItHint::CreateReplacement(Body->getSourceRange(), Replacement) | 
 |          << RemoveInitializers; | 
 |   } | 
 | } | 
 |  | 
 | } // namespace modernize | 
 | } // namespace tidy | 
 | } // namespace clang |