使用Clang作为库 —— 如何编写基于 ASTFrontendActions 的 RecursiveASTVisitor


本文为译文,点击 此处查看原文。

1. 介绍

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

2. 创建一个 FrontendAction

当编写基于clang的工具(比如Clang Plugin)或基于LibTooling的独立工具时,通常的入口点是FrontendActionFrontendAction是一个接口,它允许用户指定的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()));
}

既然ASTContextRecursiveASTVisitor中可用,我们就可以对 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
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值