如何访问序列化的Clang抽象语法树(AST)?
基于Clang实现AST遍历工具的完整方案
我最近成功搭建了一套基于Clang的AST遍历工具,核心是通过ASTFrontendAction、ASTConsumer和RecursiveASTVisitor来遍历源文件的所有AST节点,还通过自定义ToolAction实现了每次AST处理前后的自定义逻辑,整个流程跑起来很顺畅,分享下具体实现:
1. 实现RecursiveASTVisitor遍历AST节点
首先定义一个继承自RecursiveASTVisitor的访问器类,重写你需要处理的节点类型对应的VisitXXX方法,比如处理所有声明和语句节点:
#include "clang/AST/RecursiveASTVisitor.h" class MyASTVisitor : public RecursiveASTVisitor<MyASTVisitor> { public: // 处理所有声明节点(变量、函数、类等) bool VisitDecl(Decl *decl) { // 这里可以添加你的自定义处理逻辑,比如打印节点信息 decl->dump(); return true; // 返回true继续遍历子节点 } // 处理所有语句节点(if、for、赋值等) bool VisitStmt(Stmt *stmt) { // 自定义语句处理逻辑 stmt->dump(); return true; } };
2. 创建ASTConsumer关联访问器
ASTConsumer是Clang用来处理AST的核心类,我们需要在它的HandleTranslationUnit方法中启动刚才定义的访问器:
#include "clang/AST/ASTConsumer.h" #include "clang/AST/ASTContext.h" class MyASTConsumer : public ASTConsumer { public: void HandleTranslationUnit(ASTContext &context) override { MyASTVisitor visitor; // 从翻译单元根节点开始遍历整个AST visitor.TraverseDecl(context.getTranslationUnitDecl()); } };
3. 实现ASTFrontendAction提供Consumer
ASTFrontendAction负责创建我们的ASTConsumer实例,是连接Clang前端和自定义AST处理逻辑的桥梁:
#include "clang/Frontend/ASTFrontendAction.h" #include "clang/Frontend/CompilerInstance.h" class MyFrontendAction : public ASTFrontendAction { public: std::unique_ptr<ASTConsumer> CreateASTConsumer(CompilerInstance &compiler, llvm::StringRef inFile) override { // 返回我们自定义的ASTConsumer return std::make_unique<MyASTConsumer>(); } };
4. 自定义ToolAction实现前后处理逻辑
如果需要在每次处理源文件的AST前后执行自定义逻辑(比如日志记录、资源清理),可以重写ToolAction的runInvocation方法:
#include "clang/Tooling/Tooling.h" #include "clang/Frontend/CompilerInvocation.h" #include "clang/Frontend/CompilerInstance.h" class MyToolAction : public clang::tooling::ToolAction { public: bool runInvocation(std::shared_ptr<clang::CompilerInvocation> invocation, clang::FileManager *files, std::shared_ptr<clang::PCHContainerOperations> pchOps, clang::DiagnosticConsumer *diagConsumer) override { // 处理前的自定义逻辑:比如打印当前处理的文件名 const auto& inputFile = invocation->getFrontendOpts().Inputs[0].getFile(); llvm::errs() << "* Starting processing file: " << inputFile << "\n"; // 初始化编译器实例并运行我们的FrontendAction clang::CompilerInstance compiler; compiler.setInvocation(std::move(invocation)); compiler.setFileManager(files); compiler.setPCHContainerOperations(std::move(pchOps)); compiler.createDiagnostics(diagConsumer, false); std::unique_ptr<clang::FrontendAction> action = std::make_unique<MyFrontendAction>(); bool success = compiler.ExecuteAction(*action); // 处理后的自定义逻辑:比如打印处理结果状态 llvm::errs() << "* Finished processing file: " << inputFile << ", success: " << (success ? "✅" : "❌") << "\n\n"; return success; } };
5. 主函数中启动ClangTool
最后在主函数中配置ClangTool,并传入我们自定义的ToolAction:
#include "clang/Tooling/CommonOptionsParser.h" #include "llvm/Support/CommandLine.h" static llvm::cl::OptionCategory MyToolCategory("My AST Traversal Tool Options"); int main(int argc, const char **argv) { // 解析命令行参数 clang::tooling::CommonOptionsParser optionsParser(argc, argv, MyToolCategory); clang::tooling::ClangTool tool(optionsParser.getCompilations(), optionsParser.getSourcePathList()); // 运行自定义ToolAction return tool.run(std::make_unique<MyToolAction>().get()); }
整个方案目前运行稳定,自定义的AST处理逻辑和前后置操作都能正常工作,适合用来做代码分析、静态检查这类场景。
内容的提问来源于stack exchange,提问作者JMLoy
相关产品推荐
相关产品推荐

