//===-- PassByValueActions.cpp --------------------------------------------===// // // The LLVM Compiler Infrastructure // // This file is distributed under the University of Illinois Open Source // License. See LICENSE.TXT for details. // //===----------------------------------------------------------------------===// /// /// \file /// \brief This file contains the definition of the ASTMatcher callback for the /// PassByValue transform. /// //===----------------------------------------------------------------------===// #include "PassByValueActions.h" #include "Core/IncludeDirectives.h" #include "Core/Transform.h" #include "PassByValueMatchers.h" #include "clang/AST/RecursiveASTVisitor.h" #include "clang/Basic/SourceManager.h" #include "clang/Lex/Lexer.h" using namespace clang; using namespace clang::tooling; using namespace clang::ast_matchers; namespace { /// \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 = llvm::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; }; } // end anonymous namespace /// \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) { ExactlyOneUsageVisitor Visitor(ParamDecl); return Visitor.hasExactlyOneUsageIn(Ctor); } /// \brief Find all references to \p ParamDecl across all of the /// redeclarations of \p Ctor. static void collectParamDecls(const CXXConstructorDecl *Ctor, const ParmVarDecl *ParamDecl, llvm::SmallVectorImpl &Results) { unsigned ParamIdx = ParamDecl->getFunctionScopeIndex(); for (CXXConstructorDecl::redecl_iterator I = Ctor->redecls_begin(), E = Ctor->redecls_end(); I != E; ++I) Results.push_back((*I)->getParamDecl(ParamIdx)); } void ConstructorParamReplacer::run(const MatchFinder::MatchResult &Result) { assert(IncludeManager && "Include directives manager not set."); SourceManager &SM = *Result.SourceManager; const CXXConstructorDecl *Ctor = Result.Nodes.getNodeAs(PassByValueCtorId); const ParmVarDecl *ParamDecl = Result.Nodes.getNodeAs(PassByValueParamId); const CXXCtorInitializer *Initializer = Result.Nodes.getNodeAs(PassByValueInitializerId); assert(Ctor && ParamDecl && Initializer && "Bad Callback, missing node."); // Check this now to avoid unnecessary work. The param locations are checked // later. if (!Owner.isFileModifiable(SM, Initializer->getSourceLocation())) return; // The parameter will be in an unspecified state after the move, so check if // the parameter is used for anything else other than the copy. If so do not // apply any changes. if (!paramReferredExactlyOnce(Ctor, ParamDecl)) return; llvm::SmallVector AllParamDecls; collectParamDecls(Ctor, ParamDecl, AllParamDecls); // Generate all replacements for the params. llvm::SmallVector ParamReplaces; for (unsigned I = 0, E = AllParamDecls.size(); I != E; ++I) { TypeLoc ParamTL = AllParamDecls[I]->getTypeSourceInfo()->getTypeLoc(); ReferenceTypeLoc RefTL = ParamTL.getAs(); SourceRange Range(AllParamDecls[I]->getLocStart(), ParamTL.getLocEnd()); CharSourceRange CharRange = Lexer::makeFileCharRange( CharSourceRange::getTokenRange(Range), SM, LangOptions()); // do not generate a replacement when the parameter is already a value if (RefTL.isNull()) continue; // transform non-value parameters (e.g: const-ref) to values TypeLoc ValueTypeLoc = RefTL.getPointeeLoc(); llvm::SmallString<32> ValueStr = Lexer::getSourceText( CharSourceRange::getTokenRange(ValueTypeLoc.getSourceRange()), SM, LangOptions()); // If it's impossible to change one of the parameter (e.g: comes from an // unmodifiable header) quit the callback now, do not generate any changes. if (CharRange.isInvalid() || ValueStr.empty() || !Owner.isFileModifiable(SM, CharRange.getBegin())) return; // 'const Foo ¶m' -> 'Foo param' // ~~~~~~~~~~~ ~~~^ ValueStr += ' '; ParamReplaces.push_back(Replacement(SM, CharRange, ValueStr)); } // Reject the changes if the the risk level is not acceptable. if (!Owner.isAcceptableRiskLevel(RL_Reasonable)) { RejectedChanges++; return; } // if needed, include in the file that uses std::move() const FileEntry *STDMoveFile = SM.getFileEntryForID(SM.getFileID(Initializer->getLParenLoc())); const tooling::Replacement &IncludeReplace = IncludeManager->addAngledInclude(STDMoveFile, "utility"); if (IncludeReplace.isApplicable()) { Owner.addReplacementForCurrentTU(IncludeReplace); AcceptedChanges++; } // const-ref params becomes values (const Foo & -> Foo) for (const Replacement *I = ParamReplaces.begin(), *E = ParamReplaces.end(); I != E; ++I) { Owner.addReplacementForCurrentTU(*I); } AcceptedChanges += ParamReplaces.size(); // move the value in the init-list Owner.addReplacementForCurrentTU(Replacement( SM, Initializer->getLParenLoc().getLocWithOffset(1), 0, "std::move(")); Owner.addReplacementForCurrentTU( Replacement(SM, Initializer->getRParenLoc(), 0, ")")); AcceptedChanges += 2; }