Search code examples
clangabstract-syntax-treematcher

AST matcher on a specific node


I wrote a AST matcher for finding specific type statements. In the matched nodes I calculated the neighbor siblings of that node. Now I need to run matcher on the neighbor nodes to verify they satisfies my condition or not. The clang AST matcher matches the whole tree node one by one. I want to run matcher against a particular node and return true if the node matches my required condition. Is this possible?


Solution

  • I suggest to implement your own matcher that will encapsulate the logic of finding neighbor nodes and matching them against other matchers.

    I've put together the following matcher as an example of how that can be done:

    using clang::ast_matchers::internal::Matcher;
    constexpr auto AVERAGE_NUMBER_OF_NESTED_MATCHERS = 3;
    using Matchers =
        llvm::SmallVector<Matcher<clang::Stmt>, AVERAGE_NUMBER_OF_NESTED_MATCHERS>;
    
    
    clang::Stmt *getNeighbor(const clang::Stmt &Node, clang::ASTContext &Context) {
      // It is my naive implementation of this method, you can easily put your own
      auto Parents = Context.getParents(Node);
      if (Parents.size() != 1) {
        return nullptr;
      }
    
      // As we deal with statements, let's assume that neighbor - is the next
      // statement in the enclosing compound statement.
      if (auto *Parent = Parents[0].get<clang::CompoundStmt>()) {
        auto Neighbor = std::adjacent_find(
            Parent->body_begin(), Parent->body_end(),
            [&Node](const auto *Top, const auto *Bottom) { return Top == &Node; });
    
        if (Neighbor != Parent->body_end()) {
          return *std::next(Neighbor);
        }
      }
    
      return nullptr;
    }
    
    AST_MATCHER_P(clang::Stmt, neighbors, Matchers, NestedMatchers) {
      // Node is the current tested node
      const clang::Stmt *CurrentNode = &Node;
    
      // Our goal is to iterate over the given matchers and match the current node
      // with the first matcher.
      //
      // Further on, we plan on checking whether the next
      // matcher matches the neighbor/sibling of the previous node.
      for (auto NestedMatcher : NestedMatchers) {
        // This is how one can call a matcher to test one node.
        //
        // NOTE: it uses Finder and Builder, so it's better to do it from
        //       inside of a matcher and get those for free
        if (CurrentNode == nullptr or
            not NestedMatcher.matches(*CurrentNode, Finder, Builder)) {
          return false;
        }
    
        // Here you can put your own implementation of finding neighbor/sibling
        CurrentNode = getNeighbor(*CurrentNode, Finder->getASTContext());
      }
    
      return true;
    }
    

    I hope that the comments within the snippet cover the main ideas behind this matcher.

    Demo

    Matcher:

    neighbors({declStmt().bind("first"), forStmt().bind("second"),
               returnStmt().bind("third")})
    

    Code snippet:

    int foo() {
      int x = 42;
      int y = 10;
      for (; x > y; --x) {
      }
      return x;
    }
    

    Output:

    first:
    DeclStmt 0x4c683e0
    `-VarDecl 0x4c68360  used y 'int' cinit
      `-IntegerLiteral 0x4c683c0 'int' 10
    
    second:
    ForStmt 0x4c684d0
    |-<<<NULL>>>
    |-<<<NULL>>>
    |-BinaryOperator 0x4c68468 '_Bool' '>'
    | |-ImplicitCastExpr 0x4c68438 'int' <LValueToRValue>
    | | `-DeclRefExpr 0x4c683f8 'int' lvalue Var 0x4c682b0 'x' 'int'
    | `-ImplicitCastExpr 0x4c68450 'int' <LValueToRValue>
    |   `-DeclRefExpr 0x4c68418 'int' lvalue Var 0x4c68360 'y' 'int'
    |-UnaryOperator 0x4c684a8 'int' lvalue prefix '--'
    | `-DeclRefExpr 0x4c68488 'int' lvalue Var 0x4c682b0 'x' 'int'
    `-CompoundStmt 0x4c684c0
    
    third:
    ReturnStmt 0x4c68540
    `-ImplicitCastExpr 0x4c68528 'int' <LValueToRValue>
      `-DeclRefExpr 0x4c68508 'int' lvalue Var 0x4c682b0 'x' 'int'
    

    I hope that answers your question!