如何编写基于 ASTFrontendActions 的 RecursiveASTVisitor
本文为译文,点击 此处查看原文。
1. 介绍
在本教程中,您将学习如何使用一个RecursiveASTVisitor
创建一个FrontendAction
,以查找具有指定名称的CXXRecordDecl AST
节点。
2. 创建一个 FrontendAction
当编写基于clang
的工具(比如Clang Plugin
)或基于LibTooling
的独立工具时,通常的入口点是FrontendAction
。FrontendAction
是一个接口,它允许用户指定的actions
作为编译的一部分来执行。为了在 AST clang 上运行工具,AST clang 提供了方便的接口ASTFrontendAction
,它负责执行action
。剩下的唯一部分是实现CreateASTConsumer
方法,该方法为每个翻译单元返回一个ASTConsumer
。
class FindNamedClassAction : public clang::ASTFrontendAction {
public:
virtual std::unique_ptr<clang::ASTConsumer> CreateASTConsumer(
clang::CompilerInstance &Compiler, llvm::StringRef InFile) {
return std::unique_ptr<clang::ASTConsumer>(
new FindNamedClassConsumer);
}
};
3. 创建一个 ASTConsumer
ASTConsumer
是一个用于在一个 AST 上编写通用 actions 的接口,而不管 AST 是如何生成的。ASTConsumer
提供了许多不同的入口点,但是对于我们的用例来说,唯一需要的入口点是HandleTranslationUnit
,它是用ASTContext
为翻译单元调用的。
class FindNamedClassConsumer : public clang::ASTConsumer {
public:
virtual void HandleTranslationUnit(clang::ASTContext &Context) {
// 通过一个 RecursiveASTVisitor 遍历 翻译单元decl(TraverseDecl) 将访问AST中的所有节点。
Visitor.TraverseDecl(Context.getTranslationUnitDecl());
}
private:
// A RecursiveASTVisitor implementation.
FindNamedClassVisitor Visitor;
};
4. 使用 RecursiveASTVisitor
现在一切都连接好了,下一步是实现一个RecursiveASTVisitor
来从AST
中提取相关信息。
RecursiveASTVisitor
为大多数 AST 节点提供bool VisitNodeType(NodeType *)
形式的 hooks;异常是按值传递的TypeLoc
节点。我们只需要实现相关节点类型的方法。
让我们从编写一个RecursiveASTVisitor
开始,它访问所有的CXXRecordDecl
。
class FindNamedClassVisitor
: public RecursiveASTVisitor<FindNamedClassVisitor> {
public:
bool VisitCXXRecordDecl(CXXRecordDecl *Declaration) {
// 对于调试,转储 AST 节点将显示哪些节点已经被访问。
Declaration->dump();
// 返回值指示是否希望继续进行访问。
// 返回false以停止对 AST 的遍历。
return true;
}
};
在RecursiveASTVisitor
的方法中,我们现在可以使用Clang AST
的全部功能来钻取我们感兴趣的部分。例如,要查找具有一个特定名称的所有类声明,我们可以检查一个特定的限定名:
bool VisitCXXRecordDecl(CXXRecordDecl *Declaration) {
if (Declaration->getQualifiedNameAsString() == "n::m::C")
Declaration->dump();
return true;
}
5. 访问 SourceManager 和 ASTContext
关于 AST 的一些信息,如源文件位置
和全局标识符信息
,并不存储在 AST 节点本身,而是存储在ASTContext
及其相关的源文件管理器中。要检索它们,我们需要将ASTContext
提交到RecursiveASTVisitor
实现中。
在调用CreateASTConsumer
期间,可以从CompilerInstance
获得ASTContext
。因此,我们可以提取它,并将其提交到我们新创建的FindNamedClassConsumer
:
virtual std::unique_ptr<clang::ASTConsumer> CreateASTConsumer(
clang::CompilerInstance &Compiler, llvm::StringRef InFile) {
return std::unique_ptr<clang::ASTConsumer>(
new FindNamedClassConsumer(&Compiler.getASTContext()));
}
既然ASTContext
在RecursiveASTVisitor
中可用,我们就可以对 AST 节点做更有趣的事情,比如查找它们的源文件位置:
bool VisitCXXRecordDecl(CXXRecordDecl *Declaration) {
if (Declaration->getQualifiedNameAsString() == "n::m::C") {
// getFullLoc使用ASTContext的SourceManager解析源文件位置,并将其分解为行和列部分。
FullSourceLoc FullLocation = Context->getFullLoc(Declaration->getBeginLoc());
if (FullLocation.isValid())
llvm::outs() << "Found declaration at "
<< FullLocation.getSpellingLineNumber() << ":"
<< FullLocation.getSpellingColumnNumber() << "\n";
}
return true;
}
6. 把它们放在一起
现在我们可以把以上所有的代码结合成一个小的示例程序:
#include "clang/AST/ASTConsumer.h"
#include "clang/AST/RecursiveASTVisitor.h"
#include "clang/Frontend/CompilerInstance.h"
#include "clang/Frontend/FrontendAction.h"
#include "clang/Tooling/Tooling.h"
using namespace clang;
class FindNamedClassVisitor
: public RecursiveASTVisitor<FindNamedClassVisitor> {
public:
explicit FindNamedClassVisitor(ASTContext *Context)
: Context(Context) {}
bool VisitCXXRecordDecl(CXXRecordDecl *Declaration) {
if (Declaration->getQualifiedNameAsString() == "n::m::C") {
FullSourceLoc FullLocation = Context->getFullLoc(Declaration->getBeginLoc());
if (FullLocation.isValid())
llvm::outs() << "Found declaration at "
<< FullLocation.getSpellingLineNumber() << ":"
<< FullLocation.getSpellingColumnNumber() << "\n";
}
return true;
}
private:
ASTContext *Context;
};
class FindNamedClassConsumer : public clang::ASTConsumer {
public:
explicit FindNamedClassConsumer(ASTContext *Context)
: Visitor(Context) {}
virtual void HandleTranslationUnit(clang::ASTContext &Context) {
Visitor.TraverseDecl(Context.getTranslationUnitDecl());
}
private:
FindNamedClassVisitor Visitor;
};
class FindNamedClassAction : public clang::ASTFrontendAction {
public:
virtual std::unique_ptr<clang::ASTConsumer> CreateASTConsumer(
clang::CompilerInstance &Compiler, llvm::StringRef InFile) {
return std::unique_ptr<clang::ASTConsumer>(
new FindNamedClassConsumer(&Compiler.getASTContext()));
}
};
int main(int argc, char **argv) {
if (argc > 1) {
clang::tooling::runToolOnCode(new FindNamedClassAction, argv[1]);
}
}
我们将它存储到一个名为FindClassDecls.cpp
的文件中,并创建以下CMakeLists.txt
来链接它:
add_clang_executable(find-class-decls FindClassDecls.cpp)
target_link_libraries(find-class-decls clangTooling)
当在一个小代码片段上运行这个工具时,它将输出一个类n::m::C
它找到的所有声明:
$ ./bin/find-class-decls "namespace n { namespace m { class C {}; } }"
Found declaration at 1:29