[cfe-dev] [AST Matcher API] How to match against a subtree?

Stephen Kelly via cfe-dev cfe-dev at lists.llvm.org
Wed Jul 25 15:00:26 PDT 2018


Gábor Márton via cfe-dev wrote:

> Hi,
> 
> I'd like to do a traversal on a subtree rooted on a specific node, but
> not on the whole tree rooted at the TranslataionUnitDecl. My
> understanding is this is not possible today, or is it? I wonder why
> this was not missing for anybody.

Hi Gabor,

I encountered the same issue, and wrote a similar solution. However, then I 
realized the ast_matchers::match documentation says:

/// If you want to find all matches on the sub-tree rooted at \c Node 
(rather
/// than only the matches on \c Node itself), surround the \c Matcher with a
/// \c findAll().

So, we should be able to do something like 

 Decl* someRootDeclContext = ...;
 
 clang::ast_matchers::match(
   decl(findAll(callExpr()))
   , *someRootDeclContext, *Result.Context);


However, that does not work because findAll is implemented with eachOf and 

   decl(eachOf(callExpr(), expr()))

doesn't work for the same reason

   decl(callExpr())

doesn't work - a decl is never a callExpr.

I filed a bug for this: https://bugs.llvm.org/show_bug.cgi?id=38318

Meanwhile, findAll might work for your specific case, or you might be able 
to write a replacement or use something more specific.

A case I encountered involved replacing the type of certain varDecl nodes in 
the source code, but porting them if they are passed by reference in a 
function call (a case I handled separately).

Given 

    void takeRef(int&)
    {
        
    }

    void takeConstRef(int const&)
    {
        
    }

    void takeVal(int)
    {
        
    }

    struct IntWrapper
    {
        IntWrapper(int i) : m_i(i) {}

        operator int() { return m_i; }

    private:
        int m_i;
    };

    int main()
    {
        int a = 42;
        int b = 7;

        takeVal(a);
        takeConstRef(a);

        takeVal(b);
        takeRef(b);
    }

the goal is to replace 

        int a = 42;

with 

        IntWrapper a = 42;

but leave 

        int b = 7;

untouched because 

        takeRef(b);

would not compile if the varDecl is ported.


Here is my solution which uses match(). You should be able to do something 
similar for your case without the additional API.

Thanks,

Stephen.


    #include "PortToIntwrapperCheck.h"
    #include "clang/AST/ASTContext.h"
    #include "clang/ASTMatchers/ASTMatchFinder.h"

    using namespace clang::ast_matchers;

    namespace clang {
    namespace tidy {
    namespace misc {

    void PortToIntwrapperCheck::registerMatchers(MatchFinder *Finder) {

      Finder->addMatcher(
        varDecl(
            hasType(asString("int")),
            unless(parmVarDecl()),
            hasDeclContext(decl().bind("varDeclContext"))
            ).bind("portToIntWrapperIfPossible")
        , this);
    }

    void PortToIntwrapperCheck::check(const MatchFinder::MatchResult 
&Result) {

      if (const auto *portToIntWrapper = 
Result.Nodes.getNodeAs<VarDecl>("portToIntWrapperIfPossible"))
      {
        const auto *varDeclContext = 
Result.Nodes.getNodeAs<Decl>("varDeclContext");

        // Got a match for a particular var. 
        // Check if it is passed by reference in any calls
        // within the context it is defined in

        auto usesByReference = clang::ast_matchers::match(
            decl(forEachDescendant(
                callExpr(forEachArgumentWithParam(
                    declRefExpr(
                        to(varDecl(equalsNode(portToIntWrapper)))
                        ).bind("arg"),
                    parmVarDecl(
                        hasType(lValueReferenceType(
                            unless(pointee(isConstQualified()))
                            ))
                        )
                    ))
                ))
            , *varDeclContext, *Result.Context);

        if (usesByReference.empty())
        {
            // All uses are safe to port to IntWrapper

            auto tInfo = portToIntWrapper->getTypeSourceInfo();
            auto tLoc = tInfo->getTypeLoc();
            
            SourceRange typeRange = tLoc.getSourceRange();

            diag(typeRange.getBegin(), "type of %0 should be IntWrapper")
              << portToIntWrapper
              << FixItHint::CreateReplacement(typeRange, "IntWrapper");
        }
      }
    }

    } // namespace misc
    } // namespace tidy
    } // namespace clang




More information about the cfe-dev mailing list