How to write RecursiveASTVisitor based ASTFrontendActions.
- - -Introduction
- - -In this tutorial you will learn how to create a FrontendAction that uses -a RecursiveASTVisitor to find CXXRecordDecl AST nodes with a specified name. - - -Creating a FrontendAction
- - -When writing a clang based tool like a Clang Plugin or a standalone tool -based on LibTooling, the common entry point is the FrontendAction. -FrontendAction is an interface that allows execution of user specific actions -as part of the compilation. To run tools over the AST clang provides the -convenience interface ASTFrontendAction, which takes care of executing the -action. The only part left is to implement the CreateASTConsumer method that -returns an ASTConsumer per translation unit.
-- class FindNamedClassAction : public clang::ASTFrontendAction { - public: - virtual clang::ASTConsumer *CreateASTConsumer( - clang::CompilerInstance &Compiler, llvm::StringRef InFile) { - return new FindNamedClassConsumer; - } - }; -- - -
Creating an ASTConsumer
- - -ASTConsumer is an interface used to write generic actions on an AST, -regardless of how the AST was produced. ASTConsumer provides many different -entry points, but for our use case the only one needed is HandleTranslationUnit, -which is called with the ASTContext for the translation unit.
-- class FindNamedClassConsumer : public clang::ASTConsumer { - public: - virtual void HandleTranslationUnit(clang::ASTContext &Context) { - // Traversing the translation unit decl via a RecursiveASTVisitor - // will visit all nodes in the AST. - Visitor.TraverseDecl(Context.getTranslationUnitDecl()); - } - private: - // A RecursiveASTVisitor implementation. - FindNamedClassVisitor Visitor; - }; -- - -
Using the RecursiveASTVisitor
- - -Now that everything is hooked up, the next step is to implement a -RecursiveASTVisitor to extract the relevant information from the AST.
-The RecursiveASTVisitor provides hooks of the form -bool VisitNodeType(NodeType *) for most AST nodes; the exception are TypeLoc -nodes, which are passed by-value. We only need to implement the methods for the -relevant node types. -
-Let's start by writing a RecursiveASTVisitor that visits all CXXRecordDecl's. -
- class FindNamedClassVisitor - : public RecursiveASTVisitor<FindNamedClassVisitor> { - public: - bool VisitCXXRecordDecl(CXXRecordDecl *Declaration) { - // For debugging, dumping the AST nodes will show which nodes are already - // being visited. - Declaration->dump(); - - // The return value indicates whether we want the visitation to proceed. - // Return false to stop the traversal of the AST. - return true; - } - }; -- -
In the methods of our RecursiveASTVisitor we can now use the full power of -the Clang AST to drill through to the parts that are interesting for us. For -example, to find all class declaration with a certain name, we can check for a -specific qualified name: -
- bool VisitCXXRecordDecl(CXXRecordDecl *Declaration) { - if (Declaration->getQualifiedNameAsString() == "n::m::C") - Declaration->dump(); - return true; - } -- - - -
Accessing the SourceManager and ASTContext
- - -Some of the information about the AST, like source locations and global -identifier information, are not stored in the AST nodes themselves, but in -the ASTContext and its associated source manager. To retrieve them we need to -hand the ASTContext into our RecursiveASTVisitor implementation.
-The ASTContext is available from the CompilerInstance during the call -to CreateASTConsumer. We can thus extract it there and hand it into our -freshly created FindNamedClassConsumer:
-- virtual clang::ASTConsumer *CreateASTConsumer( - clang::CompilerInstance &Compiler, llvm::StringRef InFile) { - return new FindNamedClassConsumer(&Compiler.getASTContext()); - } -- -
Now that the ASTContext is available in the RecursiveASTVisitor, we can do -more interesting things with AST nodes, like looking up their source -locations:
-- bool VisitCXXRecordDecl(CXXRecordDecl *Declaration) { - if (Declaration->getQualifiedNameAsString() == "n::m::C") { - // getFullLoc uses the ASTContext's SourceManager to resolve the source - // location and break it up into its line and column parts. - FullSourceLoc FullLocation = Context->getFullLoc(Declaration->getLocStart()); - if (FullLocation.isValid()) - llvm::outs() << "Found declaration at " - << FullLocation.getSpellingLineNumber() << ":" - << FullLocation.getSpellingColumnNumber() << "\n"; - } - return true; - } -- - -
Putting it all together
- - -Now we can combine all of the above into a small example program:
-- #include "clang/AST/ASTConsumer.h" - #include "clang/AST/RecursiveASTVisitor.h" - #include "clang/Frontend/CompilerInstance.h" - #include "clang/Frontend/FrontendAction.h" - #include "clang/Tooling/Tooling.h" - - using namespace clang; - - class FindNamedClassVisitor - : public RecursiveASTVisitor<FindNamedClassVisitor> { - public: - explicit FindNamedClassVisitor(ASTContext *Context) - : Context(Context) {} - - bool VisitCXXRecordDecl(CXXRecordDecl *Declaration) { - if (Declaration->getQualifiedNameAsString() == "n::m::C") { - FullSourceLoc FullLocation = Context->getFullLoc(Declaration->getLocStart()); - if (FullLocation.isValid()) - llvm::outs() << "Found declaration at " - << FullLocation.getSpellingLineNumber() << ":" - << FullLocation.getSpellingColumnNumber() << "\n"; - } - return true; - } - - private: - ASTContext *Context; - }; - - class FindNamedClassConsumer : public clang::ASTConsumer { - public: - explicit FindNamedClassConsumer(ASTContext *Context) - : Visitor(Context) {} - - virtual void HandleTranslationUnit(clang::ASTContext &Context) { - Visitor.TraverseDecl(Context.getTranslationUnitDecl()); - } - private: - FindNamedClassVisitor Visitor; - }; - - class FindNamedClassAction : public clang::ASTFrontendAction { - public: - virtual clang::ASTConsumer *CreateASTConsumer( - clang::CompilerInstance &Compiler, llvm::StringRef InFile) { - return new FindNamedClassConsumer(&Compiler.getASTContext()); - } - }; - - int main(int argc, char **argv) { - if (argc > 1) { - clang::tooling::runToolOnCode(new FindNamedClassAction, argv[1]); - } - } -- -
We store this into a file called FindClassDecls.cpp and create the following -CMakeLists.txt to link it:
--set(LLVM_USED_LIBS clangTooling) - -add_clang_executable(find-class-decls FindClassDecls.cpp) -- -
When running this tool over a small code snippet it will output all -declarations of a class n::m::C it found:
-- $ ./bin/find-class-decls "namespace n { namespace m { class C {}; } }" - Found declaration at 1:29 -- -