Files
libreoffice/compilerplugins/clang/sharedvisitor/generator.cxx
Noel Grandin 476fb5d26e Revert "simplify sharedplugin PostTraverse calls"
This reverts commit ff55ad1ace,
Lubos prefers it the way it was.

Change-Id: I68edc21c438b6aa2fc819245dd9a3d590af3a278
Reviewed-on: https://gerrit.libreoffice.org/75790
Reviewed-by: Luboš Luňák <l.lunak@collabora.com>
Tested-by: Jenkins
2019-07-17 20:40:01 +02:00

589 lines
21 KiB
C++

/* -*- 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 <cstddef>
#include <cstring>
#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;
}
static bool foundSomething;
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 ));
foundSomething = true;
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)
{
vector< string > args;
int i = 1;
for( ; i < argc; ++ i )
{
constexpr std::size_t prefixlen = 5; // strlen("-arg=");
if (std::strncmp(argv[i], "-arg=", prefixlen) != 0)
{
break;
}
args.push_back(argv[i] + prefixlen);
}
#define STRINGIFY2(a) #a
#define STRINGIFY(a) STRINGIFY2(a)
args.insert(
args.end(),
{
"-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",
});
for( ; i < argc; ++ i )
{
string contents = readSourceFile(argv[i]);
if( contents.empty())
continue;
foundSomething = false;
if( !clang::tooling::runToolOnCodeWithArgs( new FindNamedClassAction, contents, args, argv[ i ] ))
{
cerr << "Failed to analyze: " << argv[ i ] << endl;
return 2;
}
if( !foundSomething )
{
// there's #ifndef LO_CLANG_SHARED_PLUGINS in the source, but no class matched
cerr << "Failed to find code: " << 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;
}