Skip to content

Commit

Permalink
Add an example refactoring tool
Browse files Browse the repository at this point in the history
This is a simple tool that is able to exemplify how you would
implement the removal of a system that you know have a determinstic
result for the function calls.

Signed-off-by: Daniel Ruoso <druoso@bloomberg.net>
  • Loading branch information
ruoso committed May 13, 2019
1 parent d742dd0 commit 23d58fe
Show file tree
Hide file tree
Showing 15 changed files with 352 additions and 0 deletions.
22 changes: 22 additions & 0 deletions examples/remove_ye_olde_feature_toggle/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
cmake_minimum_required(VERSION 3.6)

project( remove_ye_olde_feature_toggle C CXX)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)

find_package(Clang REQUIRED)
find_package(clangmetatool REQUIRED)

add_executable(
remove_ye_olde_feature_toggle
src/main.cpp
# add other source names here
)

target_include_directories(remove_ye_olde_feature_toggle PRIVATE ${CLANG_INCLUDE_DIRS} )
target_link_libraries(remove_ye_olde_feature_toggle clangmetatool clangTooling )

clangmetatool_install(remove_ye_olde_feature_toggle)

enable_testing()
add_subdirectory(t)
203 changes: 203 additions & 0 deletions examples/remove_ye_olde_feature_toggle/src/main.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,203 @@
#include <iosfwd>
#include <map>
#include <tuple>
#include <optional>
#include <sstream>

#include <clang/Frontend/FrontendAction.h>
#include <clang/Tooling/Core/Replacement.h>
#include <clang/Tooling/CommonOptionsParser.h>
#include <clang/Tooling/Tooling.h>
#include <clang/Tooling/Refactoring.h>
#include <llvm/Support/CommandLine.h>
#include <clang/ASTMatchers/ASTMatchFinder.h>


#include <clangmetatool/meta_tool_factory.h>
#include <clangmetatool/meta_tool.h>

#include <clangmetatool/types/file_uid.h>
#include <clangmetatool/collectors/find_calls.h>
#include <clangmetatool/collectors/find_calls_data.h>
#include <clangmetatool/collectors/include_graph.h>
#include <clangmetatool/collectors/include_graph_data.h>
#include <clangmetatool/propagation/constant_integer_propagator.h>

namespace {

std::optional<int64_t> ye_deterministic_val(int64_t feature_id) {
if (feature_id % 3 == 0 && feature_id % 5 == 0) {
return {};
} else if (feature_id % 3 == 0) {
return 1;
} else if (feature_id % 5 == 0) {
return 0;
} else {
return feature_id;
}
}

std::string get_newcode_for_ye(int64_t feature_id, int64_t value) {
std::stringstream newcode_ss;
newcode_ss
<< value
<< " /* removed ye_olde_feature_toggle "
<< feature_id
<< " */";
return newcode_ss.str();
}

}

