217 lines
7.3 KiB
ReStructuredText
217 lines
7.3 KiB
ReStructuredText
|
==========================================================
|
||
|
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
|
||
|
|