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 .
*
*/
2017-04-14 08:42:15 +10:00
# include <memory>
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>
2016-12-18 14:20:19 +01:00
# if defined _WIN32
# include <process.h>
# else
2013-02-09 18:47:55 +01:00
# include <sys/stat.h>
2013-02-02 16:35:47 +01:00
# include <unistd.h>
2016-12-18 14:20:19 +01:00
# endif
2013-02-02 16:35:47 +01:00
2017-06-20 10:26:46 +02:00
/**
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
2017-06-20 10:26:46 +02:00
static bool isPrefix ( const std : : string & prefix , const std : : string & full )
2013-06-05 23:32:16 +02:00
{
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
2017-06-20 10:26:46 +02:00
{
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 ;
2017-06-20 10:26:46 +02:00
} ;
2013-02-02 17:45:18 +01:00
const int MAX_PLUGINS = 100 ;
static PluginData plugins [ MAX_PLUGINS ] ;
static int pluginCount = 0 ;
2015-09-25 11:41:53 +01:00
static bool bPluginObjectsCreated = false ;
2017-06-19 13:44:14 +02:00
static bool unitTestMode = false ;
2013-02-02 17:45:18 +01:00
2017-06-20 10:26:46 +02:00
PluginHandler : : PluginHandler ( CompilerInstance & compiler , const std : : vector < std : : string > & args )
2013-03-21 16:42:10 +01:00
: compiler ( compiler )
, rewriter ( compiler . getSourceManager ( ) , compiler . getLangOpts ( ) )
2013-02-09 18:47:55 +01:00
, scope ( " mainfile " )
2016-04-26 17:33:47 +02:00
, warningsAsErrors ( false )
2017-06-20 10:26:46 +02:00
{
std : : set < std : : string > rewriters ;
for ( std : : string const & arg : args )
2013-02-02 17:45:18 +01:00
{
2017-06-19 09:00:59 +02:00
if ( arg . size ( ) > = 2 & & arg [ 0 ] = = ' - ' & & arg [ 1 ] = = ' - ' )
handleOption ( arg . substr ( 2 ) ) ;
2013-02-09 18:47:55 +01:00
else
2017-06-19 09:00:59 +02:00
rewriters . insert ( arg ) ;
2013-02-02 17:45:18 +01:00
}
2014-02-15 15:25:03 +01:00
createPlugins ( rewriters ) ;
2015-09-25 11:41:53 +01:00
bPluginObjectsCreated = true ;
2017-06-20 10:26:46 +02:00
}
2013-02-02 17:45:18 +01:00
PluginHandler : : ~ PluginHandler ( )
2017-06-20 10:26:46 +02:00
{
for ( int i = 0 ; i < pluginCount ; + + i )
2013-02-02 17:45:18 +01:00
if ( plugins [ i ] . object ! = NULL )
2017-06-20 10:26:46 +02:00
{
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 ;
2017-06-20 10:26:46 +02:00
}
}
2013-02-02 17:45:18 +01:00
2017-06-19 13:44:14 +02:00
bool PluginHandler : : isUnitTestMode ( )
2017-06-20 10:26:46 +02:00
{
2017-06-19 13:44:14 +02:00
return unitTestMode ;
2017-06-20 10:26:46 +02:00
}
2017-06-19 13:44:14 +02:00
2017-06-20 10:26:46 +02:00
void PluginHandler : : handleOption ( const std : : string & option )
{
2013-02-09 18:47:55 +01:00
if ( option . substr ( 0 , 6 ) = = " scope= " )
2017-06-20 10:26:46 +02:00
{
2013-02-09 18:47:55 +01:00
scope = option . substr ( 6 ) ;
if ( scope = = " mainfile " | | scope = = " all " )
; // ok
else
2017-06-20 10:26:46 +02:00
{
2013-02-09 18:47:55 +01:00
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 ;
}
2017-06-20 10:26:46 +02:00
}
2014-01-27 13:09:20 +01:00
else if ( option . substr ( 0 , 14 ) = = " warnings-only= " )
2017-06-20 10:26:46 +02:00
{
2014-01-27 13:09:20 +01:00
warningsOnly = option . substr ( 14 ) ;
2017-06-20 10:26:46 +02:00
}
2016-04-26 17:33:47 +02:00
else if ( option = = " warnings-as-errors " )
warningsAsErrors = true ;
2017-06-19 09:00:59 +02:00
else if ( option = = " unit-test-mode " )
unitTestMode = true ;
2013-02-09 18:47:55 +01:00
else
report ( DiagnosticsEngine : : Fatal , " unknown option %0 " ) < < option ;
2017-06-20 10:26:46 +02:00
}
2013-02-09 18:47:55 +01:00
2017-06-20 10:26:46 +02:00
void PluginHandler : : createPlugins ( std : : set < std : : string > rewriters )
{
for ( int i = 0 ; i < pluginCount ; + + i )
2013-02-09 18:47:55 +01:00
{
2017-06-19 13:44:14 +02:00
const char * name = plugins [ i ] . optionName ;
if ( rewriters . erase ( name ) ! = 0 )
plugins [ i ] . object = plugins [ i ] . create ( Plugin : : InstantiationData { name , * this , compiler , & rewriter } ) ;
2014-02-15 15:25:03 +01:00
else if ( plugins [ i ] . byDefault )
2017-06-19 13:44:14 +02:00
plugins [ i ] . object = plugins [ i ] . create ( Plugin : : InstantiationData { name , * this , compiler , NULL } ) ;
else if ( unitTestMode & & strcmp ( name , " unusedmethodsremove " ) ! = 0 & & strcmp ( name , " unusedfieldsremove " ) ! = 0 )
plugins [ i ] . object = plugins [ i ] . create ( Plugin : : InstantiationData { name , * this , compiler , NULL } ) ;
2017-06-20 10:26:46 +02:00
}
2014-02-15 15:25:03 +01:00
for ( auto r : rewriters )
report ( DiagnosticsEngine : : Fatal , " unknown plugin tool %0 " ) < < r ;
2017-06-20 10:26:46 +02:00
}
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 )
2017-06-20 10:26:46 +02:00
{
2015-09-25 11:41:53 +01:00
assert ( ! bPluginObjectsCreated ) ;
2013-02-02 17:45:18 +01:00
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 ;
2017-06-20 10:26:46 +02:00
}
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 )
2017-06-20 10:26:46 +02:00
{
2014-01-27 13:09:20 +01:00
DiagnosticsEngine & diag = compiler . getDiagnostics ( ) ;
// Do some mappings (e.g. for -Werror) that clang does not do for custom messages for some reason.
2016-04-26 17:33:47 +02:00
if ( level = = DiagnosticsEngine : : Warning & & ( ( diag . getWarningsAsErrors ( ) & & ( plugin = = nullptr | | plugin ! = warningsOnly ) ) | | warningsAsErrors ) )
2014-01-27 13:09:20 +01:00
level = DiagnosticsEngine : : Error ;
if ( level = = DiagnosticsEngine : : Error & & diag . getErrorsAsFatal ( ) )
level = DiagnosticsEngine : : Fatal ;
2017-06-20 10:26:46 +02:00
std : : string fullMessage = ( message + " [loplugin " ) . str ( ) ;
2014-01-27 13:09:20 +01:00
if ( plugin )
2017-06-20 10:26:46 +02:00
{
2014-01-27 13:09:20 +01:00
fullMessage + = " : " ;
fullMessage + = plugin ;
2017-06-20 10:26:46 +02:00
}
2014-01-27 13:09:20 +01:00
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 ) ) ;
2017-06-20 10:26:46 +02:00
}
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 )
2017-06-20 10:26:46 +02:00
{
2014-01-27 13:09:20 +01:00
return report ( level , nullptr , message , compiler , loc ) ;
2017-06-20 10:26:46 +02:00
}
2013-02-02 19:31:24 +01:00
2014-02-20 19:47:01 +01:00
bool PluginHandler : : addRemoval ( SourceLocation loc )
2017-06-20 10:26:46 +02:00
{
2014-02-20 19:47:01 +01:00
return removals . insert ( loc ) . second ;
2017-06-20 10:26:46 +02:00
}
2014-02-20 19:47:01 +01:00
2013-02-02 16:35:47 +01:00
void PluginHandler : : HandleTranslationUnit ( ASTContext & context )
2017-06-20 10:26:46 +02:00
{
2013-02-02 16:35:47 +01:00
if ( context . getDiagnostics ( ) . hasErrorOccurred ( ) )
return ;
2016-10-16 19:43:15 +02:00
StringRef const mainFileName = context . getSourceManager ( ) . getFileEntryForID ( context . getSourceManager ( ) . getMainFileID ( ) ) - > getName ( ) ;
if ( mainFileName . endswith ( " .ii " ) )
2015-10-30 15:15:46 +01:00
{
report ( DiagnosticsEngine : : Fatal ,
" input file has suffix .ii: \" %0 \" \n highly suspicious, probably ccache generated, this will break warning suppressions; export CCACHE_CPP2=1 to prevent this " ) < < mainFileName ;
return ;
}
2017-06-20 10:26:46 +02:00
for ( int i = 0 ; i < pluginCount ; + + i )
{
2013-02-02 17:45:18 +01:00
if ( plugins [ i ] . object ! = NULL )
2017-06-20 10:26:46 +02:00
{
2017-06-19 09:00:59 +02:00
// When in unit-test mode, ignore plugins whose names don't match the filename of the test,
// so that we only generate warnings for the plugin that we want to test.
if ( ! unitTestMode | | mainFileName . find ( plugins [ i ] . optionName ) ! = StringRef : : npos )
plugins [ i ] . object - > run ( ) ;
2013-02-02 16:35:47 +01:00
}
2017-06-20 10:26:46 +02:00
}
2017-01-27 10:46:15 +01:00
# if defined _WIN32
//TODO: make the call to 'rename' work on Windows (where the renamed-to
// original file is probably still held open somehow):
rewriter . overwriteChangedFiles ( ) ;
# else
2013-02-02 16:35:47 +01:00
for ( Rewriter : : buffer_iterator it = rewriter . buffer_begin ( ) ;
it ! = rewriter . buffer_end ( ) ;
+ + it )
2017-06-20 10:26:46 +02:00
{
2013-02-02 16:35:47 +01:00
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 . */
2017-06-20 10:26:46 +02:00
std : : string modifyFile ;
2013-02-09 18:47:55 +01:00
const char * pathWarning = NULL ;
2015-09-25 11:41:53 +01:00
bool bSkip = false ;
2016-10-16 19:43:15 +02:00
StringRef const name = e - > getName ( ) ;
if ( name . startswith ( WORKDIR " / " ) )
2013-02-09 18:47:55 +01:00
pathWarning = " modified source in workdir/ : %0 " ;
2016-10-16 19:43:15 +02:00
else if ( strcmp ( SRCDIR , BUILDDIR ) ! = 0 & & name . startswith ( BUILDDIR " / " ) )
2013-02-09 18:47:55 +01:00
pathWarning = " modified source in build dir : %0 " ;
2016-10-16 19:43:15 +02:00
else if ( name . startswith ( SRCDIR " / " ) )
2013-02-02 16:35:47 +01:00
; // ok
else
2017-06-20 10:26:46 +02:00
{
2013-02-09 18:47:55 +01:00
pathWarning = " modified source in unknown location, not modifying : %0 " ;
2015-09-25 11:41:53 +01:00
bSkip = true ;
2017-06-20 10:26:46 +02:00
}
2013-02-02 16:35:47 +01:00
if ( modifyFile . empty ( ) )
2016-10-16 19:43:15 +02:00
modifyFile = name ;
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 " )
2017-06-20 10:26:46 +02:00
{
2013-02-09 18:47:55 +01:00
if ( it - > first ! = context . getSourceManager ( ) . getMainFileID ( ) )
continue ;
2017-06-20 10:26:46 +02:00
}
2013-02-09 18:47:55 +01:00
else if ( scope = = " all " )
; // ok
else // scope is module
2017-06-20 10:26:46 +02:00
{
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 ;
2017-06-20 10:26:46 +02:00
}
2013-02-09 18:47:55 +01:00
// Warn only now, so that files not in scope do not cause warnings.
if ( pathWarning ! = NULL )
2016-10-16 19:43:15 +02:00
report ( DiagnosticsEngine : : Warning , pathWarning ) < < name ;
2015-09-25 11:41:53 +01:00
if ( bSkip )
2013-02-09 18:47:55 +01:00
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 ( ) ) ;
2017-06-20 10:26:46 +02:00
std : : string error ;
2015-09-25 11:41:53 +01:00
bool bOk = false ;
2014-02-25 10:15:28 +01:00
std : : unique_ptr < raw_fd_ostream > ostream (
compat : : create_raw_fd_ostream ( filename , error ) ) ;
2013-02-02 16:35:47 +01:00
if ( error . empty ( ) )
2017-06-20 10:26:46 +02:00
{
2014-02-25 10:15:28 +01:00
it - > second . write ( * ostream ) ;
ostream - > close ( ) ;
if ( ! ostream - > has_error ( ) & & rename ( filename , modifyFile . c_str ( ) ) = = 0 )
2015-09-25 11:41:53 +01:00
bOk = true ;
2017-06-20 10:26:46 +02:00
}
2014-02-25 10:15:28 +01:00
ostream - > clear_error ( ) ;
2013-02-02 16:35:47 +01:00
unlink ( filename ) ;
2015-09-25 11:41:53 +01:00
if ( ! bOk )
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 ;
}
2017-06-20 10:26:46 +02:00
# endif
}
2013-02-02 16:35:47 +01:00
2016-02-26 12:50:16 +01:00
# if CLANG_VERSION >= 30600
2014-08-11 15:16:08 +02:00
std : : unique_ptr < ASTConsumer > LibreOfficeAction : : CreateASTConsumer ( CompilerInstance & Compiler , StringRef )
2017-06-20 10:26:46 +02:00
{
2015-09-29 15:04:06 +02:00
return llvm : : make_unique < PluginHandler > ( Compiler , _args ) ;
2017-06-20 10:26:46 +02:00
}
2014-08-11 15:16:08 +02:00
# else
2013-03-21 16:17:07 +01:00
ASTConsumer * LibreOfficeAction : : CreateASTConsumer ( CompilerInstance & Compiler , StringRef )
2017-06-20 10:26:46 +02:00
{
2013-03-21 16:42:10 +01:00
return new PluginHandler ( Compiler , _args ) ;
2017-06-20 10:26:46 +02:00
}
2014-08-11 15:16:08 +02:00
# endif
2013-02-02 16:35:47 +01:00
2017-06-20 10:26:46 +02:00
bool LibreOfficeAction : : ParseArgs ( const CompilerInstance & , const std : : vector < std : : string > & args )
{
2013-02-02 16:35:47 +01:00
_args = args ;
return true ;
2017-06-20 10:26:46 +02:00
}
2013-02-02 16:35:47 +01:00
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: */