Search code examples
clangclang++clang-ast-matchers

Clang AST Matchers: how to find function body from a function declaration?


I was trying to write a simple clang-tidy checker that will check for constructor that is calling fopen() more than once. My indention is to find potential memory leak in case any exception happens in the second fopen() call.

class Dummy_file
{
  FILE *f1_;
  FILE *f2_;
  public:
    Dummy_file(const char* f1_name, const char* f2_name, const char * mode){
        f1_ = fopen(f1_name, mode);
        f2_ = fopen(f2_name, mode);
    }
    ~Dummy_file(){
        fclose(f1_);
        fclose(f2_);
    }
};

Using this

callExpr(callee(functionDecl(hasName("fopen")))).bind("fopencalls")

was able to find all the fopen() calls.

But I could not find cxxConstructorDeclusing this.

cxxConstructorDecl(has(callExpr(callee(functionDecl(hasName("fopen")))))).bind("ctr")

I am doubting since I am using cxxConstructorDecl my filter is not applied to the constructor body. So how to find function body from a function declaration?


Solution

  • Short explanation

    You should use hasDescendant matcher instead of has matcher. While has checks only immediate children of the tested node for the match, hasDescendant matches any descendant.

    Here you can see that for your example:

      |-CXXConstructorDecl <line:8:3, line:11:3> line:8:3 Dummy_file 'void (const char *, const char *, const char *)'
      | |-ParmVarDecl <col:14, col:26> col:26 used f1_name 'const char *'
      | |-ParmVarDecl <col:35, col:47> col:47 used f2_name 'const char *'
      | |-ParmVarDecl <col:56, col:68> col:68 used mode 'const char *'
      | `-CompoundStmt <col:74, line:11:3>
      |   |-BinaryOperator <line:9:5, col:30> 'FILE *' lvalue '='
      |   | |-MemberExpr <col:5> 'FILE *' lvalue ->f1_ 0x55d36491a230
      |   | | `-CXXThisExpr <col:5> 'Dummy_file *' this
      |   | `-CallExpr <col:11, col:30> 'FILE *'
      |   |   |-ImplicitCastExpr <col:11> 'FILE *(*)(const char *__restrict, const char *__restrict)' <FunctionToPointerDecay>
      |   |   | `-DeclRefExpr <col:11> 'FILE *(const char *__restrict, const char *__restrict)' lvalue Function 0x55d3648fa220 'fopen' 'FILE *(const char *__restrict, const char *__restrict)'
      |   |   |-ImplicitCastExpr <col:17> 'const char *' <LValueToRValue>
      |   |   | `-DeclRefExpr <col:17> 'const char *' lvalue ParmVar 0x55d36491a310 'f1_name' 'const char *'
      |   |   `-ImplicitCastExpr <col:26> 'const char *' <LValueToRValue>
      |   |     `-DeclRefExpr <col:26> 'const char *' lvalue ParmVar 0x55d36491a400 'mode' 'const char *'
    

    CallExpr is a not a child of CXXConstructorDecl, but of BinaryOperator.

    Solution

    Below I finalized your matcher and checked it in clang-query.

    clang-query> match cxxConstructorDecl(hasDescendant(callExpr(callee(functionDecl(hasName("fopen")))).bind("fopencall"))).bind("ctr")
    
    Match #1:
    
    $TEST_DIR/test.cpp:8:3: note: "ctr" binds here
      Dummy_file(const char *f1_name, const char *f2_name, const char *mode) {
      ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    $TEST_DIR/test.cpp:9:11: note: "fopencall" binds here
        f1_ = fopen(f1_name, mode);
              ^~~~~~~~~~~~~~~~~~~~
    $TEST_DIR/test.cpp:8:3: note: "root" binds here
      Dummy_file(const char *f1_name, const char *f2_name, const char *mode) {
      ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    1 match.
    

    I hope this answers your question!