匹配 Clang AST

本文档介绍如何使用 Clang 的 LibASTMatchers 来匹配 AST 中有趣的节点,并执行使用匹配节点的代码。与 LibTooling 结合使用,LibASTMatchers 有助于编写代码到代码转换工具或查询工具。

我们假设您对 Clang AST 有基本了解。请参阅 Clang AST 简介,如果您想了解更多关于 AST 结构的信息。

介绍

LibASTMatchers 提供了一种特定于领域的语言来创建对 Clang AST 的谓词。这种 DSL 是用 C++ 编写的,也可以从 C++ 中使用,允许用户编写单个程序来匹配 AST 节点并访问节点的 C++ 接口以提取属性、源位置或 AST 级别上提供的任何其他信息。

AST 匹配器是对 AST 中节点的谓词。匹配器是通过调用创建函数创建的,这些函数允许构建匹配器的树,其中内部匹配器用于使匹配更具体。

例如,要创建一个匹配翻译单元 AST 中所有类或联合声明的匹配器,您可以调用 recordDecl()。为了缩小匹配范围,例如找到所有名为“Foo”的类或联合声明,插入一个 hasName 匹配器:调用 recordDecl(hasName("Foo")) 返回一个匹配器,它匹配任何命名空间中名为“Foo”的类或联合。默认情况下,接受多个内部匹配器的匹配器使用隐式 allOf()。这允许进一步缩小匹配范围,例如匹配从“Bar”派生的所有类:recordDecl(hasName("Foo"), isDerivedFrom("Bar"))

如何创建匹配器

Clang AST 中有超过一千个类,当试图弄清楚如何为特定模式创建匹配器时,人们很容易迷失方向。本节将教你如何使用严格的分步模式来构建你感兴趣的匹配器。请注意,总会有一些匹配器缺少 AST 的某些部分。请参阅本文档后面有关 如何编写自己的 AST 匹配器 的部分。

使用匹配器的先决条件是了解您要匹配的内容的 AST 的结构。Clang AST 简介 教你如何将翻译单元的 AST 转换为人类可读的格式。

通常,创建正确匹配器的策略是

  1. 找到您想匹配的 Clang AST 中最外层的类。

  2. 查看 AST 匹配器参考,了解匹配您感兴趣的节点或缩小节点属性的匹配器。

  3. 创建您的外部匹配表达式。验证它是否按预期工作。

  4. 检查您要匹配的下一个内部节点的匹配器。

  5. 重复此过程,直到匹配器完成。

在匹配表达式中绑定节点

匹配表达式允许您指定 AST 的哪些部分对特定任务很重要。通常,您会希望随后对匹配的节点做一些事情,例如构建源代码转换。

为此,匹配特定 AST 节点的匹配器(称为节点匹配器)是可绑定的;例如,recordDecl(hasName("MyClass")).bind("id") 将匹配的 recordDecl 节点绑定到字符串“id”,以便稍后在 匹配回调 中检索。

编写自己的匹配器

有多种不同的方法来定义匹配器,具体取决于它的类型和灵活性。

VariadicDynCastAllOfMatcher<Base, Derived>

这些匹配器匹配所有类型为 Base 的节点,如果它们可以动态转换为 Derived。这些匹配器的名称是名词,与 Derived 非常相似。VariadicDynCastAllOfMatchers 是匹配器层次结构的支柱。大多数情况下,您的匹配表达式将从其中一个表达式开始,您可以 绑定 它们所代表的节点到 ID 以供稍后处理。

VariadicDynCastAllOfMatchers 是可调用类,模拟 C++03 中的可变参数模板函数。它们接受任意数量的 Matcher<Derived> 并返回一个 Matcher<Base>

AST_MATCHER_P(Type, Name, ParamType, Param)

大多数匹配器定义使用匹配器创建宏。这些宏定义了类型为 Matcher<Type> 的匹配器本身,以及一个名为 Name 的匹配器创建函数,该函数接受类型为 ParamType 的参数并返回相应的匹配器。

有多个匹配器定义宏,用于处理多态返回值和不同的参数计数。请参阅 ASTMatchersMacros.h

匹配器创建函数

匹配器是通过嵌套调用匹配器创建函数生成的。大多数情况下,这些函数是通过使用 VariadicDynCastAllOfMatcher 或匹配器创建宏(见下文)创建的。独立函数表明该匹配器只是其他匹配器的组合,例如 callee