1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
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/.
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:
27 if( TraverseDecl(compiler.getASTContext().getTranslationUnitDecl()))
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:
32 if( PreTraverse*(decl))
34 ret = RecursiveASTVisitor::Traverse*(decl);
35 PostTraverse*(decl, ret);
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"
65 #include "../check.hxx"
66 #include "../check.cxx"
70 using namespace clang
;
72 using namespace loplugin
;
74 // Info about a Visit* function in a plugin.
75 struct VisitFunctionInfo
82 // Info about a Traverse* function in a plugin.
83 struct TraverseFunctionInfo
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.
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
127 PluginVisitTemplates
,
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
);
144 ostream
& output
= cout
;
146 "// This file is autogenerated. Do not modify.\n"
147 "// Generated by compilerplugins/clang/sharedvisitor/generator.cxx .\n"
149 "#ifdef LO_CLANG_SHARED_PLUGINS\n"
151 "#include <config_clang.h>\n"
153 "#include <clang/AST/ASTContext.h>\n"
154 "#include <clang/AST/RecursiveASTVisitor.h>\n"
156 "#include \"../plugin.hxx\"\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
;
166 "using namespace clang;\n"
167 "using namespace llvm;\n"
169 "namespace loplugin\n"
172 for( int type
= Plugin_Begin
; type
< Plugin_End
; ++type
)
173 generateVisitor( static_cast< PluginType
>( type
));
176 "} // namespace loplugin\n"
178 "#endif // LO_CLANG_SHARED_PLUGINS\n";
181 void generateVisitor( PluginType type
)
183 if( plugins
[ type
].empty())
185 ostream
& output
= cout
;
188 "class SharedRecursiveASTVisitor" << pluginTypeNames
[ type
] << "\n"
189 " : public FilteringPlugin< SharedRecursiveASTVisitor" << pluginTypeNames
[ type
] << ">\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";
199 " virtual bool preRun() override\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";
208 " return anyPluginActive();\n"
212 " virtual void postRun() override\n"
214 for( const PluginInfo
& plugin
: plugins
[ type
] )
216 output
<< " if( " << plugin
.variableName
<< " )\n";
217 output
<< " " << plugin
.variableName
<< "->postRun();\n";
223 " virtual void run() override {\n"
225 " TraverseDecl(compiler.getASTContext().getTranslationUnitDecl());\n"
229 " enum { isSharedPlugin = true };\n";
232 " virtual bool setSharedPlugin( Plugin* plugin, const char* name ) override\n"
235 for( const PluginInfo
& plugin
: plugins
[ type
] )
241 output
<< "if( strcmp( name, \"" << plugin
.lowercaseName
<< "\" ) == 0 )\n";
242 output
<< " " << plugin
.variableName
<< " = static_cast< " << plugin
.className
<< "* >( plugin );\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";
264 " if( ignoreLocation( arg ))\n"
266 for( const PluginInfo
& plugin
: plugins
[ type
] )
268 if( plugin
.visitFunctions
.find( visit
) == plugin
.visitFunctions
.end())
270 output
<< " if( " << plugin
.variableName
<< " != nullptr ";
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";
280 " return anyPluginActive();\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";
292 for( const PluginInfo
& plugin
: plugins
[ type
] )
294 auto pluginTraverse
= plugin
.traverseFunctions
.find( traverse
);
295 if( pluginTraverse
== plugin
.traverseFunctions
.end())
297 output
<< " " << plugin
.className
<< "* save" << plugin
.className
<< " = " << plugin
.variableName
<< ";\n";
298 if( pluginTraverse
->hasPre
)
300 output
<< " if( " << plugin
.variableName
<< " != nullptr ";
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";
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())
316 if( pluginTraverse
->hasPost
)
318 output
<< " if( " << plugin
.variableName
<< " != nullptr ";
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";
326 output
<< " " << plugin
.variableName
<< " = save" << plugin
.className
<< ";\n";
328 output
<< " return ret;\n";
336 " bool anyPluginActive() const\n"
339 for( const PluginInfo
& plugin
: plugins
[ type
] )
342 output
<< " return " << plugin
.variableName
<< " != nullptr";
344 output
<< "\n || " << plugin
.variableName
<< " != nullptr";
350 for( const PluginInfo
& plugin
: plugins
[ type
] )
351 output
<< " " << plugin
.className
<< "* " << plugin
.variableName
<< ";\n";
356 "loplugin::Plugin::Registration< SharedRecursiveASTVisitor" << pluginTypeNames
[ type
]
357 << " > registration" << pluginTypeNames
[ type
] << "(\"sharedvisitor" << pluginTypeNames
[ type
] << "\");\n"
361 class CheckFileVisitor
362 : public RecursiveASTVisitor
< CheckFileVisitor
>
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())
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
;
389 auto foundInfo
= pluginInfo
.traverseFunctions
.find( info
);
390 if( foundInfo
!= pluginInfo
.traverseFunctions
.end())
392 info
= move( *foundInfo
);
393 pluginInfo
.traverseFunctions
.erase( foundInfo
);
398 static bool foundSomething
;
400 bool CheckFileVisitor::VisitCXXRecordDecl( CXXRecordDecl
* decl
)
402 if( !isDerivedFrom( decl
, inheritsPluginClassCheck
))
405 if( decl
->getName() == "FilteringPlugin" || decl
->getName() == "FilteringRewritePlugin" )
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
)
416 pluginInfo
.shouldVisitTemplateInstantiations
= false;
417 pluginInfo
.shouldVisitImplicitCode
= false;
418 for( const CXXMethodDecl
* method
: decl
->methods())
420 if( !method
->getDeclName().isIdentifier())
422 if( method
->isStatic() || method
->getAccess() != AS_public
)
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
));
435 cerr
<< "Unhandled Visit* function: " << pluginInfo
.className
<< "::" << method
->getName().str() << endl
;
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
));
449 cerr
<< "Unhandled Traverse* function: " << pluginInfo
.className
<< "::" << method
->getName().str() << endl
;
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
;
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
));
483 plugins
[ PluginBasic
].push_back( move( pluginInfo
));
485 foundSomething
= true;
490 class FindNamedClassConsumer
494 virtual void HandleTranslationUnit(ASTContext
& context
) override
496 visitor
.TraverseDecl( context
.getTranslationUnitDecl());
499 CheckFileVisitor visitor
;
502 class FindNamedClassAction
503 : public ASTFrontendAction
506 virtual unique_ptr
<ASTConsumer
> CreateASTConsumer( CompilerInstance
&, StringRef
) override
508 return unique_ptr
<ASTConsumer
>( new FindNamedClassConsumer
);
513 string
readSourceFile( const char* filename
)
516 ifstream
stream( filename
);
519 cerr
<< "Failed to open: " << filename
<< endl
;
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 )
532 if( stream
.eof() && hasIfdef
)
537 int main(int argc
, char** argv
)
539 vector
< string
> args
;
541 for( ; i
< argc
; ++ i
)
543 constexpr std::size_t prefixlen
= 5; // strlen("-arg=");
544 if (std::strncmp(argv
[i
], "-arg=", prefixlen
) != 0)
548 args
.push_back(argv
[i
] + prefixlen
);
550 #define STRINGIFY2(a) #a
551 #define STRINGIFY(a) STRINGIFY2(a)
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
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())
568 foundSomething
= false;
569 if( !clang::tooling::runToolOnCodeWithArgs( new FindNamedClassAction
, contents
, args
, argv
[ i
] ))
571 cerr
<< "Failed to analyze: " << argv
[ i
] << endl
;
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
;
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
; } );