1

I am trying to build a matcher which will find the usage of a forbidden enum value, e.g.

foo(enumDeprecated); e.g.:

static internal::Matcher<Stmt> buildSimpleFunctionArgumentMatcher(
    const std::string argumentName,
    const std::string binding) {
    const auto hasArgumentName =
      hasAnyArgument(declRefExpr(to(varDecl(hasName(argumentName)))));

  const auto matcher = callExpr(hasArgumentName);

  return matcher;
}

But I add

Finder->addMatcher(
      buildSimpleArgumentMatcher(
          "enumDeprecated",
          "my_warning_message"),
      this);

Calling

foo(enumDeprecated)

Does not match.

Any idea?

1 Answers1

1

Matching the use of an enumerator

The main issue with your attempt is the use of varDecl. Instead, to match an enumerator, you must use enumConstantDecl.

In the Clang AST, a VarDecl is a declaration of a stand-alone variable, like int x;. (It is also used for function parameters and a few other things; see the linked document.) In contrast, an EnumConstantDecl is a declaration of an enumerator, like the A in enum E {A,B,C};. Their nearest common ancestor class is ValueDecl, reflecting the fact that each evaluates to a value when used as an (rvalue) expression, but that is the extent of their semantic commonality.

As a demonstration, here is an example clang-query command to report all uses of a particular enumerator:

clang-query -c='m declRefExpr(to(enumConstantDecl(hasName("enumDeprecated"))))' test.cc --

Test input:

// test.cc
// Test clang-query finding a specific enumerator.

enum MyEnumeration {
  someEnumerator,
  enumDeprecated   // Goal is to report uses of this.
};

int someVariable;

void f(int);

void g()
{
  // These three are not reported.
  f(3);
  f(someVariable);
  f(someEnumerator);

  // This one is reported.
  f(enumDeprecated);
}

// EOF

When trying to match particular syntax, it is often useful to study the Clang AST for that syntax carefully by running clang -Xclang -ast-dump -fsyntax-only test.cc. For the example above, the key parts are the declaration of the enumerator:

| `-EnumConstantDecl 0x91fc430 <line:6:3> col:3 referenced enumDeprecated 'MyEnumeration'

and its use within a call expression:

    `-CallExpr 0x91fcb08 <line:21:3, col:19> 'void'
      |-ImplicitCastExpr 0x91fcaf0 <col:3> 'void (*)(int)' <FunctionToPointerDecay>
      | `-DeclRefExpr 0x91fcad0 <col:3> 'void (int)' lvalue Function 0x91fc628 'f' 'void (int)'
      `-ImplicitCastExpr 0x91fcb30 <col:5> 'int' <IntegralCast>
        `-DeclRefExpr 0x91fcab0 <col:5> 'MyEnumeration' EnumConstant 0x91fc430 'enumDeprecated' 'MyEnumeration'

Comparing that to the AST Matchers Reference, we see that varDecl only matches VarDecl, and that matching EnumConstantDecl can be done with enumConstantDecl. (In this case, the names arguably make that conclusion trivial, but in general, the task of associating matcher to C++ class can be difficult.)

Implicit casts and hasAnyArgument

From your post, it appears you want to find not just any use of the enumerator in question, but specifically function calls where that enumerator (by itself) is one of the arguments. Using callExpr(hasAnyArgument(...)) is a sensible method, but there is a catch. Notice how the AST above includes an ImplicitCastExpr above the DeclRefExpr. That is because I declared the corresponding function parameter as int rather than MyEnumeration. The implicit cast will cause hasAnyArgument(declRefExpr(...)) to fail to match, even though hasArgument(0, declRefExpr(...)) does match (I'm not sure why this inconsistency exists).

In the context of a clang-query invocation, one solution is to set IgnoreUnlessSpelledInSource:

clang-query \
  -c='set traversal IgnoreUnlessSpelledInSource' \
  -c='m callExpr(hasAnyArgument(declRefExpr(to(enumConstantDecl(hasName("enumDeprecated"))))))' \
  test.cc --

That flag effectively suppresses AST nodes that represent implicit conversions, and consqeuently hasAnyArgument(declRefExpr(...)) works despite the implicit cast.

Note that whether this is needed or not depends on how the callee is declared, so it might not apply in your situation.

IgnoreUnlessSpelledInSource for a C++ matcher

You are using LibASTMatchers directly from C++, rather than calling the clang-query program. Although I have not tested this myself, from reading the source code, the equivalent of set traversal IgnoreUnlessSpelledInSource is to call clang::ParentMapContext::setTraversalKind(), which does not have its own Doxygen documentation (and hence no stable URL) but is referenced from TraversalKindScope, and pass TK_IgnoreUnlessSpelledInSource as the argument.

Scott McPeak
  • 8,803
  • 2
  • 40
  • 79