// 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;
}