| // Copyright 2016 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include <assert.h> |
| #include <stdlib.h> |
| #include <algorithm> |
| #include <memory> |
| #include <string> |
| |
| #include "clang/AST/ASTContext.h" |
| #include "clang/AST/ParentMap.h" |
| #include "clang/ASTMatchers/ASTMatchFinder.h" |
| #include "clang/ASTMatchers/ASTMatchers.h" |
| #include "clang/ASTMatchers/ASTMatchersMacros.h" |
| #include "clang/Analysis/CFG.h" |
| #include "clang/Basic/SourceManager.h" |
| #include "clang/Frontend/FrontendActions.h" |
| #include "clang/Lex/Lexer.h" |
| #include "clang/Tooling/CommonOptionsParser.h" |
| #include "clang/Tooling/Refactoring.h" |
| #include "clang/Tooling/Tooling.h" |
| #include "llvm/Support/CommandLine.h" |
| #include "llvm/Support/TargetSelect.h" |
| |
| using Replacements = std::vector<clang::tooling::Replacement>; |
| using clang::ASTContext; |
| using clang::CFG; |
| using clang::CFGBlock; |
| using clang::CFGLifetimeEnds; |
| using clang::CFGStmt; |
| using clang::CallExpr; |
| using clang::Decl; |
| using clang::DeclRefExpr; |
| using clang::FunctionDecl; |
| using clang::LambdaExpr; |
| using clang::Stmt; |
| using clang::UnaryOperator; |
| using clang::ast_type_traits::DynTypedNode; |
| using clang::tooling::CommonOptionsParser; |
| using namespace clang::ast_matchers; |
| |
| namespace { |
| |
| class Rewriter { |
| public: |
| virtual ~Rewriter() {} |
| }; |
| |
| // Removes unneeded base::Passed() on a parameter of base::BindOnce(). |
| // Example: |
| // // Before |
| // base::BindOnce(&Foo, base::Passed(&bar)); |
| // base::BindOnce(&Foo, base::Passed(std::move(baz))); |
| // base::BindOnce(&Foo, base::Passed(qux)); |
| // |
| // // After |
| // base::BindOnce(&Foo, std::move(bar)); |
| // base::BindOnce(&Foo, std::move(baz)); |
| // base::BindOnce(&Foo, std::move(*qux)); |
| class PassedToMoveRewriter : public MatchFinder::MatchCallback, |
| public Rewriter { |
| public: |
| explicit PassedToMoveRewriter(Replacements* replacements) |
| : replacements_(replacements) {} |
| |
| StatementMatcher GetMatcher() { |
| auto is_passed = namedDecl(hasName("::base::Passed")); |
| auto is_bind_once_call = callee(namedDecl(hasName("::base::BindOnce"))); |
| |
| // Matches base::Passed() call on a base::BindOnce() argument. |
| return callExpr(is_bind_once_call, |
| hasAnyArgument(ignoringImplicit( |
| callExpr(callee(is_passed)).bind("target")))); |
| } |
| |
| void run(const MatchFinder::MatchResult& result) override { |
| auto* target = result.Nodes.getNodeAs<CallExpr>("target"); |
| auto* callee = target->getCallee()->IgnoreImpCasts(); |
| |
| auto* callee_decl = clang::dyn_cast<DeclRefExpr>(callee)->getDecl(); |
| auto* passed_decl = clang::dyn_cast<FunctionDecl>(callee_decl); |
| auto* param_type = passed_decl->getParamDecl(0)->getType().getTypePtr(); |
| |
| if (param_type->isRValueReferenceType()) { |
| // base::Passed(xxx) -> xxx. |
| // The parameter type is already an rvalue reference. |
| // Example: |
| // std::unique_ptr<int> foo(); |
| // std::unique_ptr<int> bar; |
| // base::Passed(foo()); |
| // base::Passed(std::move(bar)); |
| // In these cases, we can just remove base::Passed. |
| auto left = clang::CharSourceRange::getTokenRange( |
| result.SourceManager->getSpellingLoc(target->getLocStart()), |
| result.SourceManager->getSpellingLoc(target->getArg(0)->getExprLoc()) |
| .getLocWithOffset(-1)); |
| auto r_paren = clang::CharSourceRange::getTokenRange( |
| result.SourceManager->getSpellingLoc(target->getRParenLoc()), |
| result.SourceManager->getSpellingLoc(target->getRParenLoc())); |
| replacements_->emplace_back(*result.SourceManager, left, " "); |
| replacements_->emplace_back(*result.SourceManager, r_paren, " "); |
| return; |
| } |
| |
| if (!param_type->isPointerType()) |
| return; |
| |
| auto* passed_arg = target->getArg(0)->IgnoreImpCasts(); |
| if (auto* unary = clang::dyn_cast<clang::UnaryOperator>(passed_arg)) { |
| if (unary->getOpcode() == clang::UO_AddrOf) { |
| // base::Passed(&xxx) -> std::move(xxx). |
| auto left = clang::CharSourceRange::getTokenRange( |
| result.SourceManager->getSpellingLoc(target->getLocStart()), |
| result.SourceManager->getSpellingLoc( |
| target->getArg(0)->getExprLoc())); |
| replacements_->emplace_back(*result.SourceManager, left, "std::move("); |
| return; |
| } |
| } |
| |
| // base::Passed(xxx) -> std::move(*xxx) |
| auto left = clang::CharSourceRange::getTokenRange( |
| result.SourceManager->getSpellingLoc(target->getLocStart()), |
| result.SourceManager->getSpellingLoc(target->getArg(0)->getExprLoc()) |
| .getLocWithOffset(-1)); |
| replacements_->emplace_back(*result.SourceManager, left, "std::move(*"); |
| } |
| |
| private: |
| Replacements* replacements_; |
| }; |
| |
| // Replace base::Bind() to base::BindOnce() where resulting base::Callback is |
| // implicitly converted into base::OnceCallback. |
| // Example: |
| // // Before |
| // base::PostTask(FROM_HERE, base::Bind(&Foo)); |
| // base::OnceCallback<void()> cb = base::Bind(&Foo); |
| // |
| // // After |
| // base::PostTask(FROM_HERE, base::BindOnce(&Foo)); |
| // base::OnceCallback<void()> cb = base::BindOnce(&Foo); |
| class BindOnceRewriter : public MatchFinder::MatchCallback, public Rewriter { |
| public: |
| explicit BindOnceRewriter(Replacements* replacements) |
| : replacements_(replacements) {} |
| |
| StatementMatcher GetMatcher() { |
| auto is_once_callback = hasType(hasCanonicalType(hasDeclaration( |
| classTemplateSpecializationDecl(hasName("::base::OnceCallback"))))); |
| auto is_repeating_callback = |
| hasType(hasCanonicalType(hasDeclaration(classTemplateSpecializationDecl( |
| hasName("::base::RepeatingCallback"))))); |
| |
| auto bind_call = |
| callExpr(callee(namedDecl(hasName("::base::Bind")))).bind("target"); |
| auto parameter_construction = |
| cxxConstructExpr(is_repeating_callback, argumentCountIs(1), |
| hasArgument(0, ignoringImplicit(bind_call))); |
| auto constructor_conversion = cxxConstructExpr( |
| is_once_callback, argumentCountIs(1), |
| hasArgument(0, ignoringImplicit(parameter_construction))); |
| return implicitCastExpr(is_once_callback, |
| hasSourceExpression(constructor_conversion)); |
| } |
| |
| void run(const MatchFinder::MatchResult& result) override { |
| auto* target = result.Nodes.getNodeAs<clang::CallExpr>("target"); |
| auto* callee = target->getCallee(); |
| auto range = clang::CharSourceRange::getTokenRange( |
| result.SourceManager->getSpellingLoc(callee->getLocEnd()), |
| result.SourceManager->getSpellingLoc(callee->getLocEnd())); |
| replacements_->emplace_back(*result.SourceManager, range, "BindOnce"); |
| } |
| |
| private: |
| Replacements* replacements_; |
| }; |
| |
| // Converts pass-by-const-ref base::Callback's to pass-by-value. |
| // Example: |
| // // Before |
| // using BarCallback = base::Callback<void(void*)>; |
| // void Foo(const base::Callback<void(int)>& cb); |
| // void Bar(const BarCallback& cb); |
| // |
| // // After |
| // using BarCallback = base::Callback<void(void*)>; |
| // void Foo(base::Callback<void(int)> cb); |
| // void Bar(BarCallback cb); |
| class PassByValueRewriter : public MatchFinder::MatchCallback, public Rewriter { |
| public: |
| explicit PassByValueRewriter(Replacements* replacements) |
| : replacements_(replacements) {} |
| |
| DeclarationMatcher GetMatcher() { |
| auto is_repeating_callback = |
| namedDecl(hasName("::base::RepeatingCallback")); |
| return parmVarDecl( |
| hasType(hasCanonicalType(references(is_repeating_callback)))) |
| .bind("target"); |
| } |
| |
| void run(const MatchFinder::MatchResult& result) override { |
| auto* target = result.Nodes.getNodeAs<clang::ParmVarDecl>("target"); |
| auto qual_type = target->getType(); |
| auto* ref_type = |
| clang::dyn_cast<clang::LValueReferenceType>(qual_type.getTypePtr()); |
| if (!ref_type || !ref_type->getPointeeType().isLocalConstQualified()) |
| return; |
| |
| // Remove the leading `const` and the following `&`. |
| auto type_loc = target->getTypeSourceInfo()->getTypeLoc(); |
| auto const_keyword = clang::CharSourceRange::getTokenRange( |
| result.SourceManager->getSpellingLoc(target->getLocStart()), |
| result.SourceManager->getSpellingLoc(target->getLocStart())); |
| auto lvalue_ref = clang::CharSourceRange::getTokenRange( |
| result.SourceManager->getSpellingLoc(type_loc.getLocEnd()), |
| result.SourceManager->getSpellingLoc(type_loc.getLocEnd())); |
| replacements_->emplace_back(*result.SourceManager, const_keyword, " "); |
| replacements_->emplace_back(*result.SourceManager, lvalue_ref, " "); |
| } |
| |
| private: |
| Replacements* replacements_; |
| }; |
| |
| // Adds std::move() to base::RepeatingCallback<> where it looks relevant. |
| // Example: |
| // // Before |
| // void Foo(base::Callback<void(int)> cb1) { |
| // base::Closure cb2 = base::Bind(cb1, 42); |
| // PostTask(FROM_HERE, cb2); |
| // } |
| // |
| // // After |
| // void Foo(base::Callback<void(int)> cb1) { |
| // base::Closure cb2 = base::Bind(std::move(cb1), 42); |
| // PostTask(FROM_HERE, std::move(cb2)); |
| // } |
| class AddStdMoveRewriter : public MatchFinder::MatchCallback, public Rewriter { |
| public: |
| explicit AddStdMoveRewriter(Replacements* replacements) |
| : replacements_(replacements) {} |
| |
| StatementMatcher GetMatcher() { |
| return declRefExpr( |
| hasType(hasCanonicalType(hasDeclaration( |
| namedDecl(hasName("::base::RepeatingCallback"))))), |
| anyOf(hasAncestor(cxxConstructorDecl().bind("enclosing_ctor")), |
| hasAncestor(functionDecl().bind("enclosing_func")), |
| hasAncestor(lambdaExpr().bind("enclosing_lambda")))) |
| .bind("target"); |
| } |
| |
| // Build Control Flow Graph (CFG) for |stmt| and populate class members with |
| // the content of the graph. Returns true if the analysis finished |
| // successfully. |
| bool ExtractCFGContentToMembers(Stmt* stmt, ASTContext* context) { |
| // Try to make a cache entry. The failure implies it's already in the cache. |
| auto inserted = cfg_cache_.emplace(stmt, nullptr); |
| if (!inserted.second) |
| return !!inserted.first->second; |
| |
| std::unique_ptr<CFG>& cfg = inserted.first->second; |
| CFG::BuildOptions opts; |
| opts.AddInitializers = true; |
| opts.AddLifetime = true; |
| opts.AddStaticInitBranches = true; |
| cfg = CFG::buildCFG(nullptr, stmt, context, opts); |
| |
| // CFG construction may fail. Report it to the caller. |
| if (!cfg) |
| return false; |
| if (!parent_map_) |
| parent_map_ = llvm::make_unique<clang::ParentMap>(stmt); |
| else |
| parent_map_->addStmt(stmt); |
| |
| // Populate |top_stmts_|, that contains Stmts that is evaluated in its own |
| // CFGElement. |
| for (auto* block : *cfg) { |
| for (auto& elem : *block) { |
| if (auto stmt = elem.getAs<CFGStmt>()) |
| top_stmts_.insert(stmt->getStmt()); |
| } |
| } |
| |
| // Populate |enclosing_block_|, that maps a Stmt to a CFGBlock that contains |
| // the Stmt. |
| std::function<void(const CFGBlock*, const Stmt*)> recursive_set_enclosing = |
| [&](const CFGBlock* block, const Stmt* stmt) { |
| enclosing_block_[stmt] = block; |
| for (auto* c : stmt->children()) { |
| if (!c) |
| continue; |
| if (top_stmts_.find(c) != top_stmts_.end()) |
| continue; |
| recursive_set_enclosing(block, c); |
| } |
| }; |
| for (auto* block : *cfg) { |
| for (auto& elem : *block) { |
| if (auto stmt = elem.getAs<CFGStmt>()) |
| recursive_set_enclosing(block, stmt->getStmt()); |
| } |
| } |
| |
| return true; |
| } |
| |
| const Stmt* EnclosingCxxStatement(const Stmt* stmt) { |
| while (true) { |
| const Stmt* parent = parent_map_->getParentIgnoreParenCasts(stmt); |
| assert(parent); |
| switch (parent->getStmtClass()) { |
| case Stmt::CompoundStmtClass: |
| case Stmt::ForStmtClass: |
| case Stmt::CXXForRangeStmtClass: |
| case Stmt::WhileStmtClass: |
| case Stmt::DoStmtClass: |
| case Stmt::IfStmtClass: |
| |
| // Other candidates: |
| // Stmt::CXXTryStmtClass |
| // Stmt::CXXCatchStmtClass |
| // Stmt::CapturedStmtClass |
| // Stmt::SwitchStmtClass |
| // Stmt::SwitchCaseClass |
| return stmt; |
| default: |
| stmt = parent; |
| break; |
| } |
| } |
| } |
| |
| bool WasPointerTaken(const Stmt* stmt, const Decl* decl) { |
| std::function<bool(const Stmt*)> visit_stmt = [&](const Stmt* stmt) { |
| if (auto* op = clang::dyn_cast<UnaryOperator>(stmt)) { |
| if (op->getOpcode() == clang::UO_AddrOf) { |
| auto* ref = clang::dyn_cast<DeclRefExpr>(op->getSubExpr()); |
| // |ref| may be null if the sub-expr has a dependent type. |
| if (ref && ref->getDecl() == decl) |
| return true; |
| } |
| } |
| |
| for (auto* c : stmt->children()) { |
| if (!c) |
| continue; |
| if (visit_stmt(c)) |
| return true; |
| } |
| return false; |
| }; |
| return visit_stmt(stmt); |
| } |
| |
| bool HasCapturingLambda(const Stmt* stmt, const Decl* decl) { |
| std::function<bool(const Stmt*)> visit_stmt = [&](const Stmt* stmt) { |
| if (auto* l = clang::dyn_cast<LambdaExpr>(stmt)) { |
| for (auto c : l->captures()) { |
| if (c.getCapturedVar() == decl) |
| return true; |
| } |
| } |
| |
| for (auto* c : stmt->children()) { |
| if (!c) |
| continue; |
| |
| if (visit_stmt(c)) |
| return true; |
| } |
| |
| return false; |
| }; |
| return visit_stmt(stmt); |
| } |
| |
| // Returns true if there are multiple occurrences to |decl| in one of C++ |
| // statements in |stmt|. |
| bool HasUnorderedOccurrences(const Decl* decl, const Stmt* stmt) { |
| int count = 0; |
| std::function<void(const Stmt*)> visit_stmt = [&](const Stmt* s) { |
| if (auto* ref = clang::dyn_cast<DeclRefExpr>(s)) { |
| if (ref->getDecl() == decl) |
| ++count; |
| } |
| for (auto* c : s->children()) { |
| if (!c) |
| continue; |
| visit_stmt(c); |
| } |
| }; |
| |
| visit_stmt(EnclosingCxxStatement(stmt)); |
| return count > 1; |
| } |
| |
| void run(const MatchFinder::MatchResult& result) override { |
| auto* target = result.Nodes.getNodeAs<clang::DeclRefExpr>("target"); |
| auto* decl = clang::dyn_cast<clang::VarDecl>(target->getDecl()); |
| |
| // Other than local variables and parameters are out-of-scope. |
| if (!decl || !decl->isLocalVarDeclOrParm()) |
| return; |
| |
| auto qual_type = decl->getType(); |
| // Qualified variables are out-of-scope. They are likely not movable. |
| if (qual_type.getCanonicalType().hasQualifiers()) |
| return; |
| |
| auto* type = qual_type.getTypePtr(); |
| // References and pointers are out-of-scope. |
| if (type->isReferenceType() || type->isPointerType()) |
| return; |
| |
| Stmt* body = nullptr; |
| if (auto* ctor = result.Nodes.getNodeAs<LambdaExpr>("enclosing_ctor")) |
| return; // Skip constructor case for now. TBD. |
| else if (auto* func = |
| result.Nodes.getNodeAs<FunctionDecl>("enclosing_func")) |
| body = func->getBody(); |
| else if (auto* lambda = |
| result.Nodes.getNodeAs<LambdaExpr>("enclosing_lambda")) |
| body = lambda->getBody(); |
| else |
| return; |
| |
| // Disable the replacement if there is a lambda that captures |decl|. |
| if (HasCapturingLambda(body, decl)) |
| return; |
| |
| // Disable the replacement if the pointer to |decl| is taken in the scope. |
| if (WasPointerTaken(body, decl)) |
| return; |
| |
| if (!ExtractCFGContentToMembers(body, result.Context)) |
| return; |
| |
| auto* parent = parent_map_->getParentIgnoreParenCasts(target); |
| if (auto* p = clang::dyn_cast<CallExpr>(parent)) { |
| auto* callee = p->getCalleeDecl(); |
| // |callee| may be null if the CallExpr has an unresolved look up. |
| if (!callee) |
| return; |
| auto* callee_decl = clang::dyn_cast<clang::NamedDecl>(callee); |
| auto name = callee_decl->getQualifiedNameAsString(); |
| |
| // Disable the replacement if it's already in std::move() or |
| // std::forward(). |
| if (name == "std::__1::move" || name == "std::__1::forward") |
| return; |
| } else if (parent->getStmtClass() == Stmt::ReturnStmtClass) { |
| // Disable the replacement if it's in a return statement. |
| return; |
| } |
| |
| // If the same C++ statement contains multiple reference to the variable, |
| // don't insert std::move() to be conservative. |
| if (HasUnorderedOccurrences(decl, target)) |
| return; |
| |
| bool saw_reuse = false; |
| ForEachFollowingStmts(target, [&](const Stmt* stmt) { |
| if (auto* ref = clang::dyn_cast<DeclRefExpr>(stmt)) { |
| if (ref->getDecl() == decl) { |
| saw_reuse = true; |
| return false; |
| } |
| } |
| |
| // TODO: Detect Reset() and operator=() to stop the traversal. |
| return true; |
| }); |
| if (saw_reuse) |
| return; |
| |
| replacements_->emplace_back( |
| *result.SourceManager, |
| result.SourceManager->getSpellingLoc(target->getLocStart()), 0, |
| "std::move("); |
| replacements_->emplace_back( |
| *result.SourceManager, |
| clang::Lexer::getLocForEndOfToken(target->getLocEnd(), 0, |
| *result.SourceManager, |
| result.Context->getLangOpts()), |
| 0, ")"); |
| } |
| |
| // Invokes |handler| for each Stmt that follows |target| until it reaches the |
| // end of the lifetime of the variable that |target| references. |
| // If |handler| returns false, stops following the current control flow. |
| void ForEachFollowingStmts(const DeclRefExpr* target, |
| std::function<bool(const Stmt*)> handler) { |
| auto* decl = target->getDecl(); |
| auto* block = enclosing_block_[target]; |
| |
| std::set<const clang::CFGBlock*> visited; |
| std::vector<const clang::CFGBlock*> stack = {block}; |
| |
| bool saw_target = false; |
| std::function<bool(const Stmt*)> visit_stmt = [&](const Stmt* s) { |
| for (auto* t : s->children()) { |
| if (!t) |
| continue; |
| |
| // |t| is evaluated elsewhere if a sub-Stmt is in |top_stmt_|. |
| if (top_stmts_.find(t) != top_stmts_.end()) |
| continue; |
| |
| if (!visit_stmt(t)) |
| return false; |
| } |
| |
| if (!saw_target) { |
| if (s == target) |
| saw_target = true; |
| return true; |
| } |
| |
| return handler(s); |
| }; |
| |
| bool visited_initial_block_twice = false; |
| while (!stack.empty()) { |
| auto* b = stack.back(); |
| stack.pop_back(); |
| if (!visited.insert(b).second) { |
| if (b != block || visited_initial_block_twice) |
| continue; |
| visited_initial_block_twice = true; |
| } |
| |
| bool cont = true; |
| for (auto e : *b) { |
| if (auto s = e.getAs<CFGStmt>()) { |
| if (!visit_stmt(s->getStmt())) { |
| cont = false; |
| break; |
| } |
| } else if (auto l = e.getAs<CFGLifetimeEnds>()) { |
| if (l->getVarDecl() == decl) { |
| cont = false; |
| break; |
| } |
| } |
| } |
| |
| if (cont) { |
| for (auto s : b->succs()) { |
| if (!s) |
| continue; // Unreachable block. |
| stack.push_back(s); |
| } |
| } |
| } |
| } |
| |
| private: |
| // Function body to CFG. |
| std::map<const Stmt*, std::unique_ptr<CFG>> cfg_cache_; |
| |
| // Statement to the enclosing CFGBlock. |
| std::map<const Stmt*, const CFGBlock*> enclosing_block_; |
| |
| // Stmt to its parent Stmt. |
| std::unique_ptr<clang::ParentMap> parent_map_; |
| |
| // A set of Stmt that a CFGElement has it directly. |
| std::set<const Stmt*> top_stmts_; |
| |
| Replacements* replacements_; |
| }; |
| |
| // Remove base::AdaptCallbackForRepeating() where resulting |
| // base::RepeatingCallback is implicitly converted into base::OnceCallback. |
| // Example: |
| // // Before |
| // base::PostTask( |
| // FROM_HERE, |
| // base::AdaptCallbackForRepeating(base::BindOnce(&Foo))); |
| // base::OnceCallback<void()> cb = base::AdaptCallbackForRepeating( |
| // base::OnceBind(&Foo)); |
| // |
| // // After |
| // base::PostTask(FROM_HERE, base::BindOnce(&Foo)); |
| // base::OnceCallback<void()> cb = base::BindOnce(&Foo); |
| class AdaptCallbackForRepeatingRewriter : public MatchFinder::MatchCallback, |
| public Rewriter { |
| public: |
| explicit AdaptCallbackForRepeatingRewriter(Replacements* replacements) |
| : replacements_(replacements) {} |
| |
| StatementMatcher GetMatcher() { |
| auto is_once_callback = hasType(hasCanonicalType(hasDeclaration( |
| classTemplateSpecializationDecl(hasName("::base::OnceCallback"))))); |
| auto is_repeating_callback = |
| hasType(hasCanonicalType(hasDeclaration(classTemplateSpecializationDecl( |
| hasName("::base::RepeatingCallback"))))); |
| |
| auto adapt_callback_call = |
| callExpr( |
| callee(namedDecl(hasName("::base::AdaptCallbackForRepeating")))) |
| .bind("target"); |
| auto parameter_construction = |
| cxxConstructExpr(is_repeating_callback, argumentCountIs(1), |
| hasArgument(0, ignoringImplicit(adapt_callback_call))); |
| auto constructor_conversion = cxxConstructExpr( |
| is_once_callback, argumentCountIs(1), |
| hasArgument(0, ignoringImplicit(parameter_construction))); |
| return implicitCastExpr(is_once_callback, |
| hasSourceExpression(constructor_conversion)); |
| } |
| |
| void run(const MatchFinder::MatchResult& result) override { |
| auto* target = result.Nodes.getNodeAs<clang::CallExpr>("target"); |
| |
| auto left = clang::CharSourceRange::getTokenRange( |
| result.SourceManager->getSpellingLoc(target->getLocStart()), |
| result.SourceManager->getSpellingLoc(target->getArg(0)->getExprLoc()) |
| .getLocWithOffset(-1)); |
| |
| // We use " " as replacement to work around https://crbug.com/861886. |
| replacements_->emplace_back(*result.SourceManager, left, " "); |
| auto r_paren = clang::CharSourceRange::getTokenRange( |
| result.SourceManager->getSpellingLoc(target->getRParenLoc()), |
| result.SourceManager->getSpellingLoc(target->getRParenLoc())); |
| replacements_->emplace_back(*result.SourceManager, r_paren, " "); |
| } |
| |
| private: |
| Replacements* replacements_; |
| }; |
| |
| llvm::cl::extrahelp common_help(CommonOptionsParser::HelpMessage); |
| llvm::cl::OptionCategory rewriter_category("Rewriter Options"); |
| |
| llvm::cl::opt<std::string> rewriter_option( |
| "rewriter", |
| llvm::cl::desc(R"(One of the name of rewriter to apply. |
| Available rewriters are: |
| remove_unneeded_passed |
| bind_to_bind_once |
| pass_by_value |
| add_std_move |
| remove_unneeded_adapt_callback |
| The default is remove_unneeded_passed. |
| )"), |
| llvm::cl::init("remove_unneeded_passed"), |
| llvm::cl::cat(rewriter_category)); |
| |
| } // namespace. |
| |
| int main(int argc, const char* argv[]) { |
| llvm::InitializeNativeTarget(); |
| llvm::InitializeNativeTargetAsmParser(); |
| CommonOptionsParser options(argc, argv, rewriter_category); |
| clang::tooling::ClangTool tool(options.getCompilations(), |
| options.getSourcePathList()); |
| |
| MatchFinder match_finder; |
| std::vector<clang::tooling::Replacement> replacements; |
| |
| std::unique_ptr<Rewriter> rewriter; |
| if (rewriter_option == "remove_unneeded_passed") { |
| auto passed_to_move = |
| llvm::make_unique<PassedToMoveRewriter>(&replacements); |
| match_finder.addMatcher(passed_to_move->GetMatcher(), passed_to_move.get()); |
| rewriter = std::move(passed_to_move); |
| } else if (rewriter_option == "bind_to_bind_once") { |
| auto bind_once = llvm::make_unique<BindOnceRewriter>(&replacements); |
| match_finder.addMatcher(bind_once->GetMatcher(), bind_once.get()); |
| rewriter = std::move(bind_once); |
| } else if (rewriter_option == "pass_by_value") { |
| auto pass_by_value = llvm::make_unique<PassByValueRewriter>(&replacements); |
| match_finder.addMatcher(pass_by_value->GetMatcher(), pass_by_value.get()); |
| rewriter = std::move(pass_by_value); |
| } else if (rewriter_option == "add_std_move") { |
| auto add_std_move = llvm::make_unique<AddStdMoveRewriter>(&replacements); |
| match_finder.addMatcher(add_std_move->GetMatcher(), add_std_move.get()); |
| rewriter = std::move(add_std_move); |
| } else if (rewriter_option == "remove_unneeded_adapt_callback") { |
| auto remove_unneeded_adapt_callback = |
| llvm::make_unique<AdaptCallbackForRepeatingRewriter>(&replacements); |
| match_finder.addMatcher(remove_unneeded_adapt_callback->GetMatcher(), |
| remove_unneeded_adapt_callback.get()); |
| rewriter = std::move(remove_unneeded_adapt_callback); |
| } else { |
| abort(); |
| } |
| |
| std::unique_ptr<clang::tooling::FrontendActionFactory> factory = |
| clang::tooling::newFrontendActionFactory(&match_finder); |
| int result = tool.run(factory.get()); |
| if (result != 0) |
| return result; |
| |
| // Serialization format is documented in tools/clang/scripts/run_tool.py |
| llvm::outs() << "==== BEGIN EDITS ====\n"; |
| for (const auto& r : replacements) { |
| std::string replacement_text = r.getReplacementText().str(); |
| std::replace(replacement_text.begin(), replacement_text.end(), '\n', '\0'); |
| llvm::outs() << "r:::" << r.getFilePath() << ":::" << r.getOffset() |
| << ":::" << r.getLength() << ":::" << replacement_text << "\n"; |
| } |
| llvm::outs() << "==== END EDITS ====\n"; |
| |
| return 0; |
| } |