1 | ========================================================== |
2 | How to write RecursiveASTVisitor based ASTFrontendActions. |
3 | ========================================================== |
4 | |
5 | Introduction |
6 | ============ |
7 | |
8 | In this tutorial you will learn how to create a FrontendAction that uses |
9 | a RecursiveASTVisitor to find CXXRecordDecl AST nodes with a specified |
10 | name. |
11 | |
12 | Creating a FrontendAction |
13 | ========================= |
14 | |
15 | When writing a clang based tool like a Clang Plugin or a standalone tool |
16 | based on LibTooling, the common entry point is the FrontendAction. |
17 | FrontendAction is an interface that allows execution of user specific |
18 | actions as part of the compilation. To run tools over the AST clang |
19 | provides the convenience interface ASTFrontendAction, which takes care |
20 | of executing the action. The only part left is to implement the |
21 | CreateASTConsumer method that returns an ASTConsumer per translation |
22 | unit. |
23 | |
24 | :: |
25 | |
26 | class FindNamedClassAction : public clang::ASTFrontendAction { |
27 | public: |
28 | virtual std::unique_ptr<clang::ASTConsumer> CreateASTConsumer( |
29 | clang::CompilerInstance &Compiler, llvm::StringRef InFile) { |
30 | return std::unique_ptr<clang::ASTConsumer>( |
31 | new FindNamedClassConsumer); |
32 | } |
33 | }; |
34 | |
35 | Creating an ASTConsumer |
36 | ======================= |
37 | |
38 | ASTConsumer is an interface used to write generic actions on an AST, |
39 | regardless of how the AST was produced. ASTConsumer provides many |
40 | different entry points, but for our use case the only one needed is |
41 | HandleTranslationUnit, which is called with the ASTContext for the |
42 | translation unit. |
43 | |
44 | :: |
45 | |
46 | class FindNamedClassConsumer : public clang::ASTConsumer { |
47 | public: |
48 | virtual void HandleTranslationUnit(clang::ASTContext &Context) { |
49 | // Traversing the translation unit decl via a RecursiveASTVisitor |
50 | // will visit all nodes in the AST. |
51 | Visitor.TraverseDecl(Context.getTranslationUnitDecl()); |
52 | } |
53 | private: |
54 | // A RecursiveASTVisitor implementation. |
55 | FindNamedClassVisitor Visitor; |
56 | }; |
57 | |
58 | Using the RecursiveASTVisitor |
59 | ============================= |
60 | |
61 | Now that everything is hooked up, the next step is to implement a |
62 | RecursiveASTVisitor to extract the relevant information from the AST. |
63 | |
64 | The RecursiveASTVisitor provides hooks of the form bool |
65 | VisitNodeType(NodeType \*) for most AST nodes; the exception are TypeLoc |
66 | nodes, which are passed by-value. We only need to implement the methods |
67 | for the relevant node types. |
68 | |
69 | Let's start by writing a RecursiveASTVisitor that visits all |
70 | CXXRecordDecl's. |
71 | |
72 | :: |
73 | |
74 | class FindNamedClassVisitor |
75 | : public RecursiveASTVisitor<FindNamedClassVisitor> { |
76 | public: |
77 | bool VisitCXXRecordDecl(CXXRecordDecl *Declaration) { |
78 | // For debugging, dumping the AST nodes will show which nodes are already |
79 | // being visited. |
80 | Declaration->dump(); |
81 | |
82 | // The return value indicates whether we want the visitation to proceed. |
83 | // Return false to stop the traversal of the AST. |
84 | return true; |
85 | } |
86 | }; |
87 | |
88 | In the methods of our RecursiveASTVisitor we can now use the full power |
89 | of the Clang AST to drill through to the parts that are interesting for |
90 | us. For example, to find all class declaration with a certain name, we |
91 | can check for a specific qualified name: |
92 | |
93 | :: |
94 | |
95 | bool VisitCXXRecordDecl(CXXRecordDecl *Declaration) { |
96 | if (Declaration->getQualifiedNameAsString() == "n::m::C") |
97 | Declaration->dump(); |
98 | return true; |
99 | } |
100 | |
101 | Accessing the SourceManager and ASTContext |
102 | ========================================== |
103 | |
104 | Some of the information about the AST, like source locations and global |
105 | identifier information, are not stored in the AST nodes themselves, but |
106 | in the ASTContext and its associated source manager. To retrieve them we |
107 | need to hand the ASTContext into our RecursiveASTVisitor implementation. |
108 | |
109 | The ASTContext is available from the CompilerInstance during the call to |
110 | CreateASTConsumer. We can thus extract it there and hand it into our |
111 | freshly created FindNamedClassConsumer: |
112 | |
113 | :: |
114 | |
115 | virtual std::unique_ptr<clang::ASTConsumer> CreateASTConsumer( |
116 | clang::CompilerInstance &Compiler, llvm::StringRef InFile) { |
117 | return std::unique_ptr<clang::ASTConsumer>( |
118 | new FindNamedClassConsumer(&Compiler.getASTContext())); |
119 | } |
120 | |
121 | Now that the ASTContext is available in the RecursiveASTVisitor, we can |
122 | do more interesting things with AST nodes, like looking up their source |
123 | locations: |
124 | |
125 | :: |
126 | |
127 | bool VisitCXXRecordDecl(CXXRecordDecl *Declaration) { |
128 | if (Declaration->getQualifiedNameAsString() == "n::m::C") { |
129 | // getFullLoc uses the ASTContext's SourceManager to resolve the source |
130 | // location and break it up into its line and column parts. |
131 | FullSourceLoc FullLocation = Context->getFullLoc(Declaration->getBeginLoc()); |
132 | if (FullLocation.isValid()) |
133 | llvm::outs() << "Found declaration at " |
134 | << FullLocation.getSpellingLineNumber() << ":" |
135 | << FullLocation.getSpellingColumnNumber() << "\n"; |
136 | } |
137 | return true; |
138 | } |
139 | |
140 | Putting it all together |
141 | ======================= |
142 | |
143 | Now we can combine all of the above into a small example program: |
144 | |
145 | :: |
146 | |
147 | #include "clang/AST/ASTConsumer.h" |
148 | #include "clang/AST/RecursiveASTVisitor.h" |
149 | #include "clang/Frontend/CompilerInstance.h" |
150 | #include "clang/Frontend/FrontendAction.h" |
151 | #include "clang/Tooling/Tooling.h" |
152 | |
153 | using namespace clang; |
154 | |
155 | class FindNamedClassVisitor |
156 | : public RecursiveASTVisitor<FindNamedClassVisitor> { |
157 | public: |
158 | explicit FindNamedClassVisitor(ASTContext *Context) |
159 | : Context(Context) {} |
160 | |
161 | bool VisitCXXRecordDecl(CXXRecordDecl *Declaration) { |
162 | if (Declaration->getQualifiedNameAsString() == "n::m::C") { |
163 | FullSourceLoc FullLocation = Context->getFullLoc(Declaration->getBeginLoc()); |
164 | if (FullLocation.isValid()) |
165 | llvm::outs() << "Found declaration at " |
166 | << FullLocation.getSpellingLineNumber() << ":" |
167 | << FullLocation.getSpellingColumnNumber() << "\n"; |
168 | } |
169 | return true; |
170 | } |
171 | |
172 | private: |
173 | ASTContext *Context; |
174 | }; |
175 | |
176 | class FindNamedClassConsumer : public clang::ASTConsumer { |
177 | public: |
178 | explicit FindNamedClassConsumer(ASTContext *Context) |
179 | : Visitor(Context) {} |
180 | |
181 | virtual void HandleTranslationUnit(clang::ASTContext &Context) { |
182 | Visitor.TraverseDecl(Context.getTranslationUnitDecl()); |
183 | } |
184 | private: |
185 | FindNamedClassVisitor Visitor; |
186 | }; |
187 | |
188 | class FindNamedClassAction : public clang::ASTFrontendAction { |
189 | public: |
190 | virtual std::unique_ptr<clang::ASTConsumer> CreateASTConsumer( |
191 | clang::CompilerInstance &Compiler, llvm::StringRef InFile) { |
192 | return std::unique_ptr<clang::ASTConsumer>( |
193 | new FindNamedClassConsumer(&Compiler.getASTContext())); |
194 | } |
195 | }; |
196 | |
197 | int main(int argc, char **argv) { |
198 | if (argc > 1) { |
199 | clang::tooling::runToolOnCode(new FindNamedClassAction, argv[1]); |
200 | } |
201 | } |
202 | |
203 | We store this into a file called FindClassDecls.cpp and create the |
204 | following CMakeLists.txt to link it: |
205 | |
206 | :: |
207 | |
208 | add_clang_executable(find-class-decls FindClassDecls.cpp) |
209 | |
210 | target_link_libraries(find-class-decls clangTooling) |
211 | |
212 | When running this tool over a small code snippet it will output all |
213 | declarations of a class n::m::C it found: |
214 | |
215 | :: |
216 | |
217 | $ ./bin/find-class-decls "namespace n { namespace m { class C {}; } }" |
218 | Found declaration at 1:29 |
219 | |
220 | |