Loading

编写基于 ASTFrontendActions 的 RecursiveASTVisitor

参考资料

https://clang.llvm.org/docs/RAVFrontendAction.html

llvm-project源代码

https://github.com/llvm/llvm-project

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;

让我们从编写一个RecursiveASTVisitor开始,它访问所有的CXXRecordDecl

RecursiveASTVisitor的方法中,我们现在可以使用Clang 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;
}

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、总代码

现在我们可以把以上所有的代码结合成一个小的示例程序:

6.1、命令行版本

#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]);
  }
}

6.2、cpp文件类别

#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) {
  auto ExpectedParser = tooling::CommonOptionsParser::create(argc, argv, MyToolCategory);
  if (!ExpectedParser) {
    llvm::errs() << ExpectedParser.takeError();
    return 1;
  }
  clang::tooling::CommonOptionsParser &OptionsParser = ExpectedParser.get();
  std::vector<clang::tooling::CompileCommand> CompileCommandsForProject =
      OptionsParser.getCompilations().getAllCompileCommands();
  clang::tooling::ClangTool Tool(OptionsParser.getCompilations(),
                                 OptionsParser.getSourcePathList());
  Tool.run(
      clang::tooling::newFrontendActionFactory<FindNamedClassAction>().get());
}

7、运行

7.1、clang-extra-tool下操作

在llvm-git文件夹下的clang-extra-tool文件夹下,新建文件夹FindClassDecls并在clang-extra-tool文件夹下的CmakeList.txt中添加

add_subderectory(FindClassDecls)

7.2、在FindClassDecls文件夹下操纵

创建一个FindClassDecls.cpp的文件,将上述代码放在这个文件中,

在FindClassDecls文件夹下创建CMakeLists.txt

add_clang_executable(find-class-decls FindClassDecls.cpp)
target_link_libraries(find-class-decls clangTooling)

7.3、编译llvm

在llvm-git文件夹下创建build文件夹。

进入build文件夹。

cmake -DLLVM_ENABLE_PROJECTS="clang;clang-tools-extra" -DCMAKE_BUILD_TYPE=RelWithDebInfo -G "Ninja" ../llvm
cmake --build /media/data/clang-llvm/llvm-git/build --target find-class-decls

7.4、命令行版本运行该代码

当在一个小代码片段上运行这个工具时,它将输出一个类n:Ⓜ️:C它找到的所有声明:

$ ./bin/find-class-decls "namespace n { namespace m { class C {}; } }"

Found declaration at 1:29

可以知道下述代码的C是CXXRecordDecl,并且被程序发现。

namespace n { namespace m { class C {}; } }

7.5、cpp文件类别运行该文件

在根目录/C下创建test.cpp

namespace n { namespace m { class C {}; } }

在/C下创建build文件夹,并且在/C/build下生成compile_commands.json

生成方法详见https://www.cnblogs.com/xine/p/16689250.html

运行程序

$ ./bin/find-class-decls -p /C/build test.cpp

Found declaration at 1:29

结果是一样的。

posted @ 2022-09-13 19:54  xine  阅读(638)  评论(0编辑  收藏  举报