diff options
Diffstat (limited to 'compilerplugins/clang/sharedvisitor/generator.cxx')
-rw-r--r-- | compilerplugins/clang/sharedvisitor/generator.cxx | 563 |
1 files changed, 563 insertions, 0 deletions
diff --git a/compilerplugins/clang/sharedvisitor/generator.cxx b/compilerplugins/clang/sharedvisitor/generator.cxx new file mode 100644 index 000000000000..c714c0cc69db --- /dev/null +++ b/compilerplugins/clang/sharedvisitor/generator.cxx @@ -0,0 +1,563 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +/* +This tool generates another "plugin" which in fact only dispatches Visit* and Traverse* +calls to all other plugins registered with it. This means that there is just one +RecursiveASTVisitor pass for all those plugins instead of one per each, which +with the current number of plugins actually makes a performance difference. + +If you work on a plugin, comment out LO_CLANG_SHARED_PLUGINS in Makefile-clang.mk in order +to disable the feature (re-generating takes time). + +Requirements for plugins: +- Can use Visit* and Traverse* functions, but not WalkUp*. +- Visit* functions can generally remain unmodified. +- run() function must be split into preRun() and postRun() if there's any additional functionality + besides calling TraverseDecl(). The shared visitor will call the preRun() and postRun() functions + as necessary while calling its own run(). The run() function of the plugin must stay + (in case of a non-shared build) but should generally look like this: + if( preRun()) + if( TraverseDecl(compiler.getASTContext().getTranslationUnitDecl())) + postRun(); +- Traverse* functions must be split into PreTraverse* and PostTraverse*, similarly to how run() + is handled, the Traverse* function should generally look like this: + bool ret = true; + if( PreTraverse*(decl)) + { + ret = RecursiveASTVisitor::Traverse*(decl); + PostTraverse*(decl, ret); + } + return ret; + + +TODO: +- Create macros for the standardized layout of run(), Traverse*, etc.? +- Possibly check plugin sources more thoroughly (e.g. that run() doesn't actually do more). +- Have one tool that extracts info from plugin .cxx files into some .txt file and another tool + that generates sharedvisitor.cxx based on those files? That would generally make the generation + faster when doing incremental changes. The .txt file could also contain some checksum of the .cxx + to avoid the analysing pass completely if just the timestamp has changed. +- Do not re-compile sharedvisitor.cxx if its contents have not actually changed. +- Is it possible to make the clang code analyze just the .cxx without also parsing all the headers? +- Instead of having to comment out LO_CLANG_SHARED_PLUGINS, implement --enable-compiler-plugins=debug . + +*/ + +#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" + +#include <iostream> +#include <fstream> +#include <set> + +#include "../check.hxx" +#include "../check.cxx" + +using namespace std; + +using namespace clang; + +using namespace loplugin; + +// Info about a Visit* function in a plugin. +struct VisitFunctionInfo +{ + string name; + string argument; +}; + + +// Info about a Traverse* function in a plugin. +struct TraverseFunctionInfo +{ + string name; + string argument; + bool hasPre = false; + bool hasPost = false; +}; + +struct VisitFunctionInfoLess +{ + bool operator()( const VisitFunctionInfo& l, const VisitFunctionInfo& r ) const + { + return l.name < r.name; + } +}; + +struct TraverseFunctionInfoLess +{ + bool operator()( const TraverseFunctionInfo& l, const TraverseFunctionInfo& r ) const + { + return l.name < r.name; + } +}; + + +// Information about each LO plugin. +struct PluginInfo +{ + string className; // e.g. "BadStatics" + string variableName; // e.g. "badStatics" + string lowercaseName; + bool shouldVisitTemplateInstantiations; + bool shouldVisitImplicitCode; + set< VisitFunctionInfo, VisitFunctionInfoLess > visitFunctions; + set< TraverseFunctionInfo, TraverseFunctionInfoLess > traverseFunctions; +}; + +// We need separate visitors for shouldVisitTemplateInstantiations and shouldVisitImplicitCode, +// so split plugins into groups by what they should visit. +// It seems that trying to handle the shouldVisit* functionality with just one visitor +// is tricky. +enum PluginType +{ + PluginBasic, + PluginVisitTemplates, + PluginVisitImplicit, + PluginVisitTemplatesImplicit, +}; + +const int Plugin_Begin = PluginBasic; +const int Plugin_End = PluginVisitTemplatesImplicit + 1; +static const char* const pluginTypeNames[ Plugin_End ] + = { "Basic", "VisitTemplates", "VisitImplicit", "VisitTemplatesImplicit" }; + +static vector< PluginInfo > plugins[ Plugin_End ]; + + +void generateVisitor( PluginType type ); + +void generate() +{ + ostream& output = cout; + output << +"// This file is autogenerated. Do not modify.\n" +"// Generated by compilerplugins/clang/sharedvisitor/generator.cxx .\n" +"\n" +"#ifdef LO_CLANG_SHARED_PLUGINS\n" +"\n" +"#include <config_clang.h>\n" +"\n" +"#include <clang/AST/ASTContext.h>\n" +"#include <clang/AST/RecursiveASTVisitor.h>\n" +"\n" +"#include \"../plugin.hxx\"\n" +"\n"; + + output << "#undef LO_CLANG_SHARED_PLUGINS // to get sources of individual plugins\n"; + for( const auto& pluginGroup : plugins ) + for( const PluginInfo& plugin : pluginGroup ) + output << "#include \"../" << plugin.lowercaseName << ".cxx\"" << endl; + + output << +"\n" +"using namespace clang;\n" +"using namespace llvm;\n" +"\n" +"namespace loplugin\n" +"{\n"; + + for( int type = Plugin_Begin; type < Plugin_End; ++type ) + generateVisitor( static_cast< PluginType >( type )); + + output << +"} // namespace loplugin\n" +"\n" +"#endif // LO_CLANG_SHARED_PLUGINS\n"; +}; + +void generateVisitor( PluginType type ) +{ + if( plugins[ type ].empty()) + return; + ostream& output = cout; + output << +"\n" +"class SharedRecursiveASTVisitor" << pluginTypeNames[ type ] << "\n" +" : public FilteringPlugin< SharedRecursiveASTVisitor" << pluginTypeNames[ type ] << ">\n" +"{\n" +"public:\n" +" explicit SharedRecursiveASTVisitor" << pluginTypeNames[ type ] << "(const InstantiationData& rData)\n" +" : FilteringPlugin(rData)\n"; + for( const PluginInfo& plugin : plugins[ type ] ) + output << " , " << plugin.variableName << "( nullptr )\n"; + output << " {}\n"; + + output << +" virtual bool preRun() override\n" +" {\n"; + for( const PluginInfo& plugin : plugins[ type ] ) + { + output << " if( " << plugin.variableName << " && !" << plugin.variableName << "->preRun())\n"; + // This will disable the plugin for the rest of the run. + output << " " << plugin.variableName << " = nullptr;\n"; + } + output << +" return anyPluginActive();\n" +" }\n"; + + output << +" virtual void postRun() override\n" +" {\n"; + for( const PluginInfo& plugin : plugins[ type ] ) + { + output << " if( " << plugin.variableName << " )\n"; + output << " " << plugin.variableName << "->postRun();\n"; + } + output << +" }\n"; + + output << +" virtual void run() override {\n" +" if (preRun()) {\n" +" TraverseDecl(compiler.getASTContext().getTranslationUnitDecl());\n" +" postRun();\n" +" }\n" +" }\n" +" enum { isSharedPlugin = true };\n"; + + output << +" virtual bool setSharedPlugin( Plugin* plugin, const char* name ) override\n" +" {\n"; + bool first = true; + for( const PluginInfo& plugin : plugins[ type ] ) + { + output << " "; + if( !first ) + output << "else "; + first = false; + output << "if( strcmp( name, \"" << plugin.lowercaseName << "\" ) == 0 )\n"; + output << " " << plugin.variableName << " = static_cast< " << plugin.className << "* >( plugin );\n"; + } + output << +" else\n" +" return false;\n" +" return true;\n" +" }\n"; + + if( type == PluginVisitTemplates || type == PluginVisitTemplatesImplicit ) + output << "bool shouldVisitTemplateInstantiations() const { return true; }\n"; + if( type == PluginVisitImplicit || type == PluginVisitTemplatesImplicit ) + output << "bool shouldVisitImplicitCode() const { return true; }\n"; + + set< VisitFunctionInfo, VisitFunctionInfoLess > visitFunctions; + for( const PluginInfo& plugin : plugins[ type ] ) + for( const VisitFunctionInfo& visit : plugin.visitFunctions ) + visitFunctions.insert( visit ); + for( const VisitFunctionInfo& visit : visitFunctions ) + { + output << " bool " << visit.name << "(" << visit.argument << " arg)\n"; + output << +" {\n" +" if( ignoreLocation( arg ))\n" +" return true;\n"; + for( const PluginInfo& plugin : plugins[ type ] ) + { + if( plugin.visitFunctions.find( visit ) == plugin.visitFunctions.end()) + continue; + output << " if( " << plugin.variableName << " != nullptr "; + output << ")\n"; + output << " {\n"; + output << " if( !" << plugin.variableName << "->" << visit.name << "( arg ))\n"; + // This will disable the plugin for the rest of the run (as would returning false + // from Visit* normally do in the non-shared case). + output << " " << plugin.variableName << " = nullptr;\n"; + output << " }\n"; + } + output << +" return anyPluginActive();\n" +" }\n"; + } + + set< TraverseFunctionInfo, TraverseFunctionInfoLess > traverseFunctions; + for( const PluginInfo& plugin : plugins[ type ] ) + for( const TraverseFunctionInfo& traverse : plugin.traverseFunctions ) + traverseFunctions.insert( traverse ); + for( const TraverseFunctionInfo& traverse : traverseFunctions ) + { + output << " bool " << traverse.name << "(" << traverse.argument << " arg)\n"; + output << " {\n"; + for( const PluginInfo& plugin : plugins[ type ] ) + { + auto pluginTraverse = plugin.traverseFunctions.find( traverse ); + if( pluginTraverse == plugin.traverseFunctions.end()) + continue; + output << " " << plugin.className << "* save" << plugin.className << " = " << plugin.variableName << ";\n"; + if( pluginTraverse->hasPre ) + { + output << " if( " << plugin.variableName << " != nullptr "; + output << ")\n"; + output << " {\n"; + output << " if( !" << plugin.variableName << "->Pre" << traverse.name << "( arg ))\n"; + // This will disable the plugin for the time of the traverse, until restored later, + // just like directly returning from Traverse* would skip that part. + output << " " << plugin.variableName << " = nullptr;\n"; + output << " }\n"; + } + } + output << " bool ret = RecursiveASTVisitor::" << traverse.name << "( arg );\n"; + for( const PluginInfo& plugin : plugins[ type ] ) + { + auto pluginTraverse = plugin.traverseFunctions.find( traverse ); + if( pluginTraverse == plugin.traverseFunctions.end()) + continue; + if( pluginTraverse->hasPost ) + { + output << " if( " << plugin.variableName << " != nullptr "; + output << ")\n"; + output << " {\n"; + output << " if( !" << plugin.variableName << "->Post" << traverse.name << "( arg, ret ))\n"; + // This will disable the plugin for the rest of the run. + output << " save" << plugin.className << " = nullptr;\n"; + output << " }\n"; + } + output << " " << plugin.variableName << " = save" << plugin.className << ";\n"; + } + output << " return ret;\n"; + output << " }\n"; + } + + output << +"private:\n"; + + output << +" bool anyPluginActive() const\n" +" {\n"; + first = true; + for( const PluginInfo& plugin : plugins[ type ] ) + { + if( first ) + output << " return " << plugin.variableName << " != nullptr"; + else + output << "\n || " << plugin.variableName << " != nullptr"; + first = false; + } + output << ";\n"; + output << " }\n"; + + for( const PluginInfo& plugin : plugins[ type ] ) + output << " " << plugin.className << "* " << plugin.variableName << ";\n"; + + output << +"};\n" +"\n" +"loplugin::Plugin::Registration< SharedRecursiveASTVisitor" << pluginTypeNames[ type ] + << " > registration" << pluginTypeNames[ type ] << "(\"sharedvisitor" << pluginTypeNames[ type ] << "\");\n" +"\n"; +} + +class CheckFileVisitor + : public RecursiveASTVisitor< CheckFileVisitor > +{ +public: + bool VisitCXXRecordDecl(CXXRecordDecl *Declaration); + + bool TraverseNamespaceDecl(NamespaceDecl * decl) + { + // Skip non-LO namespaces the same way FilteringPlugin does. + if( !ContextCheck( decl ).Namespace( "loplugin" ).GlobalNamespace() + && !ContextCheck( decl ).AnonymousNamespace()) + { + return true; + } + return RecursiveASTVisitor<CheckFileVisitor>::TraverseNamespaceDecl(decl); + } +}; + +static bool inheritsPluginClassCheck( const Decl* decl ) +{ + return bool( DeclCheck( decl ).Class( "FilteringPlugin" ).Namespace( "loplugin" ).GlobalNamespace()) + || bool( DeclCheck( decl ).Class( "FilteringRewritePlugin" ).Namespace( "loplugin" ).GlobalNamespace()); +} + +static TraverseFunctionInfo findOrCreateTraverseFunctionInfo( PluginInfo& pluginInfo, StringRef name ) +{ + TraverseFunctionInfo info; + info.name = name; + auto foundInfo = pluginInfo.traverseFunctions.find( info ); + if( foundInfo != pluginInfo.traverseFunctions.end()) + { + info = move( *foundInfo ); + pluginInfo.traverseFunctions.erase( foundInfo ); + } + return info; +} + +bool CheckFileVisitor::VisitCXXRecordDecl( CXXRecordDecl* decl ) +{ + if( !isDerivedFrom( decl, inheritsPluginClassCheck )) + return true; + + if( decl->getName() == "FilteringPlugin" || decl->getName() == "FilteringRewritePlugin" ) + return true; + + PluginInfo pluginInfo; + pluginInfo.className = decl->getName().str(); + pluginInfo.variableName = pluginInfo.className; + assert( pluginInfo.variableName.size() > 0 ); + pluginInfo.variableName[ 0 ] = tolower( pluginInfo.variableName[ 0 ] ); + pluginInfo.lowercaseName = pluginInfo.className; + for( char& c : pluginInfo.lowercaseName ) + c = tolower( c ); + pluginInfo.shouldVisitTemplateInstantiations = false; + pluginInfo.shouldVisitImplicitCode = false; + for( const CXXMethodDecl* method : decl->methods()) + { + if( !method->getDeclName().isIdentifier()) + continue; + if( method->isStatic() || method->getAccess() != AS_public ) + continue; + if( method->getName().startswith( "Visit" )) + { + if( method->getNumParams() == 1 ) + { + VisitFunctionInfo visitInfo; + visitInfo.name = method->getName().str(); + visitInfo.argument = method->getParamDecl( 0 )->getTypeSourceInfo()->getType().getAsString(); + pluginInfo.visitFunctions.insert( move( visitInfo )); + } + else + { + cerr << "Unhandled Visit* function: " << pluginInfo.className << "::" << method->getName().str() << endl; + abort(); + } + } + else if( method->getName().startswith( "Traverse" )) + { + if( method->getNumParams() == 1 ) + { + TraverseFunctionInfo traverseInfo = findOrCreateTraverseFunctionInfo( pluginInfo, method->getName()); + traverseInfo.argument = method->getParamDecl( 0 )->getTypeSourceInfo()->getType().getAsString(); + pluginInfo.traverseFunctions.insert( move( traverseInfo )); + } + else + { + cerr << "Unhandled Traverse* function: " << pluginInfo.className << "::" << method->getName().str() << endl; + abort(); + } + } + else if( method->getName().startswith( "PreTraverse" )) + { + TraverseFunctionInfo traverseInfo = findOrCreateTraverseFunctionInfo( pluginInfo, method->getName(). substr( 3 )); + traverseInfo.hasPre = true; + pluginInfo.traverseFunctions.insert( move( traverseInfo )); + } + else if( method->getName().startswith( "PostTraverse" )) + { + TraverseFunctionInfo traverseInfo = findOrCreateTraverseFunctionInfo( pluginInfo, method->getName().substr( 4 )); + traverseInfo.hasPost = true; + pluginInfo.traverseFunctions.insert( move( traverseInfo )); + } + else if( method->getName() == "shouldVisitTemplateInstantiations" ) + pluginInfo.shouldVisitTemplateInstantiations = true; + else if( method->getName() == "shouldVisitImplicitCode" ) + pluginInfo.shouldVisitImplicitCode = true; + else if( method->getName().startswith( "WalkUp" )) + { + cerr << "WalkUp function not supported for shared visitor: " << pluginInfo.className << "::" << method->getName().str() << endl; + abort(); + } + } + + if( pluginInfo.shouldVisitTemplateInstantiations && pluginInfo.shouldVisitImplicitCode ) + plugins[ PluginVisitTemplatesImplicit ].push_back( move( pluginInfo )); + else if( pluginInfo.shouldVisitTemplateInstantiations ) + plugins[ PluginVisitTemplates ].push_back( move( pluginInfo )); + else if( pluginInfo.shouldVisitImplicitCode ) + plugins[ PluginVisitImplicit ].push_back( move( pluginInfo )); + else + plugins[ PluginBasic ].push_back( move( pluginInfo )); + return true; +} + + +class FindNamedClassConsumer + : public ASTConsumer +{ +public: + virtual void HandleTranslationUnit(ASTContext& context) override + { + visitor.TraverseDecl( context.getTranslationUnitDecl()); + } +private: + CheckFileVisitor visitor; +}; + +class FindNamedClassAction + : public ASTFrontendAction + { +public: + virtual unique_ptr<ASTConsumer> CreateASTConsumer( CompilerInstance&, StringRef ) override + { + return unique_ptr<ASTConsumer>( new FindNamedClassConsumer ); + } +}; + + +string readSourceFile( const char* filename ) +{ + string contents; + ifstream stream( filename ); + if( !stream ) + { + cerr << "Failed to open: " << filename << endl; + exit( 1 ); + } + string line; + bool hasIfdef = false; + while( getline( stream, line )) + { + // TODO add checks that it's e.g. not "#ifdef" ? + if( line.find( "#ifndef LO_CLANG_SHARED_PLUGINS" ) == 0 ) + hasIfdef = true; + contents += line; + contents += '\n'; + } + if( stream.eof() && hasIfdef ) + return contents; + return ""; +} + +int main(int argc, char** argv) +{ + for( int i = 1; i < argc; ++ i ) + { + string contents = readSourceFile(argv[i]); + if( contents.empty()) + continue; +#define STRINGIFY2(a) #a +#define STRINGIFY(a) STRINGIFY2(a) + vector< string > args = + { + "-I" STRINGIFY(BUILDDIR) "/config_host", // plugin sources use e.g. config_global.h + "-I" STRINGIFY(CLANGDIR) "/include", // clang's headers + "-I" STRINGIFY(CLANGSYSINCLUDE), // clang system headers + "-std=c++11", + "-D__STDC_CONSTANT_MACROS", // Clang headers require these. + "-D__STDC_FORMAT_MACROS", + "-D__STDC_LIMIT_MACROS", + }; + if( !clang::tooling::runToolOnCodeWithArgs( new FindNamedClassAction, contents, args, argv[ i ] )) + { + cerr << "Failed to analyze: " << argv[ i ] << endl; + return 2; + } + } + for( int type = Plugin_Begin; type < Plugin_End; ++type ) + { + sort( plugins[ static_cast< PluginType >( type ) ].begin(), plugins[ static_cast< PluginType >( type ) ].end(), + []( const PluginInfo& l, const PluginInfo& r ) { return l.className < r.className; } ); + } + generate(); + return 0; +} |