编写匹配器

在本页中,我们将提供一些关于匹配器是什么的信息,然后提供一个逐步开发简单匹配器的示例。

匹配器类型

匹配器有三种类型:节点匹配器、缩小匹配器和遍历匹配器。它们之间并不总是存在清晰的分界或区别,因此请将此解释视为说明性而非确定性的。以下是匹配器的文档:https://clang.llvm.net.cn/docs/LibASTMatchersReference.html

在该页面上,这一点并不明显,因此我们想指出,**单击匹配器的名称可以展开有关该匹配器的帮助信息。** 例如

../../../_images/documentation-expanded.png

节点匹配器

节点匹配器可以被认为是“名词”。它们指定您要匹配的节点的**类型**,即某个特定的事物。一个函数、一个二元运算符、一个变量、一个类型。

完整列表节点匹配器在文档中列出。一些常见的匹配器包括 functionDecl()binaryOperator()stmt()

缩小匹配器

缩小匹配器可以被认为是“形容词”。它们缩小或描述一个节点,因此必须应用于节点匹配器。例如,一个节点匹配器可以是 functionDecl,而应用于它的缩小匹配器可以是 parameterCountIs

文档中的表格中列出了所有缩小匹配器,以及它们应用于哪些节点以及如何使用它们。以下是阅读该表格的方法

../../../_images/narrowing-matcher.png

以及一些示例

m functionDecl(parameterCountIs(1))
m functionDecl(anyOf(isDefinition(), isVariadic()))

如您所见,**只允许一个缩小匹配器**,它位于节点匹配器的括号内。在第一个示例中,匹配器是 parameterCountIs,在第二个示例中,它是 anyOf

在第二个示例中,我们使用单数 anyOf 匹配器来匹配多个其他缩小匹配器中的任何一个:isDefinitionisVariadic。其他两个常见的组合缩小匹配器是 allOf()unless()

如果您需要指定缩小匹配器(因为它是一些其他匹配器的必需参数),您可以使用 anything() 缩小匹配器来获得一个无操作的缩小匹配器。

遍历匹配器

遍历匹配器可以被认为是形容词——至少大多数情况下是这样。它们也描述一个特定的节点,但与缩小匹配器的区别在于,描述的范围比单个节点更广。缩小匹配器说明了节点本身的一些信息(例如,它具有的参数数量),而遍历匹配器说明了节点的内容或在程序中的位置。

同样,文档是探索和理解这些匹配器的最佳位置,但这里有一个遍历匹配器 hasArraySize() 的简单示例

Given:
  class MyClass { };
  MyClass *p1 = new MyClass[10];


cxxNewExpr()
  matches the expression 'new MyClass[10]'.

cxxNewExpr(hasArraySize(integerLiteral(equals(9))))
  does not match anything

cxxNewExpr(hasArraySize(integerLiteral(equals(10))))
  matches the expression 'new MyClass[10]'.

迭代匹配器开发示例

在开发匹配器时,如果您执行以下操作,将会更容易

  1. 写下您想要匹配的代码。尽可能以不同的方式写出来。例如:对于代码中的一些值,使用变量、常量和返回值的函数。将您想要匹配的代码放在函数内部、条件语句内部、函数调用内部以及内联函数定义内部。

  2. 写下您想要匹配的代码,但看起来像是您想要的代码。写下良性的函数调用、良性的赋值等。

  3. 迭代您的匹配器,并将其视为您正在编写的_代码_。缩进它,将其复制到其他地方以防您的浏览器崩溃,甚至将其粘贴到一个很小的临时版本控制文件中。

作为上述内容的一个示例,下面是一个更复杂的匹配器的示例迭代开发过程。

**目标**:匹配函数调用,其中一个参数是带有整数字面量的赋值表达式,但函数参数在函数定义中具有默认值。

int add1(int a, int b) { return a + b; }
int add2(int c, int d = 8) { return c + d; }

int main() {
 int x, y, z;

 add1(x, y);     // <- No match, no assignment
 add1(3 + 4, y); // <- No match, no assignment
 add1(z = x, y); // <- No match, assignment, but not an integer literal
 add1(z = 2, y); // <- No match, assignment, integer literal, but function parameter lacks default value
 add2(3, z = 2); // <- Match
}

以下是迭代开发过程

//-------------------------------------
// Step 1: Find all the function calls
m callExpr()
// Matches all calls, as expected.

//-------------------------------------
// Step 2: Start refining based on the arguments to the call
m callExpr(forEachArgumentWithParam()))
// Error: forEachArgumentWithParam expects two parameters

//-------------------------------------
// Step 3: Figure out the syntax to matching all the calls with this new operator
m callExpr(
      forEachArgumentWithParam(
              anything(),
              anything()
      )
)
// Matches all calls, as expected

//-------------------------------------
// Step 4: Find the calls with a binary operator of any kind
m callExpr(
  forEachArgumentWithParam(
     binaryOperator(),
     anything()
   )
)
// Does not match the first call, but matches the others

//-------------------------------------
// Step 5: Limit the binary operator to assignments
m callExpr(
  forEachArgumentWithParam(
     binaryOperator(isAssignmentOperator()),
     anything()
   )
)
// Now matches the final three calls

//-------------------------------------
// Step 6: Starting to refine matching the right-hand of the assignment
m callExpr(
  forEachArgumentWithParam(
     binaryOperator(
      allOf(
        isAssignmentOperator(),
        hasRHS()
          )),
     anything()
   )
)
// Error, hasRHS expects a parameter

//-------------------------------------
// Step 7:
m callExpr(
  forEachArgumentWithParam(
     binaryOperator(
      allOf(
        isAssignmentOperator(),
        hasRHS(anything())
                )),
     anything()
   )
)
// Okay, back to matching the final three calls

//-------------------------------------
// Step 8: Refine to just integer literals
m callExpr(
  forEachArgumentWithParam(
     binaryOperator(
      allOf(
        isAssignmentOperator(),
        hasRHS(integerLiteral())
                )),
     anything()
   )
)
// Now we match the final two calls

//-------------------------------------
// Step 9: Apply a restriction to the parameter definition
m callExpr(
  forEachArgumentWithParam(
     binaryOperator(
      allOf(
        isAssignmentOperator(),
        hasRHS(integerLiteral())
                )),
     hasDefaultArgument()
   )
)
// Now we match the final call