| # 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. | |
| ```cpp | |
| class FindNamedClassAction : public clang::ASTFrontendAction { | |
| public: | |
| virtual std::unique_ptr<clang::ASTConsumer> CreateASTConsumer( | |
| clang::CompilerInstance &Compiler, llvm::StringRef InFile) { | |
| return std::make_unique<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. | |
| ```cpp | |
| 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. | |
| ```cpp | |
| 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: | |
| ```cpp | |
| 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, is 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: | |
| ```cpp | |
| virtual std::unique_ptr<clang::ASTConsumer> CreateASTConsumer( | |
| clang::CompilerInstance &Compiler, llvm::StringRef InFile) { | |
| return std::make_unique<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: | |
| ```cpp | |
| 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->getBeginLoc()); | |
| 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: | |
| ```cpp | |
| #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->getBeginLoc()); | |
| 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 std::unique_ptr<clang::ASTConsumer> CreateASTConsumer( | |
| clang::CompilerInstance &Compiler, llvm::StringRef InFile) { | |
| return std::make_unique<FindNamedClassConsumer>(&Compiler.getASTContext()); | |
| } | |
| }; | |
| int main(int argc, char **argv) { | |
| if (argc > 1) { | |
| clang::tooling::runToolOnCode(std::make_unique<FindNamedClassAction>(), argv[1]); | |
| } | |
| } | |
| ``` | |
| We store this into a file called FindClassDecls.cpp and create the | |
| following CMakeLists.txt to link it: | |
| ```cmake | |
| set(LLVM_LINK_COMPONENTS | |
| Support | |
| ) | |
| add_clang_executable(find-class-decls FindClassDecls.cpp) | |
| target_link_libraries(find-class-decls | |
| PRIVATE | |
| clangAST | |
| clangBasic | |
| clangFrontend | |
| clangSerialization | |
| clangTooling | |
| ) | |
| ``` | |
| When running this tool over a small code snippet it will output all | |
| declarations of a class n::m::C it found: | |
| ```console | |
| $ ./bin/find-class-decls "namespace n { namespace m { class C {}; } }" | |
| Found declaration at 1:29 | |
| ``` |