bump product version to 6.3.0.0.beta1
[LibreOffice.git] / compilerplugins / clang / sharedvisitor / generator.cxx
blobb12939516d164e03fd717340bd7c30bafe8f38b5
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
3 * This file is part of the LibreOffice project.
5 * This Source Code Form is subject to the terms of the Mozilla Public
6 * License, v. 2.0. If a copy of the MPL was not distributed with this
7 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
8 */
11 This tool generates another "plugin" which in fact only dispatches Visit* and Traverse*
12 calls to all other plugins registered with it. This means that there is just one
13 RecursiveASTVisitor pass for all those plugins instead of one per each, which
14 with the current number of plugins actually makes a performance difference.
16 If you work on a plugin, comment out LO_CLANG_SHARED_PLUGINS in Makefile-clang.mk in order
17 to disable the feature (re-generating takes time).
19 Requirements for plugins:
20 - Can use Visit* and Traverse* functions, but not WalkUp*.
21 - Visit* functions can generally remain unmodified.
22 - run() function must be split into preRun() and postRun() if there's any additional functionality
23 besides calling TraverseDecl(). The shared visitor will call the preRun() and postRun() functions
24 as necessary while calling its own run(). The run() function of the plugin must stay
25 (in case of a non-shared build) but should generally look like this:
26 if( preRun())
27 if( TraverseDecl(compiler.getASTContext().getTranslationUnitDecl()))
28 postRun();
29 - Traverse* functions must be split into PreTraverse* and PostTraverse*, similarly to how run()
30 is handled, the Traverse* function should generally look like this:
31 bool ret = true;
32 if( PreTraverse*(decl))
34 ret = RecursiveASTVisitor::Traverse*(decl);
35 PostTraverse*(decl, ret);
37 return ret;
40 TODO:
41 - Create macros for the standardized layout of run(), Traverse*, etc.?
42 - Possibly check plugin sources more thoroughly (e.g. that run() doesn't actually do more).
43 - Have one tool that extracts info from plugin .cxx files into some .txt file and another tool
44 that generates sharedvisitor.cxx based on those files? That would generally make the generation
45 faster when doing incremental changes. The .txt file could also contain some checksum of the .cxx
46 to avoid the analysing pass completely if just the timestamp has changed.
47 - Do not re-compile sharedvisitor.cxx if its contents have not actually changed.
48 - Is it possible to make the clang code analyze just the .cxx without also parsing all the headers?
49 - Instead of having to comment out LO_CLANG_SHARED_PLUGINS, implement --enable-compiler-plugins=debug .
53 #include "clang/AST/ASTConsumer.h"
54 #include "clang/AST/RecursiveASTVisitor.h"
55 #include "clang/Frontend/CompilerInstance.h"
56 #include "clang/Frontend/FrontendAction.h"
57 #include "clang/Tooling/Tooling.h"
59 #include <cstddef>
60 #include <cstring>
61 #include <iostream>
62 #include <fstream>
63 #include <set>
65 #include "../check.hxx"
66 #include "../check.cxx"
68 using namespace std;
70 using namespace clang;
72 using namespace loplugin;
74 // Info about a Visit* function in a plugin.
75 struct VisitFunctionInfo
77 string name;
78 string argument;
82 // Info about a Traverse* function in a plugin.
83 struct TraverseFunctionInfo
85 string name;
86 string argument;
87 bool hasPre = false;
88 bool hasPost = false;
91 struct VisitFunctionInfoLess
93 bool operator()( const VisitFunctionInfo& l, const VisitFunctionInfo& r ) const
95 return l.name < r.name;
99 struct TraverseFunctionInfoLess
101 bool operator()( const TraverseFunctionInfo& l, const TraverseFunctionInfo& r ) const
103 return l.name < r.name;
108 // Information about each LO plugin.
109 struct PluginInfo
111 string className; // e.g. "BadStatics"
112 string variableName; // e.g. "badStatics"
113 string lowercaseName;
114 bool shouldVisitTemplateInstantiations;
115 bool shouldVisitImplicitCode;
116 set< VisitFunctionInfo, VisitFunctionInfoLess > visitFunctions;
117 set< TraverseFunctionInfo, TraverseFunctionInfoLess > traverseFunctions;
120 // We need separate visitors for shouldVisitTemplateInstantiations and shouldVisitImplicitCode,
121 // so split plugins into groups by what they should visit.
122 // It seems that trying to handle the shouldVisit* functionality with just one visitor
123 // is tricky.
124 enum PluginType
126 PluginBasic,
127 PluginVisitTemplates,
128 PluginVisitImplicit,
129 PluginVisitTemplatesImplicit,
132 const int Plugin_Begin = PluginBasic;
133 const int Plugin_End = PluginVisitTemplatesImplicit + 1;
134 static const char* const pluginTypeNames[ Plugin_End ]
135 = { "Basic", "VisitTemplates", "VisitImplicit", "VisitTemplatesImplicit" };
137 static vector< PluginInfo > plugins[ Plugin_End ];
140 void generateVisitor( PluginType type );
142 void generate()
144 ostream& output = cout;
145 output <<
146 "// This file is autogenerated. Do not modify.\n"
147 "// Generated by compilerplugins/clang/sharedvisitor/generator.cxx .\n"
148 "\n"
149 "#ifdef LO_CLANG_SHARED_PLUGINS\n"
150 "\n"
151 "#include <config_clang.h>\n"
152 "\n"
153 "#include <clang/AST/ASTContext.h>\n"
154 "#include <clang/AST/RecursiveASTVisitor.h>\n"
155 "\n"
156 "#include \"../plugin.hxx\"\n"
157 "\n";
159 output << "#undef LO_CLANG_SHARED_PLUGINS // to get sources of individual plugins\n";
160 for( const auto& pluginGroup : plugins )
161 for( const PluginInfo& plugin : pluginGroup )
162 output << "#include \"../" << plugin.lowercaseName << ".cxx\"" << endl;
164 output <<
165 "\n"
166 "using namespace clang;\n"
167 "using namespace llvm;\n"
168 "\n"
169 "namespace loplugin\n"
170 "{\n";
172 for( int type = Plugin_Begin; type < Plugin_End; ++type )
173 generateVisitor( static_cast< PluginType >( type ));
175 output <<
176 "} // namespace loplugin\n"
177 "\n"
178 "#endif // LO_CLANG_SHARED_PLUGINS\n";
181 void generateVisitor( PluginType type )
183 if( plugins[ type ].empty())
184 return;
185 ostream& output = cout;
186 output <<
187 "\n"
188 "class SharedRecursiveASTVisitor" << pluginTypeNames[ type ] << "\n"
189 " : public FilteringPlugin< SharedRecursiveASTVisitor" << pluginTypeNames[ type ] << ">\n"
190 "{\n"
191 "public:\n"
192 " explicit SharedRecursiveASTVisitor" << pluginTypeNames[ type ] << "(const InstantiationData& rData)\n"
193 " : FilteringPlugin(rData)\n";
194 for( const PluginInfo& plugin : plugins[ type ] )
195 output << " , " << plugin.variableName << "( nullptr )\n";
196 output << " {}\n";
198 output <<
199 " virtual bool preRun() override\n"
200 " {\n";
201 for( const PluginInfo& plugin : plugins[ type ] )
203 output << " if( " << plugin.variableName << " && !" << plugin.variableName << "->preRun())\n";
204 // This will disable the plugin for the rest of the run.
205 output << " " << plugin.variableName << " = nullptr;\n";
207 output <<
208 " return anyPluginActive();\n"
209 " }\n";
211 output <<
212 " virtual void postRun() override\n"
213 " {\n";
214 for( const PluginInfo& plugin : plugins[ type ] )
216 output << " if( " << plugin.variableName << " )\n";
217 output << " " << plugin.variableName << "->postRun();\n";
219 output <<
220 " }\n";
222 output <<
223 " virtual void run() override {\n"
224 " if (preRun()) {\n"
225 " TraverseDecl(compiler.getASTContext().getTranslationUnitDecl());\n"
226 " postRun();\n"
227 " }\n"
228 " }\n"
229 " enum { isSharedPlugin = true };\n";
231 output <<
232 " virtual bool setSharedPlugin( Plugin* plugin, const char* name ) override\n"
233 " {\n";
234 bool first = true;
235 for( const PluginInfo& plugin : plugins[ type ] )
237 output << " ";
238 if( !first )
239 output << "else ";
240 first = false;
241 output << "if( strcmp( name, \"" << plugin.lowercaseName << "\" ) == 0 )\n";
242 output << " " << plugin.variableName << " = static_cast< " << plugin.className << "* >( plugin );\n";
244 output <<
245 " else\n"
246 " return false;\n"
247 " return true;\n"
248 " }\n";
250 if( type == PluginVisitTemplates || type == PluginVisitTemplatesImplicit )
251 output << "bool shouldVisitTemplateInstantiations() const { return true; }\n";
252 if( type == PluginVisitImplicit || type == PluginVisitTemplatesImplicit )
253 output << "bool shouldVisitImplicitCode() const { return true; }\n";
255 set< VisitFunctionInfo, VisitFunctionInfoLess > visitFunctions;
256 for( const PluginInfo& plugin : plugins[ type ] )
257 for( const VisitFunctionInfo& visit : plugin.visitFunctions )
258 visitFunctions.insert( visit );
259 for( const VisitFunctionInfo& visit : visitFunctions )
261 output << " bool " << visit.name << "(" << visit.argument << " arg)\n";
262 output <<
263 " {\n"
264 " if( ignoreLocation( arg ))\n"
265 " return true;\n";
266 for( const PluginInfo& plugin : plugins[ type ] )
268 if( plugin.visitFunctions.find( visit ) == plugin.visitFunctions.end())
269 continue;
270 output << " if( " << plugin.variableName << " != nullptr ";
271 output << ")\n";
272 output << " {\n";
273 output << " if( !" << plugin.variableName << "->" << visit.name << "( arg ))\n";
274 // This will disable the plugin for the rest of the run (as would returning false
275 // from Visit* normally do in the non-shared case).
276 output << " " << plugin.variableName << " = nullptr;\n";
277 output << " }\n";
279 output <<
280 " return anyPluginActive();\n"
281 " }\n";
284 set< TraverseFunctionInfo, TraverseFunctionInfoLess > traverseFunctions;
285 for( const PluginInfo& plugin : plugins[ type ] )
286 for( const TraverseFunctionInfo& traverse : plugin.traverseFunctions )
287 traverseFunctions.insert( traverse );
288 for( const TraverseFunctionInfo& traverse : traverseFunctions )
290 output << " bool " << traverse.name << "(" << traverse.argument << " arg)\n";
291 output << " {\n";
292 for( const PluginInfo& plugin : plugins[ type ] )
294 auto pluginTraverse = plugin.traverseFunctions.find( traverse );
295 if( pluginTraverse == plugin.traverseFunctions.end())
296 continue;
297 output << " " << plugin.className << "* save" << plugin.className << " = " << plugin.variableName << ";\n";
298 if( pluginTraverse->hasPre )
300 output << " if( " << plugin.variableName << " != nullptr ";
301 output << ")\n";
302 output << " {\n";
303 output << " if( !" << plugin.variableName << "->Pre" << traverse.name << "( arg ))\n";
304 // This will disable the plugin for the time of the traverse, until restored later,
305 // just like directly returning from Traverse* would skip that part.
306 output << " " << plugin.variableName << " = nullptr;\n";
307 output << " }\n";
310 output << " bool ret = RecursiveASTVisitor::" << traverse.name << "( arg );\n";
311 for( const PluginInfo& plugin : plugins[ type ] )
313 auto pluginTraverse = plugin.traverseFunctions.find( traverse );
314 if( pluginTraverse == plugin.traverseFunctions.end())
315 continue;
316 if( pluginTraverse->hasPost )
318 output << " if( " << plugin.variableName << " != nullptr ";
319 output << ")\n";
320 output << " {\n";
321 output << " if( !" << plugin.variableName << "->Post" << traverse.name << "( arg, ret ))\n";
322 // This will disable the plugin for the rest of the run.
323 output << " save" << plugin.className << " = nullptr;\n";
324 output << " }\n";
326 output << " " << plugin.variableName << " = save" << plugin.className << ";\n";
328 output << " return ret;\n";
329 output << " }\n";
332 output <<
333 "private:\n";
335 output <<
336 " bool anyPluginActive() const\n"
337 " {\n";
338 first = true;
339 for( const PluginInfo& plugin : plugins[ type ] )
341 if( first )
342 output << " return " << plugin.variableName << " != nullptr";
343 else
344 output << "\n || " << plugin.variableName << " != nullptr";
345 first = false;
347 output << ";\n";
348 output << " }\n";
350 for( const PluginInfo& plugin : plugins[ type ] )
351 output << " " << plugin.className << "* " << plugin.variableName << ";\n";
353 output <<
354 "};\n"
355 "\n"
356 "loplugin::Plugin::Registration< SharedRecursiveASTVisitor" << pluginTypeNames[ type ]
357 << " > registration" << pluginTypeNames[ type ] << "(\"sharedvisitor" << pluginTypeNames[ type ] << "\");\n"
358 "\n";
361 class CheckFileVisitor
362 : public RecursiveASTVisitor< CheckFileVisitor >
364 public:
365 bool VisitCXXRecordDecl(CXXRecordDecl *Declaration);
367 bool TraverseNamespaceDecl(NamespaceDecl * decl)
369 // Skip non-LO namespaces the same way FilteringPlugin does.
370 if( !ContextCheck( decl ).Namespace( "loplugin" ).GlobalNamespace()
371 && !ContextCheck( decl ).AnonymousNamespace())
373 return true;
375 return RecursiveASTVisitor<CheckFileVisitor>::TraverseNamespaceDecl(decl);
379 static bool inheritsPluginClassCheck( const Decl* decl )
381 return bool( DeclCheck( decl ).Class( "FilteringPlugin" ).Namespace( "loplugin" ).GlobalNamespace())
382 || bool( DeclCheck( decl ).Class( "FilteringRewritePlugin" ).Namespace( "loplugin" ).GlobalNamespace());
385 static TraverseFunctionInfo findOrCreateTraverseFunctionInfo( PluginInfo& pluginInfo, StringRef name )
387 TraverseFunctionInfo info;
388 info.name = name;
389 auto foundInfo = pluginInfo.traverseFunctions.find( info );
390 if( foundInfo != pluginInfo.traverseFunctions.end())
392 info = move( *foundInfo );
393 pluginInfo.traverseFunctions.erase( foundInfo );
395 return info;
398 static bool foundSomething;
400 bool CheckFileVisitor::VisitCXXRecordDecl( CXXRecordDecl* decl )
402 if( !isDerivedFrom( decl, inheritsPluginClassCheck ))
403 return true;
405 if( decl->getName() == "FilteringPlugin" || decl->getName() == "FilteringRewritePlugin" )
406 return true;
408 PluginInfo pluginInfo;
409 pluginInfo.className = decl->getName().str();
410 pluginInfo.variableName = pluginInfo.className;
411 assert( pluginInfo.variableName.size() > 0 );
412 pluginInfo.variableName[ 0 ] = tolower( pluginInfo.variableName[ 0 ] );
413 pluginInfo.lowercaseName = pluginInfo.className;
414 for( char& c : pluginInfo.lowercaseName )
415 c = tolower( c );
416 pluginInfo.shouldVisitTemplateInstantiations = false;
417 pluginInfo.shouldVisitImplicitCode = false;
418 for( const CXXMethodDecl* method : decl->methods())
420 if( !method->getDeclName().isIdentifier())
421 continue;
422 if( method->isStatic() || method->getAccess() != AS_public )
423 continue;
424 if( method->getName().startswith( "Visit" ))
426 if( method->getNumParams() == 1 )
428 VisitFunctionInfo visitInfo;
429 visitInfo.name = method->getName().str();
430 visitInfo.argument = method->getParamDecl( 0 )->getTypeSourceInfo()->getType().getAsString();
431 pluginInfo.visitFunctions.insert( move( visitInfo ));
433 else
435 cerr << "Unhandled Visit* function: " << pluginInfo.className << "::" << method->getName().str() << endl;
436 abort();
439 else if( method->getName().startswith( "Traverse" ))
441 if( method->getNumParams() == 1 )
443 TraverseFunctionInfo traverseInfo = findOrCreateTraverseFunctionInfo( pluginInfo, method->getName());
444 traverseInfo.argument = method->getParamDecl( 0 )->getTypeSourceInfo()->getType().getAsString();
445 pluginInfo.traverseFunctions.insert( move( traverseInfo ));
447 else
449 cerr << "Unhandled Traverse* function: " << pluginInfo.className << "::" << method->getName().str() << endl;
450 abort();
453 else if( method->getName().startswith( "PreTraverse" ))
455 TraverseFunctionInfo traverseInfo = findOrCreateTraverseFunctionInfo( pluginInfo, method->getName(). substr( 3 ));
456 traverseInfo.hasPre = true;
457 pluginInfo.traverseFunctions.insert( move( traverseInfo ));
459 else if( method->getName().startswith( "PostTraverse" ))
461 TraverseFunctionInfo traverseInfo = findOrCreateTraverseFunctionInfo( pluginInfo, method->getName().substr( 4 ));
462 traverseInfo.hasPost = true;
463 pluginInfo.traverseFunctions.insert( move( traverseInfo ));
465 else if( method->getName() == "shouldVisitTemplateInstantiations" )
466 pluginInfo.shouldVisitTemplateInstantiations = true;
467 else if( method->getName() == "shouldVisitImplicitCode" )
468 pluginInfo.shouldVisitImplicitCode = true;
469 else if( method->getName().startswith( "WalkUp" ))
471 cerr << "WalkUp function not supported for shared visitor: " << pluginInfo.className << "::" << method->getName().str() << endl;
472 abort();
476 if( pluginInfo.shouldVisitTemplateInstantiations && pluginInfo.shouldVisitImplicitCode )
477 plugins[ PluginVisitTemplatesImplicit ].push_back( move( pluginInfo ));
478 else if( pluginInfo.shouldVisitTemplateInstantiations )
479 plugins[ PluginVisitTemplates ].push_back( move( pluginInfo ));
480 else if( pluginInfo.shouldVisitImplicitCode )
481 plugins[ PluginVisitImplicit ].push_back( move( pluginInfo ));
482 else
483 plugins[ PluginBasic ].push_back( move( pluginInfo ));
485 foundSomething = true;
486 return true;
490 class FindNamedClassConsumer
491 : public ASTConsumer
493 public:
494 virtual void HandleTranslationUnit(ASTContext& context) override
496 visitor.TraverseDecl( context.getTranslationUnitDecl());
498 private:
499 CheckFileVisitor visitor;
502 class FindNamedClassAction
503 : public ASTFrontendAction
505 public:
506 virtual unique_ptr<ASTConsumer> CreateASTConsumer( CompilerInstance&, StringRef ) override
508 return unique_ptr<ASTConsumer>( new FindNamedClassConsumer );
513 string readSourceFile( const char* filename )
515 string contents;
516 ifstream stream( filename );
517 if( !stream )
519 cerr << "Failed to open: " << filename << endl;
520 exit( 1 );
522 string line;
523 bool hasIfdef = false;
524 while( getline( stream, line ))
526 // TODO add checks that it's e.g. not "#ifdef" ?
527 if( line.find( "#ifndef LO_CLANG_SHARED_PLUGINS" ) == 0 )
528 hasIfdef = true;
529 contents += line;
530 contents += '\n';
532 if( stream.eof() && hasIfdef )
533 return contents;
534 return "";
537 int main(int argc, char** argv)
539 vector< string > args;
540 int i = 1;
541 for( ; i < argc; ++ i )
543 constexpr std::size_t prefixlen = 5; // strlen("-arg=");
544 if (std::strncmp(argv[i], "-arg=", prefixlen) != 0)
546 break;
548 args.push_back(argv[i] + prefixlen);
550 #define STRINGIFY2(a) #a
551 #define STRINGIFY(a) STRINGIFY2(a)
552 args.insert(
553 args.end(),
555 "-I" STRINGIFY(BUILDDIR) "/config_host", // plugin sources use e.g. config_global.h
556 "-I" STRINGIFY(CLANGDIR) "/include", // clang's headers
557 "-I" STRINGIFY(CLANGSYSINCLUDE), // clang system headers
558 "-std=c++11",
559 "-D__STDC_CONSTANT_MACROS", // Clang headers require these.
560 "-D__STDC_FORMAT_MACROS",
561 "-D__STDC_LIMIT_MACROS",
563 for( ; i < argc; ++ i )
565 string contents = readSourceFile(argv[i]);
566 if( contents.empty())
567 continue;
568 foundSomething = false;
569 if( !clang::tooling::runToolOnCodeWithArgs( new FindNamedClassAction, contents, args, argv[ i ] ))
571 cerr << "Failed to analyze: " << argv[ i ] << endl;
572 return 2;
574 if( !foundSomething )
576 // there's #ifndef LO_CLANG_SHARED_PLUGINS in the source, but no class matched
577 cerr << "Failed to find code: " << argv[ i ] << endl;
578 return 2;
581 for( int type = Plugin_Begin; type < Plugin_End; ++type )
583 sort( plugins[ static_cast< PluginType >( type ) ].begin(), plugins[ static_cast< PluginType >( type ) ].end(),
584 []( const PluginInfo& l, const PluginInfo& r ) { return l.className < r.className; } );
586 generate();
587 return 0;