2013-09-21 14:42:35 +01:00
|
|
|
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
|
2013-02-02 16:35:47 +01:00
|
|
|
/*
|
|
|
|
* This file is part of the LibreOffice project.
|
|
|
|
*
|
|
|
|
* Based on LLVM/Clang.
|
|
|
|
*
|
|
|
|
* This file is distributed under the University of Illinois Open Source
|
|
|
|
* License. See LICENSE.TXT for details.
|
|
|
|
*
|
|
|
|
*/
|
|
|
|
|
2014-01-31 09:50:21 +01:00
|
|
|
#include "compat.hxx"
|
2013-02-02 16:35:47 +01:00
|
|
|
#include "pluginhandler.hxx"
|
|
|
|
|
|
|
|
#include <clang/Frontend/CompilerInstance.h>
|
|
|
|
#include <clang/Frontend/FrontendPluginRegistry.h>
|
2013-04-04 12:52:04 +02:00
|
|
|
#include <clang/Lex/PPCallbacks.h>
|
2013-02-02 16:35:47 +01:00
|
|
|
#include <stdio.h>
|
2013-02-09 18:47:55 +01:00
|
|
|
#include <sys/stat.h>
|
2013-02-02 16:35:47 +01:00
|
|
|
#include <unistd.h>
|
|
|
|
|
2013-02-02 18:34:12 +01:00
|
|
|
/*
|
|
|
|
This source file manages all plugin actions. It is not necessary to modify this
|
|
|
|
file when adding new actions.
|
|
|
|
*/
|
2013-06-05 23:32:16 +02:00
|
|
|
|
|
|
|
namespace
|
|
|
|
{
|
|
|
|
|
|
|
|
bool isPrefix( const string& prefix, const string& full)
|
|
|
|
{
|
|
|
|
return full.compare(0, prefix.size(), prefix) == 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
2013-02-02 16:35:47 +01:00
|
|
|
namespace loplugin
|
|
|
|
{
|
|
|
|
|
2013-02-02 17:45:18 +01:00
|
|
|
struct PluginData
|
|
|
|
{
|
2014-01-27 13:09:20 +01:00
|
|
|
Plugin* (*create)( const Plugin::InstantiationData& );
|
2013-02-02 17:45:18 +01:00
|
|
|
Plugin* object;
|
|
|
|
const char* optionName;
|
2013-08-06 17:57:45 +02:00
|
|
|
bool isPPCallback;
|
2014-01-27 13:09:20 +01:00
|
|
|
bool byDefault;
|
2013-02-02 17:45:18 +01:00
|
|
|
};
|
|
|
|
|
|
|
|
const int MAX_PLUGINS = 100;
|
|
|
|
static PluginData plugins[ MAX_PLUGINS ];
|
|
|
|
static int pluginCount = 0;
|
|
|
|
static bool pluginObjectsCreated = false;
|
|
|
|
|
2013-03-21 16:42:10 +01:00
|
|
|
PluginHandler::PluginHandler( CompilerInstance& compiler, const vector< string >& args )
|
|
|
|
: compiler( compiler )
|
|
|
|
, rewriter( compiler.getSourceManager(), compiler.getLangOpts())
|
2013-02-09 18:47:55 +01:00
|
|
|
, scope( "mainfile" )
|
2013-02-02 16:35:47 +01:00
|
|
|
{
|
2014-02-15 15:25:03 +01:00
|
|
|
set< string > rewriters;
|
2013-02-09 18:47:55 +01:00
|
|
|
for( vector< string >::const_iterator it = args.begin();
|
|
|
|
it != args.end();
|
|
|
|
++it )
|
2013-02-02 17:45:18 +01:00
|
|
|
{
|
2013-02-09 18:47:55 +01:00
|
|
|
if( it->size() >= 2 && (*it)[ 0 ] == '-' && (*it)[ 1 ] == '-' )
|
|
|
|
handleOption( it->substr( 2 ));
|
|
|
|
else
|
2014-02-15 15:25:03 +01:00
|
|
|
rewriters.insert( *it );
|
2013-02-02 17:45:18 +01:00
|
|
|
}
|
2014-02-15 15:25:03 +01:00
|
|
|
createPlugins( rewriters );
|
2013-02-02 17:45:18 +01:00
|
|
|
pluginObjectsCreated = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
PluginHandler::~PluginHandler()
|
|
|
|
{
|
|
|
|
for( int i = 0;
|
|
|
|
i < pluginCount;
|
|
|
|
++i )
|
|
|
|
if( plugins[ i ].object != NULL )
|
2013-04-04 12:52:04 +02:00
|
|
|
{
|
|
|
|
// PPCallbacks is owned by preprocessor object, don't delete those
|
2013-08-06 17:57:45 +02:00
|
|
|
if( !plugins[ i ].isPPCallback )
|
2013-04-04 12:52:04 +02:00
|
|
|
delete plugins[ i ].object;
|
|
|
|
}
|
2013-02-02 17:45:18 +01:00
|
|
|
}
|
|
|
|
|
2013-02-09 18:47:55 +01:00
|
|
|
void PluginHandler::handleOption( const string& option )
|
|
|
|
{
|
|
|
|
if( option.substr( 0, 6 ) == "scope=" )
|
|
|
|
{
|
|
|
|
scope = option.substr( 6 );
|
|
|
|
if( scope == "mainfile" || scope == "all" )
|
|
|
|
; // ok
|
|
|
|
else
|
|
|
|
{
|
|
|
|
struct stat st;
|
|
|
|
if( stat(( SRCDIR "/" + scope ).c_str(), &st ) != 0 || !S_ISDIR( st.st_mode ))
|
|
|
|
report( DiagnosticsEngine::Fatal, "unknown scope %0 (no such module directory)" ) << scope;
|
|
|
|
}
|
|
|
|
}
|
2014-01-27 13:09:20 +01:00
|
|
|
else if( option.substr( 0, 14 ) == "warnings-only=" )
|
|
|
|
{
|
|
|
|
warningsOnly = option.substr(14);
|
|
|
|
}
|
2013-02-09 18:47:55 +01:00
|
|
|
else
|
|
|
|
report( DiagnosticsEngine::Fatal, "unknown option %0" ) << option;
|
|
|
|
}
|
|
|
|
|
2014-02-15 15:25:03 +01:00
|
|
|
void PluginHandler::createPlugins( set< string > rewriters )
|
2013-02-09 18:47:55 +01:00
|
|
|
{
|
|
|
|
for( int i = 0;
|
|
|
|
i < pluginCount;
|
|
|
|
++i )
|
|
|
|
{
|
2014-02-15 15:25:03 +01:00
|
|
|
if( rewriters.erase( plugins[i].optionName ) != 0 )
|
|
|
|
plugins[ i ].object = plugins[ i ].create( Plugin::InstantiationData { plugins[ i ].optionName, *this, compiler, &rewriter } );
|
|
|
|
else if( plugins[ i ].byDefault )
|
|
|
|
plugins[ i ].object = plugins[ i ].create( Plugin::InstantiationData { plugins[ i ].optionName, *this, compiler, NULL } );
|
2013-02-09 18:47:55 +01:00
|
|
|
}
|
2014-02-15 15:25:03 +01:00
|
|
|
for( auto r: rewriters )
|
|
|
|
report( DiagnosticsEngine::Fatal, "unknown plugin tool %0" ) << r;
|
2013-02-09 18:47:55 +01:00
|
|
|
}
|
|
|
|
|
2014-01-27 13:09:20 +01:00
|
|
|
void PluginHandler::registerPlugin( Plugin* (*create)( const Plugin::InstantiationData& ), const char* optionName, bool isPPCallback, bool byDefault )
|
2013-02-02 17:45:18 +01:00
|
|
|
{
|
|
|
|
assert( !pluginObjectsCreated );
|
|
|
|
assert( pluginCount < MAX_PLUGINS );
|
|
|
|
plugins[ pluginCount ].create = create;
|
|
|
|
plugins[ pluginCount ].object = NULL;
|
|
|
|
plugins[ pluginCount ].optionName = optionName;
|
2013-08-06 17:57:45 +02:00
|
|
|
plugins[ pluginCount ].isPPCallback = isPPCallback;
|
2014-01-27 13:09:20 +01:00
|
|
|
plugins[ pluginCount ].byDefault = byDefault;
|
2013-02-02 17:45:18 +01:00
|
|
|
++pluginCount;
|
2013-02-02 16:35:47 +01:00
|
|
|
}
|
|
|
|
|
2014-01-27 13:09:20 +01:00
|
|
|
DiagnosticBuilder PluginHandler::report( DiagnosticsEngine::Level level, const char* plugin, StringRef message, CompilerInstance& compiler,
|
|
|
|
SourceLocation loc )
|
|
|
|
{
|
|
|
|
DiagnosticsEngine& diag = compiler.getDiagnostics();
|
|
|
|
// Do some mappings (e.g. for -Werror) that clang does not do for custom messages for some reason.
|
|
|
|
if( level == DiagnosticsEngine::Warning && diag.getWarningsAsErrors() && (plugin == nullptr || plugin != warningsOnly))
|
|
|
|
level = DiagnosticsEngine::Error;
|
|
|
|
if( level == DiagnosticsEngine::Error && diag.getErrorsAsFatal())
|
|
|
|
level = DiagnosticsEngine::Fatal;
|
|
|
|
string fullMessage = ( message + " [loplugin" ).str();
|
|
|
|
if( plugin )
|
|
|
|
{
|
|
|
|
fullMessage += ":";
|
|
|
|
fullMessage += plugin;
|
|
|
|
}
|
|
|
|
fullMessage += "]";
|
|
|
|
if( loc.isValid())
|
2014-01-31 09:50:21 +01:00
|
|
|
return diag.Report( loc, compat::getCustomDiagID(diag, level, fullMessage) );
|
2014-01-27 13:09:20 +01:00
|
|
|
else
|
2014-01-31 09:50:21 +01:00
|
|
|
return diag.Report( compat::getCustomDiagID(diag, level, fullMessage) );
|
2014-01-27 13:09:20 +01:00
|
|
|
}
|
|
|
|
|
2013-02-02 19:31:24 +01:00
|
|
|
DiagnosticBuilder PluginHandler::report( DiagnosticsEngine::Level level, StringRef message, SourceLocation loc )
|
|
|
|
{
|
2014-01-27 13:09:20 +01:00
|
|
|
return report( level, nullptr, message, compiler, loc );
|
2013-02-02 19:31:24 +01:00
|
|
|
}
|
|
|
|
|
2014-02-20 19:47:01 +01:00
|
|
|
bool PluginHandler::addRemoval( SourceLocation loc )
|
|
|
|
{
|
|
|
|
return removals.insert( loc ).second;
|
|
|
|
}
|
|
|
|
|
2013-02-02 16:35:47 +01:00
|
|
|
void PluginHandler::HandleTranslationUnit( ASTContext& context )
|
|
|
|
{
|
|
|
|
if( context.getDiagnostics().hasErrorOccurred())
|
|
|
|
return;
|
2013-02-02 17:45:18 +01:00
|
|
|
for( int i = 0;
|
|
|
|
i < pluginCount;
|
|
|
|
++i )
|
2013-02-02 16:35:47 +01:00
|
|
|
{
|
2013-02-02 17:45:18 +01:00
|
|
|
if( plugins[ i ].object != NULL )
|
|
|
|
plugins[ i ].object->run();
|
2013-02-02 16:35:47 +01:00
|
|
|
}
|
|
|
|
for( Rewriter::buffer_iterator it = rewriter.buffer_begin();
|
|
|
|
it != rewriter.buffer_end();
|
|
|
|
++it )
|
|
|
|
{
|
|
|
|
const FileEntry* e = context.getSourceManager().getFileEntryForID( it->first );
|
2013-08-14 19:00:21 +02:00
|
|
|
if( e == NULL )
|
|
|
|
continue; // Failed modification because of a macro expansion?
|
2013-02-02 16:35:47 +01:00
|
|
|
/* Check where the file actually is, and warn about cases where modification
|
|
|
|
most probably doesn't matter (generated files in workdir).
|
2013-10-31 14:02:40 +01:00
|
|
|
The order here is important, as INSTDIR and WORKDIR are often in SRCDIR/BUILDDIR,
|
2013-02-02 16:35:47 +01:00
|
|
|
and BUILDDIR is sometimes in SRCDIR. */
|
|
|
|
string modifyFile;
|
2013-02-09 18:47:55 +01:00
|
|
|
const char* pathWarning = NULL;
|
|
|
|
bool skip = false;
|
2013-10-31 14:02:40 +01:00
|
|
|
if( strncmp( e->getName(), WORKDIR "/", strlen( WORKDIR "/" )) == 0 )
|
2013-02-09 18:47:55 +01:00
|
|
|
pathWarning = "modified source in workdir/ : %0";
|
2013-02-09 18:31:38 +01:00
|
|
|
else if( strcmp( SRCDIR, BUILDDIR ) != 0 && strncmp( e->getName(), BUILDDIR "/", strlen( BUILDDIR "/" )) == 0 )
|
2013-02-09 18:47:55 +01:00
|
|
|
pathWarning = "modified source in build dir : %0";
|
2013-02-09 18:31:38 +01:00
|
|
|
else if( strncmp( e->getName(), SRCDIR "/", strlen( SRCDIR "/" )) == 0 )
|
2013-02-02 16:35:47 +01:00
|
|
|
; // ok
|
|
|
|
else
|
|
|
|
{
|
2013-02-09 18:47:55 +01:00
|
|
|
pathWarning = "modified source in unknown location, not modifying : %0";
|
|
|
|
skip = true;
|
2013-02-02 16:35:47 +01:00
|
|
|
}
|
|
|
|
if( modifyFile.empty())
|
|
|
|
modifyFile = e->getName();
|
2013-10-31 14:02:40 +01:00
|
|
|
// Check whether the modified file is in the wanted scope
|
2013-02-09 18:47:55 +01:00
|
|
|
if( scope == "mainfile" )
|
|
|
|
{
|
|
|
|
if( it->first != context.getSourceManager().getMainFileID())
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
else if( scope == "all" )
|
|
|
|
; // ok
|
|
|
|
else // scope is module
|
|
|
|
{
|
2013-06-05 23:32:16 +02:00
|
|
|
if( !( isPrefix( SRCDIR "/" + scope + "/", modifyFile ) || isPrefix( SRCDIR "/include/" + scope + "/", modifyFile ) ) )
|
2013-02-09 18:47:55 +01:00
|
|
|
continue;
|
|
|
|
}
|
|
|
|
// Warn only now, so that files not in scope do not cause warnings.
|
|
|
|
if( pathWarning != NULL )
|
|
|
|
report( DiagnosticsEngine::Warning, pathWarning ) << e->getName();
|
|
|
|
if( skip )
|
|
|
|
continue;
|
2013-02-02 16:35:47 +01:00
|
|
|
char* filename = new char[ modifyFile.length() + 100 ];
|
|
|
|
sprintf( filename, "%s.new.%d", modifyFile.c_str(), getpid());
|
|
|
|
string error;
|
|
|
|
bool ok = false;
|
|
|
|
raw_fd_ostream ostream( filename, error );
|
|
|
|
if( error.empty())
|
|
|
|
{
|
|
|
|
it->second.write( ostream );
|
|
|
|
ostream.close();
|
|
|
|
if( !ostream.has_error() && rename( filename, modifyFile.c_str()) == 0 )
|
|
|
|
ok = true;
|
|
|
|
}
|
|
|
|
ostream.clear_error();
|
|
|
|
unlink( filename );
|
|
|
|
if( !ok )
|
2013-02-02 19:38:56 +01:00
|
|
|
report( DiagnosticsEngine::Error, "cannot write modified source to %0 (%1)" ) << modifyFile << error;
|
2013-02-02 16:35:47 +01:00
|
|
|
delete[] filename;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2013-03-21 16:17:07 +01:00
|
|
|
ASTConsumer* LibreOfficeAction::CreateASTConsumer( CompilerInstance& Compiler, StringRef )
|
2013-02-02 16:35:47 +01:00
|
|
|
{
|
2013-03-21 16:42:10 +01:00
|
|
|
return new PluginHandler( Compiler, _args );
|
2013-02-02 16:35:47 +01:00
|
|
|
}
|
|
|
|
|
2013-03-21 16:17:07 +01:00
|
|
|
bool LibreOfficeAction::ParseArgs( const CompilerInstance&, const vector< string >& args )
|
2013-02-02 16:35:47 +01:00
|
|
|
{
|
|
|
|
_args = args;
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static FrontendPluginRegistry::Add< loplugin::LibreOfficeAction > X( "loplugin", "LibreOffice compile check plugin" );
|
|
|
|
|
|
|
|
} // namespace
|
2013-09-21 14:42:35 +01:00
|
|
|
|
|
|
|
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
|