class MyTool {
clang::CompilerInstance* ci;
clangmetatool::collectors::IncludeGraph ig;
clangmetatool::collectors::FindCalls fc;
public:
MyTool(clang::CompilerInstance *_ci, clang::ast_matchers::MatchFinder *f)
:ig(_ci, f),
fc(_ci, f, "ye_olde_feature_toggle_is_enabled"),
ci(_ci) {}
void postProcessing(
std::map<std::string, clang::tooling::Replacements> &replacementsMap) {
clangmetatool::collectors::IncludeGraphData* igdata = ig.getData();
clang::ASTContext& ctx = ci->getASTContext();
clang::SourceManager& sm = ctx.getSourceManager();

// what is the file id for ye_olde_feature_toggle.h
bool found_header = false;
clangmetatool::types::FileUID header_fuid = 0;
for (auto item : igdata->fuid2name) {
if (item.second == "ye_olde_feature_toggle.h") {
found_header = true;
header_fuid = item.first;
break;
}
}

// bail early if the header was not used
if (!found_header)
return;

// accumulate all the calls, the argument, and its optional determinstic value
typedef std::tuple<const clang::CallExpr*, int64_t, std::optional<int64_t> > ye_call;
std::vector<ye_call> candidates;

// iterate over all the calls to that function
clangmetatool::collectors::FindCallsData* fcdata = fc.getData();
for (auto call_ctx : fcdata->call_context) {
const clang::CallExpr* call = call_ctx.second;
if (!sm.isWrittenInMainFile(call->getBeginLoc()))
continue;
if (sm.isMacroBodyExpansion(call->getBeginLoc()))
continue;
const clang::Expr* arg = call->getArg(0);
llvm::APSInt argval;
if (arg->isIntegerConstantExpr(argval, ctx)) {
int64_t feature_id = argval.getExtValue();
candidates.push_back
( ye_call{call, feature_id, ye_deterministic_val(feature_id) } );
continue;
}

// let's try the const propagation.

// First we need to see if it is a implicitcastexpr ->
// declrefexpr -- which is the AST for using a variable
auto arg_as_ice = llvm::dyn_cast<clang::ImplicitCastExpr>(arg);
if (arg_as_ice == NULL)
continue;

const clang::Expr* subexpr = arg_as_ice->getSubExpr();
auto subexpr_as_dre = llvm::dyn_cast<clang::DeclRefExpr>(subexpr);
if (subexpr_as_dre == NULL)
continue;

clangmetatool::propagation::ConstantIntegerPropagator p(ci);
auto r = p.runPropagation(call_ctx.first, subexpr_as_dre);
if (r.isUnresolved())
continue;

int64_t feature_id = r.getResult();
candidates.push_back
( ye_call{call, feature_id, ye_deterministic_val(feature_id) } );
}

// slightly ugly bit because we have a single Replacments object per file
auto replacements_emp = replacementsMap.emplace
(sm.getFileEntryForID(sm.getMainFileID())->getName(), clang::tooling::Replacements());
clang::tooling::Replacements& replacements = replacements_emp.first->second;

// actually work on the refactorings
std::set<const clang::DeclRefExpr*> removed;
for (auto ye_candidate : candidates) {
const clang::CallExpr* call = std::get<0>(ye_candidate);
int64_t feature_id = std::get<1>(ye_candidate);
std::optional<int64_t> det_val = std::get<2>(ye_candidate);
if (!det_val.has_value())
continue;

std::string newcode = get_newcode_for_ye(feature_id, det_val.value());
clang::tooling::Replacement replacement(sm, call, newcode);

llvm::Error err = replacements.add(replacement);
// keep track of the DeclRefExpr we actually removed
if (!err)
removed.insert(fcdata->call_ref[call]);
}

// let's remove the now-unused include statements
// since we're only changing the main file, we're only going to look at
// main_fuid -> header_fuid edges of the include graph
clangmetatool::types::FileUID main_fuid =
sm.getFileEntryForID(sm.getMainFileID())->getUID();
clangmetatool::types::FileGraphEdge relevant{ main_fuid, header_fuid };
bool now_unused = true;
auto refs_range = igdata->decl_references.equal_range(relevant);
for ( auto it = refs_range.first; it != refs_range.second; it++ ) {
// double check if we removed all references
if ( removed.find(it->second) == removed.end() ) {
now_unused = false;
break;
}
}

if (now_unused) {
auto incs_range = igdata->include_statements.equal_range(relevant);
for ( auto it = incs_range.first; it != incs_range.second; it++ ) {
clang::SourceRange inc_r = it->second;
unsigned int beg_line = sm.getSpellingLineNumber(inc_r.getBegin());
unsigned int end_line = sm.getSpellingLineNumber(inc_r.getEnd());
clang::SourceLocation beg_loc =
sm.translateLineCol(sm.getMainFileID(), beg_line, 1);
clang::SourceLocation end_loc =
sm.translateLineCol(sm.getMainFileID(), end_line+1, 1);
clang::CharSourceRange repl_range =
clang::CharSourceRange(clang::SourceRange(beg_loc, end_loc), false);

clang::tooling::Replacement replacement(sm, repl_range, "");
llvm::Error err = replacements.add(replacement);
}
}

}
};

