如何将 for 循环与其主体中声明的向量相匹配?

Rah*_*ahn 8 c++ clang clang-ast-matchers clang-tidy

我希望将所有 for 循环与在其主体中声明的向量相匹配(稍后可能会扩展到 while 循环和 do-while 循环):

\n
#include <vector>\n\nint main() {\n  for (int i = 0; i < 10; i++) {\n    std::vector<int> foo;\n  }\n\n  return 0;\n}\n
Run Code Online (Sandbox Code Playgroud)\n

我从这个程序中得到的 AST 是

\n
`-FunctionDecl 0x55c1d33c8bc8 <main.cpp:3:1, line:9:1> line:3:5 main \'int ()\'\n  `-CompoundStmt 0x55c1d3402a48 <col:12, line:9:1>\n    |-ForStmt 0x55c1d34029e0 <line:4:3, line:6:3>\n    | |-DeclStmt 0x55c1d33c8d40 <line:4:8, col:17>\n    | | `-VarDecl 0x55c1d33c8cb8 <col:8, col:16> col:12 used i \'int\' cinit\n    | |   `-IntegerLiteral 0x55c1d33c8d20 <col:16> \'int\' 0\n    | |-<<<NULL>>>\n    | |-BinaryOperator 0x55c1d33c8db0 <col:19, col:23> \'bool\' \'<\'\n    | | |-ImplicitCastExpr 0x55c1d33c8d98 <col:19> \'int\' <LValueToRValue>\n    | | | `-DeclRefExpr 0x55c1d33c8d58 <col:19> \'int\' lvalue Var 0x55c1d33c8cb8 \'i\' \'int\'\n    | | `-IntegerLiteral 0x55c1d33c8d78 <col:23> \'int\' 10\n    | |-UnaryOperator 0x55c1d33c8df0 <col:27, col:28> \'int\' postfix \'++\'\n    | | `-DeclRefExpr 0x55c1d33c8dd0 <col:27> \'int\' lvalue Var 0x55c1d33c8cb8 \'i\' \'int\'\n    | `-CompoundStmt 0x55c1d34029c8 <col:32, line:6:3>\n    |   `-DeclStmt 0x55c1d34029b0 <line:5:5, col:25>\n    |     `-VarDecl 0x55c1d33cd388 <col:5, col:22> col:22 foo \'std::vector<int>\' callinit destroyed\n    |       `-CXXConstructExpr 0x55c1d3402988 <col:22> \'std::vector<int>\' \'void () noexcept\'\n    `-ReturnStmt 0x55c1d3402a38 <line:8:3, col:10>\n      `-IntegerLiteral 0x55c1d3402a18 <col:10> \'int\' 0\n
Run Code Online (Sandbox Code Playgroud)\n

现在我如何编写一个匹配器(在 clang-tidy 定制检查中)来匹配这种模式?

\n

我阅读了有关自定义 clang-tidy 检查匹配 Clang AST的文档,但它们似乎没有提供足够的信息来说明我应该如何实际组合每个 API。

\n
\n

更新:根据 @Scott McPeak\'s 的回答,我可以在终端中将 for 循环与 clang-query 进行匹配,但我无法将此匹配器移植到我的 clang-tidy 检查中:

