Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Fetching contributors…

Cannot retrieve contributors at this time

222 lines (193 sloc) 8.495 kB
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN"
"http://www.w3.org/TR/html4/strict.dtd">
<html>
<head>
<title>How to write RecursiveASTVisitor based ASTFrontendActions.</title>
<link type="text/css" rel="stylesheet" href="../menu.css">
<link type="text/css" rel="stylesheet" href="../content.css">
</head>
<body>
<div id="content">
<h1>How to write RecursiveASTVisitor based ASTFrontendActions.</h1>
<!-- ======================================================================= -->
<h2 id="intro">Introduction</h2>
<!-- ======================================================================= -->
In this tutorial you will learn how to create a FrontendAction that uses
a RecursiveASTVisitor to find CXXRecordDecl AST nodes with a specified name.
<!-- ======================================================================= -->
<h2 id="action">Creating a FrontendAction</h2>
<!-- ======================================================================= -->
<p>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.</p>
<pre>
class FindNamedClassAction : public clang::ASTFrontendAction {
public:
virtual clang::ASTConsumer *CreateASTConsumer(
clang::CompilerInstance &amp;Compiler, llvm::StringRef InFile) {
return new FindNamedClassConsumer;
}
};
</pre>
<!-- ======================================================================= -->
<h2 id="consumer">Creating an ASTConsumer</h2>
<!-- ======================================================================= -->
<p>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.</p>
<pre>
class FindNamedClassConsumer : public clang::ASTConsumer {
public:
virtual void HandleTranslationUnit(clang::ASTContext &amp;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;
};
</pre>
<!-- ======================================================================= -->
<h2 id="rav">Using the RecursiveASTVisitor</h2>
<!-- ======================================================================= -->
<p>Now that everything is hooked up, the next step is to implement a
RecursiveASTVisitor to extract the relevant information from the AST.</p>
<p>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.
</p>
<p>Let's start by writing a RecursiveASTVisitor that visits all CXXRecordDecl's.
<pre>
class FindNamedClassVisitor
: public RecursiveASTVisitor&lt;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;
}
};
</pre>
</p>
<p>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:
<pre>
bool VisitCXXRecordDecl(CXXRecordDecl *Declaration) {
if (Declaration->getQualifiedNameAsString() == "n::m::C")
Declaration->dump();
return true;
}
</pre>
</p>
<!-- ======================================================================= -->
<h2 id="context">Accessing the SourceManager and ASTContext</h2>
<!-- ======================================================================= -->
<p>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.</p>
<p>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:</p>
<pre>
virtual clang::ASTConsumer *CreateASTConsumer(
clang::CompilerInstance &amp;Compiler, llvm::StringRef InFile) {
return new FindNamedClassConsumer(<b>&amp;Compiler.getASTContext()</b>);
}
</pre>
<p>Now that the ASTContext is available in the RecursiveASTVisitor, we can do
more interesting things with AST nodes, like looking up their source
locations:</p>
<pre>
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() &lt;&lt; "Found declaration at "
&lt;&lt; FullLocation.getSpellingLineNumber() &lt;&lt; ":"
&lt;&lt; FullLocation.getSpellingColumnNumber() &lt;&lt; "\n";
}
return true;
}
</pre>
<!-- ======================================================================= -->
<h2 id="full">Putting it all together</h2>
<!-- ======================================================================= -->
<p>Now we can combine all of the above into a small example program:</p>
<pre>
#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&lt;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() &lt;&lt; "Found declaration at "
&lt;&lt; FullLocation.getSpellingLineNumber() &lt;&lt; ":"
&lt;&lt; FullLocation.getSpellingColumnNumber() &lt;&lt; "\n";
}
return true;
}
private:
ASTContext *Context;
};
class FindNamedClassConsumer : public clang::ASTConsumer {
public:
explicit FindNamedClassConsumer(ASTContext *Context)
: Visitor(Context) {}
virtual void HandleTranslationUnit(clang::ASTContext &amp;Context) {
Visitor.TraverseDecl(Context.getTranslationUnitDecl());
}
private:
FindNamedClassVisitor Visitor;
};
class FindNamedClassAction : public clang::ASTFrontendAction {
public:
virtual clang::ASTConsumer *CreateASTConsumer(
clang::CompilerInstance &amp;Compiler, llvm::StringRef InFile) {
return new FindNamedClassConsumer(&amp;Compiler.getASTContext());
}
};
int main(int argc, char **argv) {
if (argc > 1) {
clang::tooling::runToolOnCode(new FindNamedClassAction, argv[1]);
}
}
</pre>
<p>We store this into a file called FindClassDecls.cpp and create the following
CMakeLists.txt to link it:</p>
<pre>
set(LLVM_USED_LIBS clangTooling)
add_clang_executable(find-class-decls FindClassDecls.cpp)
</pre>
<p>When running this tool over a small code snippet it will output all
declarations of a class n::m::C it found:</p>
<pre>
$ ./bin/find-class-decls "namespace n { namespace m { class C {}; } }"
Found declaration at 1:29
</pre>
</div>
</body>
</html>
Jump to Line
Something went wrong with that request. Please try again.