int main(int argc, const char *argv[]) {
llvm::cl::OptionCategory MyToolCategory("my-tool options");

llvm::cl::extrahelp CommonHelp(
clang::tooling::CommonOptionsParser::HelpMessage);

clang::tooling::CommonOptionsParser optionsParser(argc, argv, MyToolCategory);

clang::tooling::RefactoringTool tool(optionsParser.getCompilations(),
optionsParser.getSourcePathList());

clangmetatool::MetaToolFactory<clangmetatool::MetaTool<MyTool>> raf(
tool.getReplacements());

int r = tool.runAndSave(&raf);

return r;
}
16 changes: 16 additions & 0 deletions examples/remove_ye_olde_feature_toggle/t/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
foreach( TEST
simple_direct_usage
true_vs_false
non_deterministic
mixed_deterministic_and_not
integer_variable
)
add_test(
NAME ${TEST}
COMMAND
${CMAKE_CURRENT_SOURCE_DIR}/tool_test_runner.sh
$<TARGET_FILE:remove_ye_olde_feature_toggle>
${CMAKE_CURRENT_SOURCE_DIR}
${CMAKE_CURRENT_BINARY_DIR}
${TEST} )
endforeach()
10 changes: 10 additions & 0 deletions examples/remove_ye_olde_feature_toggle/t/integer_variable.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
#include <ye_olde_feature_toggle.h>
int test_function() {
int feature_id = 3;
if (ye_olde_feature_toggle_is_enabled(feature_id))
return 1;
feature_id = 5;
if (ye_olde_feature_toggle_is_enabled(feature_id))
return 0;
return 42;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
int test_function() {
int feature_id = 3;
if (1 /* removed ye_olde_feature_toggle 3 */)
return 1;
feature_id = 5;
if (0 /* removed ye_olde_feature_toggle 5 */)
return 0;
return 42;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
#include <ye_olde_feature_toggle.h>
int test_function() {
if (ye_olde_feature_toggle_is_enabled(15) ||
ye_olde_feature_toggle_is_enabled(3)) {
return 1;
} else {
return 0;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
#include <ye_olde_feature_toggle.h>
int test_function() {
if (ye_olde_feature_toggle_is_enabled(15) ||
1 /* removed ye_olde_feature_toggle 3 */) {
return 1;
} else {
return 0;
}
}
8 changes: 8 additions & 0 deletions examples/remove_ye_olde_feature_toggle/t/non_deterministic.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
#include <ye_olde_feature_toggle.h>
int test_function() {
if (ye_olde_feature_toggle_is_enabled(15)) {
return 1;
} else {
return 0;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
#include <ye_olde_feature_toggle.h>
int test_function() {
if (ye_olde_feature_toggle_is_enabled(15)) {
return 1;
} else {
return 0;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
#include <ye_olde_feature_toggle.h>
int test_function() {
if (ye_olde_feature_toggle_is_enabled(3)) {
return 1;
} else {
return 0;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
int test_function() {
if (1 /* removed ye_olde_feature_toggle 3 */) {
return 1;
} else {
return 0;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
#ifndef INCLUDED_YE_OLDE_FEATURE_TOGGLE_H
#define INCLUDED_YE_OLDE_FEATURE_TOGGLE_H
#ifdef __cplusplus
extern "C" {
#endif

int ye_olde_feature_toggle_is_enabled(int feature_id);

#ifdef __cplusplus
}
#endif
#endif
12 changes: 12 additions & 0 deletions examples/remove_ye_olde_feature_toggle/t/tool_test_runner.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
#!/bin/sh
# Arguments to this script are:
# 1. the path to the tool as built by CMake
# 2. CMAKE_CURRENT_SOURCE_DIR
# 3. CMAKE_CURRENT_BINARY_DIR
# 4. the name of the test case
set -x
set -e
cp $2/$4.c $3/$4.c
$1 $3/$4.c -- -I$2/testinclude
diff -u $2/$4.c.expected $3/$4.c

10 changes: 10 additions & 0 deletions examples/remove_ye_olde_feature_toggle/t/true_vs_false.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
#include <ye_olde_feature_toggle.h>
int test_function() {
if (ye_olde_feature_toggle_is_enabled(3) ||
ye_olde_feature_toggle_is_enabled(5) ||
ye_olde_feature_toggle_is_enabled(7)) {
return 1;
} else {
return 0;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
int test_function() {
if (1 /* removed ye_olde_feature_toggle 3 */ ||
0 /* removed ye_olde_feature_toggle 5 */ ||
7 /* removed ye_olde_feature_toggle 7 */) {
return 1;
} else {
return 0;
}
}

0 comments on commit 23d58fe

Please sign in to comment.