\n
`-FunctionDecl 0x55c1d33c8bc8 <main.cpp:3:1, line:9:1> line:3:5 main \'int ()\'\n  `-CompoundStmt 0x55c1d3402a48 <col:12, line:9:1>\n    |-ForStmt 0x55c1d34029e0 <line:4:3, line:6:3>\n    | |-DeclStmt 0x55c1d33c8d40 <line:4:8, col:17>\n    | | `-VarDecl 0x55c1d33c8cb8 <col:8, col:16> col:12 used i \'int\' cinit\n    | |   `-IntegerLiteral 0x55c1d33c8d20 <col:16> \'int\' 0\n    | |-<<<NULL>>>\n    | |-BinaryOperator 0x55c1d33c8db0 <col:19, col:23> \'bool\' \'<\'\n    | | |-ImplicitCastExpr 0x55c1d33c8d98 <col:19> \'int\' <LValueToRValue>\n    | | | `-DeclRefExpr 0x55c1d33c8d58 <col:19> \'int\' lvalue Var 0x55c1d33c8cb8 \'i\' \'int\'\n    | | `-IntegerLiteral 0x55c1d33c8d78 <col:23> \'int\' 10\n    | |-UnaryOperator 0x55c1d33c8df0 <col:27, col:28> \'int\' postfix \'++\'\n    | | `-DeclRefExpr 0x55c1d33c8dd0 <col:27> \'int\' lvalue Var 0x55c1d33c8cb8 \'i\' \'int\'\n    | `-CompoundStmt 0x55c1d34029c8 <col:32, line:6:3>\n    |   `-DeclStmt 0x55c1d34029b0 <line:5:5, col:25>\n    |     `-VarDecl 0x55c1d33cd388 <col:5, col:22> col:22 foo \'std::vector<int>\' callinit destroyed\n    |       `-CXXConstructExpr 0x55c1d3402988 <col:22> \'std::vector<int>\' \'void () noexcept\'\n    `-ReturnStmt 0x55c1d3402a38 <line:8:3, col:10>\n      `-IntegerLiteral 0x55c1d3402a18 <col:10> \'int\' 0\n
Run Code Online (Sandbox Code Playgroud)\n

当构建 clang-tidy 时,它说call of overloaded \'hasType()\' is ambiguous

