//===--- 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 "clang/AST/ASTContext.h" #include "clang/ASTMatchers/ASTMatchFinder.h" #include "clang/Lex/Lexer.h" #include "../utils/LexerUtils.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 getAllNamedFields(const CXXRecordDecl *Record) { std::set 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 getAllDirectBases(const CXXRecordDecl *Record) { std::set 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 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(ast_type_traits::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( ast_type_traits::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(Operator->getBody()); // The assignment operator definition has to end with the following return // statement: // return *this; if (Compound->body_empty() || match(traverse( ast_type_traits::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(ast_type_traits::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(ast_type_traits::TK_AsIs, 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; } /// 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) { std::string SpecialFunctionName; // Both CXXConstructorDecl and CXXDestructorDecl inherit from CXXMethodDecl. const auto *SpecialFunctionDecl = Result.Nodes.getNodeAs(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(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 RemoveInitializers; if (const auto *Ctor = dyn_cast(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(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->getBeginLoc(); auto Diag = diag(Location, "use '= default' to define a trivial " + SpecialFunctionName); if (ApplyFix) { // Skipping comments, check for a semicolon after Body->getSourceRange() Optional 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