//===--- PassByValueCheck.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 "PassByValueCheck.h" #include "clang/AST/ASTContext.h" #include "clang/AST/RecursiveASTVisitor.h" #include "clang/ASTMatchers/ASTMatchFinder.h" #include "clang/ASTMatchers/ASTMatchers.h" #include "clang/Frontend/CompilerInstance.h" #include "clang/Lex/Lexer.h" #include "clang/Lex/Preprocessor.h" using namespace clang::ast_matchers; using namespace llvm; namespace clang { namespace tidy { namespace modernize { /// \brief Matches move-constructible classes. /// /// Given /// \code /// // POD types are trivially move constructible. /// struct Foo { int a; }; /// /// struct Bar { /// Bar(Bar &&) = deleted; /// int a; /// }; /// \endcode /// recordDecl(isMoveConstructible()) /// matches "Foo". AST_MATCHER(CXXRecordDecl, isMoveConstructible) { for (const CXXConstructorDecl *Ctor : Node.ctors()) { if (Ctor->isMoveConstructor() && !Ctor->isDeleted()) return true; } return false; } static TypeMatcher constRefType() { return lValueReferenceType(pointee(isConstQualified())); } static TypeMatcher nonConstValueType() { return qualType(unless(anyOf(referenceType(), isConstQualified()))); } /// \brief Whether or not \p ParamDecl is used exactly one time in \p Ctor. /// /// Checks both in the init-list and the body of the constructor. static bool paramReferredExactlyOnce(const CXXConstructorDecl *Ctor, const ParmVarDecl *ParamDecl) { /// \brief \c clang::RecursiveASTVisitor that checks that the given /// \c ParmVarDecl is used exactly one time. /// /// \see ExactlyOneUsageVisitor::hasExactlyOneUsageIn() class ExactlyOneUsageVisitor : public RecursiveASTVisitor { friend class RecursiveASTVisitor; public: ExactlyOneUsageVisitor(const ParmVarDecl *ParamDecl) : ParamDecl(ParamDecl) {} /// \brief Whether or not the parameter variable is referred only once in /// the /// given constructor. bool hasExactlyOneUsageIn(const CXXConstructorDecl *Ctor) { Count = 0; TraverseDecl(const_cast(Ctor)); return Count == 1; } private: /// \brief Counts the number of references to a variable. /// /// Stops the AST traversal if more than one usage is found. bool VisitDeclRefExpr(DeclRefExpr *D) { if (const ParmVarDecl *To = dyn_cast(D->getDecl())) { if (To == ParamDecl) { ++Count; if (Count > 1) { // No need to look further, used more than once. return false; } } } return true; } const ParmVarDecl *ParamDecl; unsigned Count; }; return ExactlyOneUsageVisitor(ParamDecl).hasExactlyOneUsageIn(Ctor); } /// \brief Find all references to \p ParamDecl across all of the /// redeclarations of \p Ctor. static SmallVector collectParamDecls(const CXXConstructorDecl *Ctor, const ParmVarDecl *ParamDecl) { SmallVector Results; unsigned ParamIdx = ParamDecl->getFunctionScopeIndex(); for (const FunctionDecl *Redecl : Ctor->redecls()) Results.push_back(Redecl->getParamDecl(ParamIdx)); return Results; } PassByValueCheck::PassByValueCheck(StringRef Name, ClangTidyContext *Context) : ClangTidyCheck(Name, Context), IncludeStyle(utils::IncludeSorter::parseIncludeStyle( Options.get("IncludeStyle", "llvm"))) {} void PassByValueCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) { Options.store(Opts, "IncludeStyle", utils::IncludeSorter::toString(IncludeStyle)); } void PassByValueCheck::registerMatchers(MatchFinder *Finder) { // Only register the matchers for C++; the functionality currently does not // provide any benefit to other languages, despite being benign. if (!getLangOpts().CPlusPlus) return; Finder->addMatcher( cxxConstructorDecl( forEachConstructorInitializer( cxxCtorInitializer( // Clang builds a CXXConstructExpr only whin it knows which // constructor will be called. In dependent contexts a // ParenListExpr is generated instead of a CXXConstructExpr, // filtering out templates automatically for us. withInitializer(cxxConstructExpr( has(ignoringParenImpCasts(declRefExpr(to( parmVarDecl( hasType(qualType( // Match only const-ref or a non-const value // parameters. Rvalues and const-values // shouldn't be modified. anyOf(constRefType(), nonConstValueType())))) .bind("Param"))))), hasDeclaration(cxxConstructorDecl( isCopyConstructor(), unless(isDeleted()), hasDeclContext( cxxRecordDecl(isMoveConstructible()))))))) .bind("Initializer"))) .bind("Ctor"), this); } void PassByValueCheck::registerPPCallbacks(CompilerInstance &Compiler) { // Only register the preprocessor callbacks for C++; the functionality // currently does not provide any benefit to other languages, despite being // benign. if (getLangOpts().CPlusPlus) { Inserter.reset(new utils::IncludeInserter( Compiler.getSourceManager(), Compiler.getLangOpts(), IncludeStyle)); Compiler.getPreprocessor().addPPCallbacks(Inserter->CreatePPCallbacks()); } } void PassByValueCheck::check(const MatchFinder::MatchResult &Result) { const auto *Ctor = Result.Nodes.getNodeAs("Ctor"); const auto *ParamDecl = Result.Nodes.getNodeAs("Param"); const auto *Initializer = Result.Nodes.getNodeAs("Initializer"); SourceManager &SM = *Result.SourceManager; // If the parameter is used or anything other than the copy, do not apply // the changes. if (!paramReferredExactlyOnce(Ctor, ParamDecl)) return; // If the parameter is trivial to copy, don't move it. Moving a trivivally // copyable type will cause a problem with misc-move-const-arg if (ParamDecl->getType().isTriviallyCopyableType(*Result.Context)) return; auto Diag = diag(ParamDecl->getLocStart(), "pass by value and use std::move"); // Iterate over all declarations of the constructor. for (const ParmVarDecl *ParmDecl : collectParamDecls(Ctor, ParamDecl)) { auto ParamTL = ParmDecl->getTypeSourceInfo()->getTypeLoc(); auto RefTL = ParamTL.getAs(); // Do not replace if it is already a value, skip. if (RefTL.isNull()) continue; TypeLoc ValueTL = RefTL.getPointeeLoc(); auto TypeRange = CharSourceRange::getTokenRange(ParmDecl->getLocStart(), ParamTL.getLocEnd()); std::string ValueStr = Lexer::getSourceText(CharSourceRange::getTokenRange( ValueTL.getSourceRange()), SM, getLangOpts()) .str(); ValueStr += ' '; Diag << FixItHint::CreateReplacement(TypeRange, ValueStr); } // Use std::move in the initialization list. Diag << FixItHint::CreateInsertion(Initializer->getRParenLoc(), ")") << FixItHint::CreateInsertion( Initializer->getLParenLoc().getLocWithOffset(1), "std::move("); if (auto IncludeFixit = Inserter->CreateIncludeInsertion( Result.SourceManager->getFileID(Initializer->getSourceLocation()), "utility", /*IsAngled=*/true)) { Diag << *IncludeFixit; } } } // namespace modernize } // namespace tidy } // namespace clang