网站首页 > 技术文章 正文
Clang通过两种方式访问AST(抽象语法树)
- Visitor遍历模式
- Matcher匹配模式
Visitor遍历模式
初始化CompilerInstance之后,调用其成员函数ExcutionAction, ExcutionAction会间接依次调用FrontendAction的6个成员函数(直接调用的是FrontendAction的三个public 接口,BeginSourceFile,Execute,EndSourceFile),而FrontendAction的ExecuteAction会最终调用语法分析函数ParseAST(未强制要求ParseAST放入ExcuteAction,但ASTFrontendAction如此)。 ParseAST在分析过程中,又会调用ASTConsumer的多个函数(用得最多是HandleTopLevelDecl和 HandleTranslationUnit)。FrontendAction是文件级别的动作,ASTConsumer则与一个Translation Unit内部处理过程相关。RecursiveASTVisitor是针对AST node的遍历,一般需要ASTConsumer中呈现的AST node(如TranslationUnitDecl)作为参数。
以深度优先的方式遍历整个 Clang AST 并访问每个节点的一个类。其执行三种不同的操作:
- 遍历整个 AST(即访问每个节点)
- 对于给定的一个节点,沿着其类继承关系向前(Derived->Base方向)游历,直至到达一个顶层类(如 Stmt,Decl,Type)
- 对于一个给定的 (node, class) 组合,调用一个可由用户重写(user-overridable)的函数来访问该节点,其中 class 是 node 的动态类型的某个基类
这些操作由三组类方法来完成,分别是:
- TraverseDecl(Decl *x) 执行任务1,是遍历以x为根的 AST 的入口函数。该函数只是简单地将任务分派给 TraverseFoo(Foo *x),继而调用 WalkUpFromFoo(x),然后递归地访问x的子节点。TraverseFoo 中的 Foo 是 *x 的动态类型。TraverseDecl(Decl *x) 和 TraverseType(QualType x) 执行的操作类似
- WalkUpFromFoo(Foo *x) 执行任务2。该函数首先调用 WalkUpFromBar(x),然后调用 VisitFoo(x)。其中 Bar 是 Foo 的直接父类(direct parent)
- VisitFoo(Foo *x) 执行任务3
这三组方法具有下列层次关系:Traverse* > WalkUpFrom* > Visit*。某一层次的方法可以调用相同层次的另一个方法以及较低层次的方法,但不能调用层次比它高的方法。
Matcher匹配模式
ifStmt(hasCondition(expr().bind("C")), hasThen(stmt().bind("T")), hasElse(stmt().bind("E")))
- ifStmt是一个匹配器实例,用来判断匹配当前节点是否为 if 节点,并且满足括号内的其他匹配器要求。他是一个包含 operator() 方法的类的实例,但是你也可以将其简单地看作是一个函数。
- expr 和 stmt:与 ifStmt 类似,也是匹配器的实例,用于匹配 Expression 和 Statement。
- .bind(str):一些匹配器拥有的方法,它可以将被匹配的这个子表达式树绑定到 str 中。例如上文中,匹配成功后会将绑定到 C 上,我们后续可以通过 C 找到这个表达式。
- hasConditon、hasThen、hasElse:它是一个作用于特定节点类型的匹配器,它尝试去获得节点的子树。
在 Clang 中,AST Matcher 被分为了三类:
- 节点匹配器(Node Matchers):用于匹配特定类型的节点,是匹配器的核心。同时,Node Matchers 是唯一具有 bind 方法的一类匹配器。例如 ifStmt、expr、classTemplateDecl 等。
- 窄化匹配器(Narrowing Matchers):对当前节点上的某些属性进行匹配,从而缩小当前类型的节点集的匹配范围。例如 isPrivate、equals、isConst 等。此外,有一些逻辑匹配器(allOf、anyOf、anything、unless),他们能构成更强大的匹配器。
- 遍历匹配器(Traversal Matchers):遍历匹配器指定从当前节点可到达的其他节点的关系。例如 hasCondition、hasParent、hasLHS 等。此外,有一些强大的匹配器 (has、hasDecendant、forEach)可以构成更强大的匹配器。
每一个匹配表达式都以 Node Matchers 开始,然后使用 Narrowing 或 Traversal Matcher 进一步完善。所有遍历匹配器都将 Node Matcher 作为其参数。此外,每个 Node Matcher 都隐含了一个 allOf Matcher。
实现
#include "clang/AST/ASTConsumer.h"
#include "clang/AST/RecursiveASTVisitor.h"
#include "clang/Frontend/CompilerInstance.h"
#include "clang/Frontend/FrontendAction.h"
#include "clang/CodeGen/ObjectFilePCHContainerOperations.h"
#include "clang/Driver/Options.h"
#include "clang/Rewrite/Frontend/FixItRewriter.h"
#include "clang/Rewrite/Frontend/FrontendActions.h"
#include "clang/StaticAnalyzer/Frontend/FrontendActions.h"
#include "clang/Tooling/CommonOptionsParser.h"
#include "clang/Tooling/Syntax/BuildTree.h"
#include "clang/Tooling/Syntax/Tokens.h"
#include "clang/Tooling/Syntax/Tree.h"
#include "clang/Tooling/Tooling.h"
#include "clang/ASTMatchers/ASTMatchFinder.h"
#include "clang/ASTMatchers/ASTMatchers.h"
#include "llvm/ADT/STLExtras.h"
#include "llvm/Option/OptTable.h"
#include "llvm/Support/Path.h"
#include "llvm/Support/Signals.h"
#include "llvm/Support/TargetSelect.h"
using namespace clang;
using namespace clang::ast_matchers;
using namespace clang::driver;
using namespace clang::tooling;
using namespace llvm;
static cl::extrahelp CommonHelp(CommonOptionsParser::HelpMessage);
static cl::extrahelp MoreHelp(
"\tFor example, to run rewriter\n"
"\n");
static cl::OptionCategory ClangCheckCategory("rewriter options");
static const opt::OptTable &Options = getDriverOptTable();
static cl::opt<bool>
VISITORDump("visitor",
cl::desc(Options.getOptionHelpText(options::OPT_ast_dump)),
cl::cat(ClangCheckCategory));
// Visitor 遍历模式
class ReWriteClassVisitor
: public RecursiveASTVisitor<ReWriteClassVisitor> {
public:
explicit ReWriteClassVisitor(ASTContext *context, SourceManager *sm, Rewriter *rw)
: context_(context), sm_(sm), rewriter_(rw) {}
bool VisitStmt(Stmt *s) {
// 当前文件,排除包含文件
if (sm_->isInMainFile(s->getBeginLoc()) && isa<IfStmt>(s)) {
IfStmt *IfStatement = cast<IfStmt>(s);
Stmt *Then = IfStatement->getThen();
rewriter_->InsertText(Then->getBeginLoc(), "// visitor, the 'if' part\n", true, true);
Stmt *Else = IfStatement->getElse();
if (Else) {
rewriter_->InsertText(Else->getBeginLoc(), "// visitor, the 'else' part\n", true, true);
}
}
return true;
}
private:
ASTContext *context_;
SourceManager *sm_;
std::string caller_;
Rewriter *rewriter_;
};
// Matcher 匹配模式
class IfStmtHandler : public MatchFinder::MatchCallback {
public:
IfStmtHandler(Rewriter &Rewrite) : Rewrite(Rewrite) {}
virtual void run(const MatchFinder::MatchResult &Result) {
if (const IfStmt *IfS = Result.Nodes.getNodeAs<clang::IfStmt>("ifStmt")) {
const Stmt *Then = IfS->getThen();
Rewrite.InsertText(Then->getBeginLoc(), "// matcher, the 'if' part\n", true, true);
if (const Stmt *Else = IfS->getElse()) {
Rewrite.InsertText(Else->getBeginLoc(), "// matcher the 'else' part\n", true, true);
}
}
}
private:
Rewriter &Rewrite;
};
class ReWriteClassConsumer : public clang::ASTConsumer {
public:
explicit ReWriteClassConsumer(ASTContext *context, SourceManager *sm,
Rewriter *rw)
: visitor_(context, sm, rw), sm_(sm), rewriter_(rw), handlerif_(*rw) {}
~ReWriteClassConsumer() {
llvm::outs() << "I have finished rewrite." << "\n";
}
virtual void Initialize(ASTContext &Context) override {
context_ = &Context;
matcher_.addMatcher(ifStmt().bind("ifStmt"), &handlerif_);
}
virtual void HandleTranslationUnit(clang::ASTContext &context) override {
if (VISITORDump) {
// 使用遍历模式
visitor_.TraverseDecl(context.getTranslationUnitDecl());
} else {
// 使用匹配模式
matcher_.matchAST(context);
}
}
private:
ReWriteClassVisitor visitor_;
ASTContext *context_;
SourceManager *sm_;
Rewriter *rewriter_;
IfStmtHandler handlerif_;
MatchFinder matcher_;
};
class ReWriteClassAction : public clang::ASTFrontendAction {
public:
void EndSourceFileAction() override {
//rewriter_.getEditBuffer(rewriter_.getSourceMgr().getMainFileID()) .write(llvm::outs());
rewriter_.overwriteChangedFiles();
}
virtual std::unique_ptr<clang::ASTConsumer>
CreateASTConsumer(clang::CompilerInstance &compiler, llvm::StringRef in_file) {
// 屏蔽错误信息输出
compiler.getDiagnostics().setClient(new IgnoringDiagConsumer()); // 相当于-w
rewriter_.setSourceMgr(compiler.getSourceManager(), compiler.getLangOpts());
return std::make_unique<ReWriteClassConsumer>(
&compiler.getASTContext(), &compiler.getSourceManager(), &rewriter_);
}
private:
Rewriter rewriter_;
};
//命令行参数: ast.cpp -- 屏蔽编译数据库找不到
int main(int argc, const char **argv) {
llvm::sys::PrintStackTraceOnErrorSignal(argv[0]);
// Initialize targets for clang module support.
llvm::InitializeAllTargets();
llvm::InitializeAllTargetMCs();
llvm::InitializeAllAsmPrinters();
llvm::InitializeAllAsmParsers();
auto ExpectedParser =
CommonOptionsParser::create(argc, argv, ClangCheckCategory);
if (!ExpectedParser) {
llvm::errs() << ExpectedParser.takeError();
return 1;
}
CommonOptionsParser &OptionsParser = ExpectedParser.get();
ClangTool Tool(OptionsParser.getCompilations(),
OptionsParser.getSourcePathList());
std::unique_ptr<FrontendActionFactory> FrontendFactory;
FrontendFactory = newFrontendActionFactory<ReWriteClassAction>();
return Tool.run(FrontendFactory.get());
}
验证
文件:ast.cpp
//for linux,__cdecl
// g++ -g test_objetc_member_function_linux.cpp -std=c++11 -I../src -o test_objetc_member_function_linux
#include<iostream>
#include "stub.h"
using namespace std;
template<typename T>
void swap(T &a, T &b){
T temp = a;
a = b;
b = temp;
}
class A{
int i;
public:
int foo(int a){
if(a > 1)
cout<<"I am A_foo:"<< a <<endl;
else
cout<<"I am A_foo > 1:"<< a <<endl;
return 0;
}
static int bar(int b){
cout<<"I am A_bar:"<< b <<endl;
return 0;
}
};
int foo_stub(void* obj, int a)
{
A* o= (A*)obj;
cout<<"I am foo_stub"<<endl;
return 0;
}
int main()
{
Stub stub;
stub.set(ADDR(A,foo), foo_stub);
A a;
a.foo(1);
int aa = 1, bb=2;
swap(aa, bb);
cout<<aa<<bb<<endl;
return 0;
}
执行:rewriter -visitor ast.cpp --
结果:
再执行:rewriter ast.cpp --
结果:
- 上一篇: C++面向对象(5)
- 下一篇: 朝文分享(49):深入C++(十六)——运算符重载
猜你喜欢
- 2025-01-20 C++|类型转换与运行时类型安全检查
- 2025-01-20 C++Qt开发——事件处理函数
- 2025-01-20 百度Linux C++后台开发面试题(个人整理)
- 2025-01-20 怎样才算学会了C++基础,一篇文章学习了解(包含Qt内容)
- 2025-01-20 C++通过aidl与Android系统服务通信(一)
- 2025-01-20 学习阅读C++编译器错误:函数声明中的荒谬错误
- 2025-01-20 朝文分享(54):深入C++(二十一)——多态
- 2025-01-20 c++多态
- 2025-01-20 深入探讨C++多线程性能优化
- 2025-01-20 【C++】C++ 11 新特性:使用示例
- 02-21走进git时代, 你该怎么玩?_gits
- 02-21GitHub是什么?它可不仅仅是云中的Git版本控制器
- 02-21Git常用操作总结_git基本用法
- 02-21为什么互联网巨头使用Git而放弃SVN?(含核心命令与原理)
- 02-21Git 高级用法,喜欢就拿去用_git基本用法
- 02-21Git常用命令和Git团队使用规范指南
- 02-21总结几个常用的Git命令的使用方法
- 02-21Git工作原理和常用指令_git原理详解
- 最近发表
- 标签列表
-
- cmd/c (57)
- c++中::是什么意思 (57)
- sqlset (59)
- ps可以打开pdf格式吗 (58)
- phprequire_once (61)
- localstorage.removeitem (74)
- routermode (59)
- vector线程安全吗 (70)
- & (66)
- java (73)
- org.redisson (64)
- log.warn (60)
- cannotinstantiatethetype (62)
- js数组插入 (83)
- resttemplateokhttp (59)
- gormwherein (64)
- linux删除一个文件夹 (65)
- mac安装java (72)
- reader.onload (61)
- outofmemoryerror是什么意思 (64)
- flask文件上传 (63)
- eacces (67)
- 查看mysql是否启动 (70)
- java是值传递还是引用传递 (58)
- 无效的列索引 (74)