首页 > 其他分享 >如何编写基于RecursiveASTVisitor的ASTFrontendAction

如何编写基于RecursiveASTVisitor的ASTFrontendAction

时间:2024-07-01 09:29:41浏览次数:23  
标签:AST ASTFrontendAction clang ASTContext public Declaration 编写 RecursiveASTVisitor

介绍

我现在工作中要写个fuzz引擎,语法分析部分用到了clang的接口,打算写一些博客记录绍下,ast(抽象语法树),libtooling接口的使用等等,文章主要是翻译英文文档

在本教程中,您将学习如何创建一个 FrontendAction,使用 RecursiveASTVisitor 查找具有指定名称的 CXXRecordDecl AST 节点。

创建 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 节点除外,它是按值传递的。我们只需实现相关节点类型的方法即可。

让我们先编写一个 RecursiveASTVisitor 来访问所有 CXXRecordDecl。

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;
}

访问源管理器和 ASTContext

AST 的某些信息(如源位置和全局标识符信息)并不存储在 AST 节点本身,而是存储在 ASTContext 及其关联的源管理器中。要检索这些信息,我们需要将 ASTContext 交给我们的 RecursiveASTVisitor 实现。

在调用 CreateASTConsumer 时,编译器实例会提供 ASTContext。因此,我们可以从这里提取 ASTContext,并将其交给我们新创建的 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

接下了我会介绍LibASTMatchers的使用和如何编译llvm

标签:AST,ASTFrontendAction,clang,ASTContext,public,Declaration,编写,RecursiveASTVisitor
From: https://blog.csdn.net/weixin_43837016/article/details/140092107

相关文章