| //===--- 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 { |
| |
| namespace { |
| /// \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; |
| } |
| } // namespace |
| |
| 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<ExactlyOneUsageVisitor> { |
| friend class RecursiveASTVisitor<ExactlyOneUsageVisitor>; |
| |
| 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<CXXConstructorDecl *>(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<ParmVarDecl>(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<const ParmVarDecl *, 2> |
| collectParamDecls(const CXXConstructorDecl *Ctor, |
| const ParmVarDecl *ParamDecl) { |
| SmallVector<const ParmVarDecl *, 2> 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.getLocalOrGlobal("IncludeStyle", "llvm"))), |
| ValuesOnly(Options.get("ValuesOnly", 0) != 0) {} |
| |
| void PassByValueCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) { |
| Options.store(Opts, "IncludeStyle", |
| utils::IncludeSorter::toString(IncludeStyle)); |
| Options.store(Opts, "ValuesOnly", ValuesOnly); |
| } |
| |
| 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( |
| unless(isBaseInitializer()), |
| // Clang builds a CXXConstructExpr only when 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. |
| ValuesOnly ? nonConstValueType() |
| : 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<CXXConstructorDecl>("Ctor"); |
| const auto *ParamDecl = Result.Nodes.getNodeAs<ParmVarDecl>("Param"); |
| const auto *Initializer = |
| Result.Nodes.getNodeAs<CXXCtorInitializer>("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 performance-move-const-arg |
| if (ParamDecl->getType().getNonReferenceType().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<ReferenceTypeLoc>(); |
| |
| // 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 |