\n
llvm-project/clang-tools-extra/clang-tidy/misc/LowPerfLoopCheck.cpp: In member function \xe2\x80\x98virtual void clang::tidy::misc::LowPerfLoopCheck::registerMatchers(clang::ast_matchers::MatchFinder*)\xe2\x80\x99:\n/home/wentao/Desktop/llvm-project/clang-tools-extra/clang-tidy/misc/LowPerfLoopCheck.cpp:19:44: error: call of overloaded \xe2\x80\x98hasType(clang::ast_matchers::internal::PolymorphicMatcher<clang::ast_matchers::internal::HasDeclarationMatcher, void(clang::ast_matchers::internal::TypeList<clang::CallExpr, clang::CXXConstructExpr, clang::CXXNewExpr, clang::DeclRefExpr, clang::EnumType, clang::ElaboratedType, clang::InjectedClassNameType, clang::LabelStmt, clang::AddrLabelExpr, clang::MemberExpr, clang::QualType, clang::RecordType, clang::TagType, clang::TemplateSpecializationType, clang::TemplateTypeParmType, clang::TypedefType, clang::UnresolvedUsingType, clang::ObjCIvarRefExpr>), clang::ast_matchers::internal::Matcher<clang::Decl> >)\xe2\x80\x99 is ambiguous\n   19 |       forStmt(hasDescendant(varDecl(hasType(hasDeclaration(cxxRecordDecl(\n      |                                     ~~~~~~~^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n   20 |                                         matchesName("^::std::vector$")))))\n      |                                         ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n
Run Code Online (Sandbox Code Playgroud)\n

为什么相同的代码适用于 clang-query 而不适用于 clang-tidy 检查?

\n

Sco*_*eak 2

您可以使用此 AST 匹配器找到for在某处包含变量声明的所有循环:std::vector

#!/bin/sh

PATH=$HOME/opt/clang+llvm-16.0.0-x86_64-linux-gnu-ubuntu-18.04/bin:$PATH

# In this query, the comments are ignored because clang-query (not the
# shell) recognizes and discards them.
query='m
  forStmt(                        # Report any "for" statement
    hasDescendant(                # where a descendant
      varDecl(                    # is a variable declaration
        hasType(                  # whose type
          cxxRecordDecl(          # is a struct/class/union (possibly templatized)
            matchesName(          # whose name matches the regex
              "^::std::vector$"   # given here.
            )
          )
        )
      ).bind("vector_var_decl")   # Bind to the declaration AST node.
    )
  )
'

clang-query \
  -c="$query" \
  test.cc -- -w

# EOF
Run Code Online (Sandbox Code Playgroud)

clang-query 为了便于实验,该匹配器被表示为 shell 脚本,但匹配器表达式可以直接复制到 C++clang-tidy检查器实现中,如下所示:

void HelloCheck::registerMatchers(MatchFinder *Finder) {
  Finder->addMatcher(
    forStmt(                        // Report any "for" statement
      hasDescendant(                // where a descendant
        varDecl(                    // is a variable declaration
          hasType(                  // whose type
            cxxRecordDecl(          // is a struct/class/union (possibly templatized)
              matchesName(          // whose name matches the regex
                "^::std::vector$"   // given here.
              )
            )
          )
        ).bind("vector_var_decl")   // Bind to the declaration AST node.
      )
    ),
    this
  );
}
Run Code Online (Sandbox Code Playgroud)

为了创建这个匹配器,我使用了AST Matcher Reference中的匹配器列表 。需要反复试验才能找到正确的组合;我希望文档更好。我可以提供的一个小技巧是,您可以使用 anything()匹配器作为占位符来匹配任何内容,同时逐步开发匹配器。

在这种情况下,第一个困难的部分(对我来说,尽管有一些先前的经验)是意识到我需要cxxRecordDecl先使用匹配器matchesName才能使用。最初,我尝试过 hasDeclaration,但它匹配不一定有名称的声明,因此matchesName只会默默地失败(clang-query在这种情况下可能会提供更多信息)。通过使用cxxRecordDecl,我们保证使用具有匹配名称的声明,因此它可以工作。

第二个问题是,当将我的原始clang-query匹配器转换为 C++clang-tidy检查时,出现了关于对 hasType. 原始匹配器有:

  varDecl(hasType(hasDeclaration(cxxRecordDecl(...))))
Run Code Online (Sandbox Code Playgroud)

显然,hasDeclaration在这个地方会在 C++ 上下文中引起问题(尽管我不太明白为什么),我的解决方案就是删除它:

  varDecl(hasType(cxxRecordDecl(...)))
Run Code Online (Sandbox Code Playgroud)

要扩展到其他类型的循环,可以将whileStmtdoStmt匹配器替换为forStmt

输入示例:

#if 1
  #include <vector>
#else
  // Simplified stand-in for easier AST inspection.
  namespace std {
    template <class T>
    class vector {};
  }
#endif

class SomethingElse {};

int main() {
  for (int i = 0; i < 10; i++) {       // Report.
    std::vector<int> foo;              // Original example.
  }

  for (int i = 0; i < 10; i++) {       // Do not report.
    SomethingElse se;                  // Not a std::vector.
  }

  for (;;) {                           // Report.
    {
      std::vector<int> foo;            // Not an immediate child.
    }
  }

  return 0;
}
Run Code Online (Sandbox Code Playgroud)

对应输出:

$ ./cmd.sh

Match #1:

$PWD/test.cc:14:3: note: "root" binds here
  for (int i = 0; i < 10; i++) {       // Report.
  ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
$PWD/test.cc:15:5: note: "vector_var_decl" binds here
    std::vector<int> foo;              // Original example.
    ^~~~~~~~~~~~~~~~~~~~

Match #2:

$PWD/test.cc:22:3: note: "root" binds here
  for (;;) {                           // Report.
  ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
$PWD/test.cc:24:7: note: "vector_var_decl" binds here
      std::vector<int> foo;            // Not an immediate child.
      ^~~~~~~~~~~~~~~~~~~~
2 matches.

Run Code Online (Sandbox Code Playgroud)