Clang RecursiveASTVisitor & ASTFrontendActions based on it

RecursiveASTVisitor Basics

类声明

template<typename Derived>
class clang::RecursiveASTVisitor<Derived>; 

以前序或后序深度优先的方式遍历整个 Clang AST 并访问每个节点的一个类。其执行三种不同的操作:

  1. 遍历整个 AST(即访问每个节点)
  2. 对于给定的一个节点,沿着其类继承关系向前(Derived->Base方向)游历,直至到达一个顶层类(如 Stmt,Decl,Type)
  3. 对于一个给定的 (node, class) 组合,调用一个可由用户重写(user-overridable)的函数来访问该节点,其中 class 是 node 的动态类型的某个基类

这些操作由三组类方法来完成,分别是:

  1. TraverseDecl(Decl *x) 执行任务1,是遍历以x为根的 AST 的入口函数。该函数只是简单地将任务分派(dispatchs, i.e. forwards)给 TraverseFoo(Foo *x),继而调用 WalkUpFromFoo(x),然后递归地访问x的子节点。TraverseFoo 中的 Foo 是 *x 的动态类型。TraverseDecl(Decl *x) 和 TraverseType(QualType x) 执行的操作类似
  2.  WalkUpFromFoo(Foo *x) 执行任务2。该函数首先调用 WalkUpFromBar(x),然后调用 VisitFoo(x)。其中 Bar 是 Foo 的直接父类(direct parent)
  3. VisitFoo(Foo *x) 执行任务3 

这三组方法具有下列层次关系:Traverse* > WalkUpFrom* > Visit*。某一层次的方法可以调用相同层次的另一个方法以及较低层次的方法,但不能调用层次比它高的方法。

由于 WalkUpFromFoo() 在调用 VisitFoo() 之前先调用 WalkUpFromBar(Bar是Foo的超类),因此最终结果是对于一个给定的节点将以自顶向下的顺序依次调用其 Visit*() 方法(如,对于 NamespaceDecl 类型的节点,调用顺序依次为 VisitDecl(),VisitNamedDecl(),VisitNamespaceDecl())。这种机制保证相同类型 AST 节点的 Visit*() 方法调用被组合在一起,而不会与不同类型节点的 Visit*() 方法搅和在一起。

要使用该 visitor,首先要进行子类化(将其自身作为模板参数,采用奇异递归模板模式(curiously recurring template pattern)),然后为声明、类型、语句、表达式以及其他所有需要自定义行为的 AST 节点重写(override)Traverse*、WalkUpFrom* 和 Visit* 方法。大多数用户只需要重写 Visit* 方法即可,也可以重写前两种方法以实现更加高级的操作。在遍历的过程中,如果这些重写函数中的任意一个返回了 false,则整个遍历过程将终止。

默认情况下该 visitor 尝试访问显式源代码(explicit source code)的每一部分而且只访问一次。针对模板的讨论详参原文档

默认情况下该 visitor 以前序的形式遍历AST,如果需要后序遍历,需要重写 shouldTraversePostOrder 方法并返回 true。

源码简析

TraverseDecl 方法

 1 template <typename Derived>
 2 bool RecursiveASTVisitor<Derived>::TraverseDecl(Decl *D) {
 3   if (!D)
 4     return true;
 5 
 6   // As a syntax visitor, by default we want to ignore declarations for
 7   // implicit declarations (ones not typed explicitly by the user).
 8   if (!getDerived().shouldVisitImplicitCode() && D->isImplicit())
 9     return true;
10 
11   switch (D->getKind()) {
12 #define ABSTRACT_DECL(DECL)
13 #define DECL(CLASS, BASE)                                                      \
14   case Decl::CLASS:                                                            \
15     if (!getDerived().Traverse##CLASS##Decl(static_cast<CLASS##Decl *>(D)))    \
16       return false;                                                            \
17     break;
18 #include "clang/AST/DeclNodes.inc"
19   }
20 
21   // Visit any attributes attached to this declaration.
22   for (auto *I : D->attrs()) {
23     if (!getDerived().TraverseAttr(I))
24       return false;
25   }
26   return true;
27 }

 行11到行19便是整个分派过程。getDerived() 方法返回派生子类的引用,

基于 RecursiveASTVisitor 的 ASTFrontendAction

 关于 FrontendAction 的知识见另一篇 blog:Clang FrontendActions。此处给出的例子只是简单地打印出程序中所有的 witch-case 语句的两部分信息:switch 条件表达式和 case 语句。

ClangTool 的 run 方法接受一个 FrontendAction (wrapped with newFrontendActionFactory),因此首先创建一个 FrontendAction 类:

1 class SwitchAction : public ASTFrontendAction {
2 public:
3   virtual std::unique_ptr<ASTConsumer> CreateASTConsumer(CompilerInstance &CI,
4                                                          StringRef InFile) {
5     return std::unique_ptr<ASTConsumer>(
6         new SwitchConsumer(&CI.getASTContext()));
7   }
8 };

编译器在对源文件进行 parsing 时执行传进来的 FrontendAction(Act),在 Act 的准备阶段调用 CreateASTConsumer 方法,在生成完 AST 后,会调用 ASTConsumer 的 HandleTranslationUnit(ASTContext &Ctx) 方法。下面创建一个 ASTConsumer 类:

 1 class SwitchConsumer : public ASTConsumer {
 2 public:
 3   explicit SwitchConsumer(ASTContext *Context) : Visitor(Context) {}
 4   virtual void HandleTranslationUnit(ASTContext &Context) {
 5     Visitor.TraverseDecl(Context.getTranslationUnitDecl());
 6   }
 7 
 8 private:
 9   SwitchVisitor Visitor;
10 };

在 ASTConsumer 的 HandleTranslationUnit 中调用 Visitor 的 TraverseDecl 方法来遍历 AST。此时 AST 已经创建完毕,我们将其根 TranslationUnitDecl 传递给 TraverseDecl 方法。

Refenences:

原文地址:https://www.cnblogs.com/jerrywossion/p/clang_recursiveastvisitor.html