如何编写基于 RecursiveASTVisitor 的 ASTFrontendActions。¶
介绍¶
在本教程中,您将学习如何创建一个使用 RecursiveASTVisitor 查找具有指定名称的 CXXRecordDecl AST 节点的 FrontendAction。
创建 FrontendAction¶
在编写基于 clang 的工具(如 Clang 插件或基于 LibTooling 的独立工具)时,常见的入口点是 FrontendAction。FrontendAction 是一个接口,允许在编译过程中执行用户特定的操作。为了在 AST 上运行工具,clang 提供了便利的接口 ASTFrontendAction,它负责执行操作。唯一剩下的部分是实现 CreateASTConsumer 方法,该方法为每个翻译单元返回一个 ASTConsumer。
class FindNamedClassAction : public clang::ASTFrontendAction {
public:
virtual std::unique_ptr<clang::ASTConsumer> CreateASTConsumer(
clang::CompilerInstance &Compiler, llvm::StringRef InFile) {
return std::make_unique<FindNamedClassConsumer>();
}
};
创建 ASTConsumer¶
ASTConsumer 是一个用于在 AST 上编写通用操作的接口,无论 AST 是如何产生的。ASTConsumer 提供了许多不同的入口点,但对于我们的用例,唯一需要的是 HandleTranslationUnit,它将与翻译单元的 ASTContext 一起调用。
class FindNamedClassConsumer : public clang::ASTConsumer {
public:
virtual void HandleTranslationUnit(clang::ASTContext &Context) {
// Traversing the translation unit decl via a RecursiveASTVisitor
// will visit all nodes in the AST.
Visitor.TraverseDecl(Context.getTranslationUnitDecl());
}
private:
// A RecursiveASTVisitor implementation.
FindNamedClassVisitor Visitor;
};
使用 RecursiveASTVisitor¶
现在一切都已经连接起来,下一步是实现一个 RecursiveASTVisitor 从 AST 中提取相关信息。
RecursiveASTVisitor 为大多数 AST 节点提供名为 bool VisitNodeType(NodeType *) 的钩子;例外是 TypeLoc 节点,它们按值传递。我们只需要为相关的节点类型实现方法。
让我们从编写一个访问所有 CXXRecordDecl 的 RecursiveASTVisitor 开始。
class FindNamedClassVisitor
: public RecursiveASTVisitor<FindNamedClassVisitor> {
public:
bool VisitCXXRecordDecl(CXXRecordDecl *Declaration) {
// For debugging, dumping the AST nodes will show which nodes are already
// being visited.
Declaration->dump();
// The return value indicates whether we want the visitation to proceed.
// Return false to stop the traversal of the AST.
return true;
}
};
在我们的 RecursiveASTVisitor 的方法中,我们现在可以使用 Clang AST 的全部功能来深入到对我们来说有趣的部分。例如,要查找具有特定名称的所有类声明,我们可以检查特定的限定名称
bool VisitCXXRecordDecl(CXXRecordDecl *Declaration) {
if (Declaration->getQualifiedNameAsString() == "n::m::C")
Declaration->dump();
return true;
}
访问 SourceManager 和 ASTContext¶
有关 AST 的一些信息(如源位置和全局标识符信息)没有存储在 AST 节点本身中,而是存储在 ASTContext 及其关联的源管理器中。要检索它们,我们需要将 ASTContext 传递到我们的 RecursiveASTVisitor 实现中。
ASTContext 可从调用 CreateASTConsumer 时的 CompilerInstance 获得。因此,我们可以将其提取出来并将其传递到我们新创建的 FindNamedClassConsumer 中
virtual std::unique_ptr<clang::ASTConsumer> CreateASTConsumer(
clang::CompilerInstance &Compiler, llvm::StringRef InFile) {
return std::make_unique<FindNamedClassConsumer>(&Compiler.getASTContext());
}
现在 ASTContext 在 RecursiveASTVisitor 中可用,我们可以对 AST 节点进行更有趣的操作,例如查找它们的源位置
bool VisitCXXRecordDecl(CXXRecordDecl *Declaration) {
if (Declaration->getQualifiedNameAsString() == "n::m::C") {
// getFullLoc uses the ASTContext's SourceManager to resolve the source
// location and break it up into its line and column parts.
FullSourceLoc FullLocation = Context->getFullLoc(Declaration->getBeginLoc());
if (FullLocation.isValid())
llvm::outs() << "Found declaration at "
<< FullLocation.getSpellingLineNumber() << ":"
<< FullLocation.getSpellingColumnNumber() << "\n";
}
return true;
}
将所有内容放在一起¶
现在我们可以将所有上述内容组合成一个小型示例程序
#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::make_unique<FindNamedClassConsumer>(&Compiler.getASTContext());
}
};
int main(int argc, char **argv) {
if (argc > 1) {
clang::tooling::runToolOnCode(std::make_unique<FindNamedClassAction>(), argv[1]);
}
}
我们将它存储在一个名为 FindClassDecls.cpp 的文件中,并创建以下 CMakeLists.txt 来链接它
set(LLVM_LINK_COMPONENTS
Support
)
add_clang_executable(find-class-decls FindClassDecls.cpp)
target_link_libraries(find-class-decls
PRIVATE
clangAST
clangBasic
clangFrontend
clangSerialization
clangTooling
)
当在这个小代码段上运行此工具时,它将输出它找到的类 n::m::C 的所有声明
$ ./bin/find-class-decls "namespace n { namespace m { class C {}; } }"
Found declaration at 1:29