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

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.

We can match **one** node against a matcher, but we will not traverse
into the children (this is MatchFinder::match).
Or we can traverse through the whole tree rooted at the TUDecl (this
is MatchFinder::matchAST).

Below, the short draft of my idea how could we support this, what do you think?

Thanks,
Gabor

diff --git a/include/clang/ASTMatchers/ASTMatchFinder.h
b/include/clang/ASTMatchers/ASTMatchFinder.h
index 389af1b..7610074 100644
--- a/include/clang/ASTMatchers/ASTMatchFinder.h
+++ b/include/clang/ASTMatchers/ASTMatchFinder.h
@@ -189,6 +189,15 @@ public:
              ASTContext &Context);
   /// @}

+ /// \brief Finds all matches in the given subtree rooted at \p Node
+ /// @{
+ template <typename T> void matchSubtree(const T &Node, ASTContext &Context) {
+ matchSubtree(clang::ast_type_traits::DynTypedNode::create(Node), Context);
+ }
+ void matchSubtree(const clang::ast_type_traits::DynTypedNode &Node,
+ ASTContext &Context);
+ /// @}

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

Hi Stephen,

Thanks for pointing out to findAll and it's weaknesses.
I went on and created a phabricator review:
https://reviews.llvm.org/D49840

Cheers,
GaborOn Thu, Jul 26, 2018 at 12:00 AM Stephen Kelly via cfe-dev