前言
实验室的代码异化项目,我来接手,但是有一说一,这个项目很复杂
头文件部分
consumer.h
主要就是定义了六中错误类型
include
这里面include了几个AST相关的东西:
#include <clang/AST/ASTConsumer.h>
#include <clang/AST/ASTContext.h>
#include <clang/ASTMatchers/ASTMatchFinder.h>
#include <clang/Rewrite/Core/Rewriter.h>
#include <cstdint>
#include <string>
#include <utility>
<clang/AST/ASTConsumer.h>
:该头文件提供了ASTConsumer
类,它是一个抽象基类,定义了消耗 Clang 解析器生成的 AST(抽象语法树)的类的接口。<clang/AST/ASTContext.h>
:该头文件提供了ASTContext
类,表示 Clang 解析器生成的 AST 的上下文。<clang/ASTMatchers/ASTMatchFinder.h>
:该头文件提供了 AST 匹配器和匹配器查找器,用于在 AST 中查找特定模式的节点。<clang/Rewrite/Core/Rewriter.h>
:该头文件提供了Rewriter
类,用于重写源代码,通常用于生成修改后的代码。
enum枚举部分
enum {
// 语义错误
OPERATOR_SEMANTIC = 0,//操作符错误
BOUND_SEMANTIC,//边界错误
VARUSE_SEMANTIC,//变量使用错误
// 语法错误
UNDEFVAR_SYNTAX,//未定义变量错误
FORSEMI_SYNTAX,//for循环逗号错误
PAREN_SYNTAX,//括号错误
ALL_CONSUMER
};
父类:CustomASTConsumer
所有错误类型的父类,他本身也继承了clang::ASTConsumer
class CustomASTConsumer : public clang::ASTConsumer {
protected:
// 用于查找 AST 中特定模式的 MatchFinder
clang::ast_matchers::MatchFinder finder;
// 存储 MatchCallback 的唯一指针的容器
std::vector<std::unique_ptr<clang::ast_matchers::MatchFinder::MatchCallback>> callbacks;
public:
// 纯虚函数,用于获取自定义 AST 消费者的名称
virtual std::string name() const = 0;
};
callbacks
是一个容器,其中的每个元素都是一个指向 clang::ast_matchers::MatchFinder::MatchCallback
类型对象的 std::unique_ptr
智能指针。
OperatorSemantic
class OperatorSemantic : public CustomASTConsumer {
public:
explicit OperatorSemantic(
std::vector<std::function<void(clang::Rewriter &)>> &all);
void HandleTranslationUnit(clang::ASTContext &Context) override;
std::string name() const override;
};
explicit OperatorSemantic(
std::vector<std::function<void(clang::Rewriter &)>> &all);
构造函数,接受一个vector,vector当中存储的是返回值为void,参数类型为Rewriter引用类型的函数
void HandleTranslationUnit(clang::ASTContext &Context) override;
处理翻译单元的AST
其余错误类型格式都是一样的,看一个就好了
ConsumerFactory
class ASTConsumerFactory {
public:
static std::unique_ptr<clang::ASTConsumer>
getASTConsumer(std::vector<std::function<void(clang::Rewriter &)>> &all,
int type = ALL_CONSUMER);
static bool isValidType(int type) {
return type < ALL_CONSUMER && type >= OPERATOR_SEMANTIC;
}
};
用于获取 clang::ASTConsumer
的对象。它接受一个vector all
作为参数,该vector中的每个元素都是一个函数对象,这些函数对象接受一个 clang::Rewriter
的引用,并且返回 void
。该函数还接受一个可选参数 type
,默认值为 ALL_CONSUMER
util.h
就是用来生成随机数和打乱一个vector的
// 声明一个函数模板,用于生成指定长度的随机字符串
std::string randomStr(int n);
// 声明一个函数模板,用于随机打乱 vector 元素的顺序
template<typename T>
void shuffleVector(std::vector<T> &v) {
// 初始化随机种子
std::random_device randDevice;
std::mt19937 generate(randDevice());
// 随机排序
shuffle(v.begin(), v.end(), generate);
}
在rand.cpp里会用到
exp.h
自定义了两种exp
#include <exception>
// 自定义异常类 InvalidConsumerExp,继承自 std::exception
class InvalidConsumerExp : public std::exception {
public:
// 重写基类中的虚函数 what(),返回异常信息的 C 风格字符串
const char *what() const noexcept override {
return "Invalid Consumer Exception";
}
};
// 自定义异常类 NoMoreConsumerExp,继承自 std::exception
class NoMoreConsumerExp : public std::exception {
public:
// 重写基类中的虚函数 what(),返回异常信息的 C 风格字符串
const char *what() const noexcept override {
return "No More Consumer Exception";
}
};
syntax包
主要是三种语法错误
ForSemiSyntax
生成错误的for语句,将for语句本来的分号替换为逗号
匿名namespace
namespace {
const string desc =
"This bug is a syntax error where a comma is mistakenly used instead "
"of a semicolon within a for loop";
const string id = "for";
class ForSemiCallback : public MatchFinder::MatchCallback {
public:
vector<function<void(Rewriter &)>> &all;
explicit ForSemiCallback(vector<function<void(Rewriter &)>> &all)
: all(all) {}
void run(const MatchFinder::MatchResult &result) override {
const ForStmt *bo = result.Nodes.getNodeAs<ForStmt>(id);//获取循环语句的节点
SourceManager *sm = result.SourceManager;
if (bo == nullptr) return;
// 获取初始子句的结束位置,这是第一个分号的位置
const Stmt *init = bo->getInit();
// 获取条件语句的结束位置, 这是第二个分号的位置
const Stmt *cond = bo->getCond();
if ((init || cond)&&sm) {
function<void(Rewriter &)> f = [init, cond, sm](Rewriter &r) {
SourceLocation loc;
int i = randomNumber(0, 2);
if (init != nullptr)
loc = init->getEndLoc();
if (i == 0 && cond != nullptr)
loc = cond->getEndLoc();
// 使用Rewriter替换分号为逗号
r.ReplaceText(loc, 1, ",");
outs() << sm->getPresumedLineNumber(loc) << " "
<< sm->getPresumedColumnNumber(loc) << "\n"
<< desc << "\n";
};
all.push_back(f);
}
}
};
} // namespace
定义了描述、id还有一个类
来看一下这个ForSemiCallback
包含一个vector引用类型的成员变量,vector<function<void(Rewriter &)>> &all;
:这是一个引用类型的成员变量,用于存储函数对象的向量。并且在构造函数中初始化:
explicit ForSemiCallback(vector<function<void(Rewriter &)>> &all)
: all(all) {}
run方法
定位两个分号的位置,随机将一个分号改成逗号
构造函数
所有错误类型的构造函数都差不多,就分析这个了
ForSemiSyntax::ForSemiSyntax(vector<function<void(Rewriter &)>> &all) {
this->callbacks.push_back(
unique_ptr<MatchFinder::MatchCallback>(new ForSemiCallback(all)));//把自定义的callback函数放到容器当中
// 匹配所有for语句并且将他们的id设置为我们自定义的id,方便后续找到
StatementMatcher m = forStmt(isExpansionInMainFile()).bind(id);
finder.addMatcher(m, this->callbacks[0].get());//对于所有for语句,都绑定ForSemiCallback,使得所有的for都能异化
}
还有两个大家都一样的方法:
void ParenSyntax::HandleTranslationUnit(clang::ASTContext &context) {
finder.matchAST(context);
}
string ParenSyntax::name() const { return "ParenSyntax"; }
以后就不分析了
ParenSyntax
只看run方法了
void run(const MatchFinder::MatchResult &result) override {
const Stmt *bo = result.Nodes.getNodeAs<Stmt>(id);
const SourceManager *sm = result.SourceManager;
if (bo && sm) {//有效才执行
function<void(Rewriter &)> f = [bo, sm](Rewriter &r) {
// 左括号位置 右括号位置
SourceLocation lParenLoc, rParenLoc;
if (const CallExpr *callExpr = dyn_cast<CallExpr>(bo)) {//如果bo是一个函数调用表达式的话
rParenLoc = callExpr->getRParenLoc();//那么就获取右括号的位置
// 获取左括号的位置
if (callExpr->getNumArgs() == 0) {
// 如果没有参数
lParenLoc = rParenLoc.getLocWithOffset(-1);//左括号偏移量为-1
} else {
// 有参数,使用第一个参数位置
lParenLoc = callExpr->getArg(0)->getBeginLoc().getLocWithOffset(-1);
// 向左搜寻,以确保找到左括号的位置(考虑有空格和其他字符的情况)
while (sm->getCharacterData(lParenLoc)[-1] != '(') {
lParenLoc = lParenLoc.getLocWithOffset(-1);
}
}
} else if (const ParenExpr *parenExpr = dyn_cast<ParenExpr>(bo)) {//如果bo是一个括号表达式的话
lParenLoc = parenExpr->getLParen();//直接获取
rParenLoc = parenExpr->getRParen();
} else {
return;
}
char c = '(';
SourceLocation loc = lParenLoc;
//随机删除左右括号
int i = randomNumber(0, 2);
if (i == 0) {
c = ')';
loc = rParenLoc;
}
r.RemoveText(loc, 1);
outs() << sm->getPresumedLineNumber(loc) << " "
<< sm->getPresumedColumnNumber(loc) << " " << c << "\n"
<< desc << "\n";
};
all.push_back(f);//被掉用户后就销毁,需要手动重新push
}
}
UndefVarSyntax
未定义变量错误,使用了未定义的变量, 现实情况可能是打错了变量名. 随机选择一个变量使用, 将它改成一个没有出现过的变量名.
void run(const MatchFinder::MatchResult &result) override {
const DeclRefExpr *bo = result.Nodes.getNodeAs<DeclRefExpr>(id);//获得为id的变量的节点
const SourceManager *sm = result.SourceManager;//获得资源管理器
if (bo && sm) {
function<void(Rewriter &)> f = [bo, sm](Rewriter &r) {
const NamedDecl *nd = dyn_cast<NamedDecl>(bo->getDecl());//获得声明并转为名称的指针
const string oldn = nd->getName().str();//old名称
const string newn = oldn + randomStr(oldn.size());//新变量名
r.ReplaceText(bo->getSourceRange(), newn);//替换变量名
SourceLocation loc = bo->getBeginLoc();
outs() << sm->getPresumedLineNumber(loc) << " "
<< sm->getPresumedColumnNumber(loc) << " " << oldn << " " << newn
<< "\n"
<< desc << "\n";
};
all.push_back(f);
}
}
semantic包
语义错误
OperatorSemantic
操作符错误,随机把正确的操作符替换为错误的操作符
首先定义了所有的二院操作符,以二维数组的形式
vector<vector<string>> binaryOperators = {
{"+", "-", "*", "/", "%"}, // Arithmetic
{"<", "<=", ">", ">=", "==", "!="}, // Comparison
{"&&", "||"}, // Logical
{"&", "|", "^"}, // Bitwise
{"=", "+=", "-=", "*=", "/=", "%="} // Assignment
};
nextStr函数
从二维数组中选取一个不同于cur的二元操作符
string nxtStr(string &cur) {
string nxt = cur;
for (auto &line : binaryOperators) {
if (find(line.begin(), line.end(), cur) == line.end())
continue;
int s = line.size();
// if (s == 1) break;
int rand = randomNumber(0, s);
nxt = line[rand];
while (nxt == cur) {
rand = randomNumber(0, s);
nxt = line[rand];
}
break;
}
return nxt;
}
run函数
void run(const MatchFinder::MatchResult &result) override {
const auto *bo = result.Nodes.getNodeAs<BinaryOperator>(id);//获得二元操作符的bo
SourceManager *sm = result.SourceManager;//获得资源管理器
bool isInMain = sm->isInMainFile(bo->getOperatorLoc());//判断二院操作符的位置是不是在主文件当中
if (bo && isInMain) {
string cur = bo->getOpcodeStr().str();//获得当前二元操作符
string nxt = nxtStr(cur);//新操作符
function<void(Rewriter &)> f = [bo, sm, cur, nxt](Rewriter &r) {
const SourceLocation &loc = bo->getOperatorLoc();
// 设置 rewriter
r.ReplaceText(loc, cur.size(), nxt);//替换
outs() << sm->getPresumedLineNumber(loc) << " "
<< sm->getPresumedColumnNumber(loc) << " " << cur << " " << nxt
<< "\n"
<< desc << "\n";
};
all.push_back(f);
}
}
BoundSemantic
边界错误: 对数值进行了不正确的增加或减少,从而导致变量值与预期不符. 挑选随机类型为数值类型的变量 a
, 在它后边插入 +/- number 的操作.
这个就比语法错误复杂很多,而且包含了两个callback类
modifyBound函数
对数值进行增加或者减少
inline void modifyBound(const DeclRefExpr *bo, const SourceManager *sm,
Rewriter &r) {
// 注意 getEndLoc 可能返回代表范围结束的令牌的开始位置
SourceLocation loc = bo->getEndLoc();
// 返回紧随给定令牌之后的第一个字符的位置
loc = Lexer::getLocForEndOfToken(loc, 0, *sm, r.getLangOpts());
// 修改边界, 范围过大反而不真实
int num = randomNumber(1, 6);
int op = randomNumber(0, 2);
string num_str = to_string(num);
string op_str = ((op == 0) ? " +" : " -");
r.InsertTextAfter(loc, op_str + " " + num_str);
outs() << sm->getPresumedLineNumber(loc) << " "
<< sm->getPresumedColumnNumber(loc) << "\n"
<< desc << "\n";
}
举例:比如
er
int x = 10;
int y = x + 5;
//执行
SourceLocation loc = bo->getEndLoc();
loc = Lexer::getLocForEndOfToken(loc, 0, *sm, r.getLangOpts());
//结果就是:
//5所在的位置
BoundCallbackComm 的run方法
第一个callback类
void run(const MatchFinder::MatchResult &result) override {
const auto *bo = result.Nodes.getNodeAs<DeclRefExpr>(id0);//获取id为id0且为声明引用类型的节点,id0为“numvarialbe"
const SourceManager *sm = result.SourceManager;
if (bo && sm) {
function<void(Rewriter &)> f = [bo, sm](Rewriter &r) {
modifyBound(bo, sm, r);//然后全部替换为其他值
};
all.push_back(f);
}
}
辅助函数isLastEquals
判断这个等号是否是最后一个赋值
bool isLastEquals(const BinaryOperator *bin, const SourceManager *sm) {
if (!bin || !sm) {
return false;
}
// 等号的确切位置
SourceLocation loc = bin->getOperatorLoc();
// 将 loc 移动到等号的下一个 token 位置
loc = Lexer::getLocForEndOfToken(loc, 0, *sm, LangOptions());
Lexer lexer(sm->getSpellingLoc(loc), LangOptions(),
sm->getBufferData(sm->getFileID(loc)).data(),
sm->getCharacterData(loc),
sm->getBufferData(sm->getFileID(loc)).end());//创建lexer对象,用于对从这个loc到文件末尾的分析
Token token;
while (!lexer.LexFromRawLexer(token)) {//读取token,并且存在token当中
// 如果 token 是分号 return true
if (token.is(tok::semi)) {
return true;
}
// 找到另一个等号,不是最后一个赋值
if (token.is(tok::equal)) {
return false;
}
}
// 没有找到其他的等号,是最后一个赋值
return true;
}
辅助函数rightDeclRefs
提取所有右值引用,放到result当中,result就是个vector的引用
void rightDeclRefs(vector<const DeclRefExpr *> &result, const Expr *e,
const SourceManager *sm) {
if (!e)
return;
#ifdef AMMON_DEBUG
printExpr(e, sm);
#endif
// 是否是变量使用
const DeclRefExpr *dre = dyn_cast<DeclRefExpr>(e);//能转换成功就代表是变量引用
// dre 不是 DeclRefExpr
if (dre == nullptr) {
// 在子节点中递归寻找
for (const Stmt *child : e->children()) {
const Expr *cur = dyn_cast<Expr>(child);
if (cur == nullptr)
continue;
rightDeclRefs(result, cur, sm);
}
return;
}
// dre 是 DeclRefExpr,那么尝试转换为变量声明
const VarDecl *vd = dyn_cast<VarDecl>(dre->getDecl());
// dre 引用的可能不是变量声明 (而是函数声明等), 所以这里是确认 vd 是
// VarDecl?
if (vd == nullptr)
return;
// vd 是 arithmetic?
if (!vd->getType()->isArithmeticType())
return;
result.push_back(dre);
}
BoundCallbackAssi 的run方法
在赋值语句中捕获右侧操作数中所有的变量引用,并将其边界进行修改
比第一个callback函数更加严谨
void run(const MatchFinder::MatchResult &result) override {
// 获取赋值表达式 =
const BinaryOperator *assi = result.Nodes.getNodeAs<BinaryOperator>(id1);//id1为assignment,就是等号
const SourceManager *sm = result.SourceManager;
if (assi && sm) {
// 保证是直到下一个分号为止最后一个 =
if (!isLastEquals(assi, sm))
return;
// 获取赋值右侧的表达式并忽略隐式的类型转换
const Expr *rhs = assi->getRHS()->IgnoreImpCasts();
// 收集所有的符合要求的 DeclRefExpr
vector<const DeclRefExpr *> valid;
rightDeclRefs(valid, rhs, sm);
for (const DeclRefExpr *bo : valid) {
if (bo == nullptr)
continue;
function<void(Rewriter &)> f = [bo, sm](Rewriter &r) {
modifyBound(bo, sm, r);
};
all.push_back(f);
}
}
}
构造函数
有点不一样,这里考虑了两种情况
BoundSemantic::BoundSemantic(vector<function<void(Rewriter &)>> &all) {
this->callbacks.push_back(
unique_ptr<MatchFinder::MatchCallback>(new BoundCallbackComm(all)));
this->callbacks.push_back(
unique_ptr<MatchFinder::MatchCallback>(new BoundCallbackAssi(all)));
// 寻找所有的数值类型变量使用, 并且如果上下文中有某些操作符时跳过
// 如果有等号就忽略
StatementMatcher m0 =
declRefExpr(isExpansionInMainFile(),
to(anyOf(varDecl(hasType(realFloatingPointType())),
varDecl(hasType(isInteger())))),
unless(hasAncestor(unaryOperator(
hasAnyOperatorName("++", "--", "->", ".")))),
unless(hasAncestor(binaryOperator(hasOperatorName("=")))))
.bind(id0);
// 集中处理等号情况
StatementMatcher m1 =
binaryOperator(isExpansionInMainFile(), hasOperatorName("="),
unless(hasAncestor(unaryOperator(
hasAnyOperatorName("++", "--", "->", ".")))),
forEachDescendant(declRefExpr(
to(anyOf(varDecl(hasType(realFloatingPointType())),
varDecl(hasType(isInteger())))))))
.bind(id1);
finder.addMatcher(m0, this->callbacks[0].get());
finder.addMatcher(m1, this->callbacks[1].get());
}
VarUseSemantic
变量使用错误: 某个该用变量 a
的地方用成了同一类型的变量 b
. 随机挑选类型 T
变量 a
, 并替换为已声明的类型 T
变量 b
.
Replacetool类
定义了两个id:
const string desc =
"This bug is a semantic error where the variable <b> is mistakenly used in "
"place of the intended variable <a> of the same type";
const string id = "numberOperand";
const string id0 = "numberOperandGet";
内部还包含了一个将bo和sm单独封装成了一个类
class ReplaceVar {
public:
const DeclRefExpr *bo;
const SourceManager *sm;
ReplaceVar(const DeclRefExpr *bo, const SourceManager *sm) : bo(bo), sm(sm) {}
};
getRandomReplaceName方法
// 随机替换变量, 返回新变量名,引用更改需要替换的bo与sm
string getRandomReplaceName(const DeclRefExpr *decl, const DeclRefExpr *&changedDecl, const SourceManager *&changedSm) {
// 随机排序
shuffleVector(replaceList);
// 循环遍历找到一个满足条件的变量
for (auto rVar : replaceList) {
const NamedDecl *rNamedDecl = dyn_cast<NamedDecl>(rVar.bo->getDecl());
const NamedDecl *namedDecl = dyn_cast<NamedDecl>(decl->getDecl());
// 不同名且同类型
if (decl==nullptr || rNamedDecl->getNameAsString()==namedDecl->getNameAsString()) {
continue;
}
string rType = getDeclType(rVar.bo);
string declType = getDeclType(decl);
if (rType.empty() || declType.empty() || rType!=declType) {
continue;
}
#ifdef AMMON_DEBUG
INFO("**Print Type**");
string typeInfo = namedDecl->getNameAsString()+" "+declType+" "+rNamedDecl->getNameAsString()+" "+rType;
INFO(typeInfo);
#endif
// 两个变量进行比较,用作用域更大的变量替换另一个
if (isLargerScope(rNamedDecl, namedDecl)) {
// 用vector中的变量替换传入的变量
return rNamedDecl->getNameAsString();
} else if (isLargerScope(namedDecl, rNamedDecl)) {
// 用传入的变量替换vector中的变量
changedDecl = rVar.bo;
changedSm = rVar.sm;
return namedDecl->getNameAsString();
}
}
return "";
}
辅助函数:isLargerScope
// srcDecl的作用域是否更大
static bool isLargerScope(const NamedDecl *srcDecl, const NamedDecl *cmpDecl) {
const DeclContext *srcContext = srcDecl->getDeclContext();
const DeclContext *cmpContext = cmpDecl->getDeclContext();
while (cmpContext) {
if (cmpContext==srcContext) {
return true;
}
cmpContext = cmpContext->getParent();
}
return false;
}
辅助函数:getDeclType
// 获取变量类型
static string getDeclType(const DeclRefExpr *decl) {
const ValueDecl *valuedecl = decl->getDecl();
QualType qualType = valuedecl->getType();//c++中,变量和函数都是值声明的一种
// 跳过typedef
clang::Type const *underType = qualType.getTypePtr()->getUnqualifiedDesugaredType();
// 输出string
LangOptions lo;//语言选项
PrintingPolicy pp(lo);//打印策略
string s;
llvm::raw_string_ostream rso(s);//定义一个字符串和stream对象,用于将类型写入这个字符串
QualType::print(underType, Qualifiers(), rso, lo, llvm::Twine());//将类型信息打入rso中
return rso.str();//返回rso中的字符串
}
辅助函数outputvar
string outputVar() {
string declInfo;
for (auto &replaceVar : replaceList) {
const NamedDecl *namedDecl = dyn_cast<NamedDecl>(replaceVar.bo->getDecl());//转换为命名声明
declInfo += " " + namedDecl->getNameAsString();
}
return declInfo;
}
VarGetCallback
run方法
void run(const MatchFinder::MatchResult &result) override {
const DeclRefExpr *bo = result.Nodes.getNodeAs<DeclRefExpr>(id);
const SourceManager *sm = result.SourceManager;
if (bo && sm) {
replaceTool.putVar(bo, sm);
#ifdef AMMON_DEBUG
printInfo(result, "Get");
#endif
}
}
这个回调函数只是将匹配到的变量放到replaceList当中
VarUseCallback
真正进行替换
run方法
void run(const MatchFinder::MatchResult &result) override {
const DeclRefExpr *bo = result.Nodes.getNodeAs<DeclRefExpr>(id);
const SourceManager *sm = result.SourceManager;
if (bo && sm) {
#ifdef AMMON_DEBUG
printInfo(result, "replace");
#endif
const DeclRefExpr *changedDecl = bo;//替换的bo
const SourceManager *changedSm = sm;//替换的sm
const string newName = replaceTool.getRandomReplaceName(bo, changedDecl, changedSm);//调用获取新变量名
if (newName.empty()) {
return;
}
function<void(Rewriter &)> f = [changedDecl, changedSm, newName](Rewriter &r) {
const NamedDecl *changedNd = dyn_cast<NamedDecl>(changedDecl->getDecl());
const string oldName = changedNd->getNameAsString();
r.ReplaceText(changedDecl->getSourceRange(), newName);//替换掉
SourceLocation loc = changedDecl->getBeginLoc();
outs() << changedSm->getPresumedLineNumber(loc) << " "
<< changedSm->getPresumedColumnNumber(loc) << " " << oldName << " " << newName
<< "\n"
<< desc << "\n";
};
all.push_back(f);
}
}
构造函数
也是add两个
VarUseSemantic::VarUseSemantic(vector<function<void(Rewriter &)>> &all) {
this->callbacks.push_back(
unique_ptr<MatchFinder::MatchCallback>(new VarGetCallback(all)));
this->callbacks.push_back(
unique_ptr<MatchFinder::MatchCallback>(new VarUseCallback(all)));
// 所有有声明的变量使用
StatementMatcher m =
declRefExpr(isExpansionInMainFile(), hasDeclaration(varDecl(hasType(isInteger()))))
.bind(id);
finder.addMatcher(m, this->callbacks[0].get());
finder.addMatcher(m, this->callbacks[1].get());
}
main.cpp
SilentDiagConsumer类
用于屏蔽源程序中的诊断信息,即警告和错误信息,不让其输出到控制台或其他目标。
重写了HandleDiagnostic
方法,为空,也就是不做任何操作
// 用于将源程序本身的警告屏蔽掉
class SilentDiagConsumer : public clang::DiagnosticConsumer {
public:
void HandleDiagnostic(clang::DiagnosticsEngine::Level DiagLevel,
const clang::Diagnostic &Info) override {}
};
AmmonFrontendAction类
继承了ASTFrontendAction
类,用于对Clang前端进行操作
成员变量
private:
Rewriter rewriter;//rewriter
// 这是不知道 rewriter 里边有 editBuffer 迭代器之前的设计...
vector<function<void(Rewriter &)>> all;//存放函数的vector
int typeOption;
EndSourceFileAction方法
重写的方法,在处理完源文件之后被调用
// 处理完文件后, 进行的操作
void EndSourceFileAction() override {
auto mainFileId = rewriter.getSourceMgr().getMainFileID();//获取主文件的标识符,这个标识符用于识别源文件的位置
int num = all.size();//有多少个修改操作,也就是rewriter
if (num == 0) {
// 无效的 ASTConsumer
throw InvalidConsumerExp();
}
// 随机选择一个位置进行改写
int rand = randomNumber(0, num);
// 在这里将修改存储在 rewriter 缓冲区中
all[rand](rewriter);
// 将整个修改后的文件写入 llvm 输出
rewriter.getEditBuffer(mainFileId).write(outs());
}
CreateASTConsumer方法
也是重写的方法,就是创建ASTComsumer对象用的
unique_ptr<ASTConsumer> CreateASTConsumer(CompilerInstance &ci,
StringRef file) override {
// 屏蔽原来的错误和异常信息,用自己的SilentDiagConsumer
ci.getDiagnostics().setClient(new SilentDiagConsumer(), true);
rewriter.setSourceMgr(ci.getSourceManager(), ci.getLangOpts());
// 为每个文件创建一个 ASTConsumer,调用的是自己写的工厂类
return ASTConsumerFactory::getASTConsumer(all, typeOption);
}
};
AmmonFrontendActionFactory类
用于创建AmmonFrontedAction对象,貌似就是把他变成unique的?
class AmmonFrontendActionFactory : public FrontendActionFactory {
private:
int typeOption;
public:
AmmonFrontendActionFactory(int t) : typeOption(t) {}
unique_ptr<FrontendAction> create() override {
return make_unique<AmmonFrontendAction>(typeOption);
}
};
一个新的namespace
namespace {
// 命令行选项
cl::OptionCategory ammonCategory("Ammon options");
// -t <type>
// magic number?
int typeOptionInit = ALL_CONSUMER;
cl::opt<int> typeOption("t", cl::desc("Specify mutation type"),//t是描述信息,表示变异类型
cl::value_desc("type"), cl::Optional,//
cl::init(typeOptionInit), cl::cat(ammonCategory));
void runTool(ClangTool &tool) {
// 预留处理其它参数的空间
if (ASTConsumerFactory::isValidType(typeOption)) {//选项是否合法
try {
tool.run(newAmmon(typeOption).get());//运行
} catch (const InvalidConsumerExp &e) {
} catch (...) {
ERROR("Something Unknown Happened");
}
return;
}
// 默认是循环尝试所有 ASTConsumer, 直到有一个可以用
while (true) {
// 如果这次选择的 consumer 无效, 则多次选择
try {
tool.run(newAmmon(typeOption).get());
} catch (const InvalidConsumerExp &e) {
continue; // 继续循环就行了
} catch (const exception &e) {
ERROR(e.what());
} catch (...) {
ERROR("Something Unknown Happened");
}
break;
}
}
} // namespace
main函数
int main(int argc, const char **argv) {
cl::HideUnrelatedOptions(ammonCategory);//隐藏与 ammonCategory 不相关的命令行选项
Expected<CommonOptionsParser> expectedParser =
CommonOptionsParser::create(argc, argv, ammonCategory);//使用 CommonOptionsParser 类创建一个命令行选项解析器对象
if (!expectedParser) {
return 1;//创建失败直接返回
}
CommonOptionsParser &op = expectedParser.get();//获取解析器的引用
// 只考虑处理一个文件
ClangTool tool(op.getCompilations(), op.getSourcePathList());
outs() << "==== Start " << op.getSourcePathList()[0] << " ====\n";
runTool(tool);
outs() << "==== End ====\n";
return 0;
}