1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
3 * This file is part of the LibreOffice project.
7 * This file is distributed under the University of Illinois Open Source
8 * License. See LICENSE.TXT for details.
13 #include "pluginhandler.hxx"
15 #include <clang/Frontend/CompilerInstance.h>
16 #include <clang/Frontend/FrontendPluginRegistry.h>
17 #include <clang/Lex/PPCallbacks.h>
23 This source file manages all plugin actions. It is not necessary to modify this
24 file when adding new actions.
30 bool isPrefix( const string
& prefix
, const string
& full
)
32 return full
.compare(0, prefix
.size(), prefix
) == 0;
42 Plugin
* (*create
)( const Plugin::InstantiationData
& );
44 const char* optionName
;
49 const int MAX_PLUGINS
= 100;
50 static PluginData plugins
[ MAX_PLUGINS
];
51 static int pluginCount
= 0;
52 static bool pluginObjectsCreated
= false;
54 PluginHandler::PluginHandler( CompilerInstance
& compiler
, const vector
< string
>& args
)
55 : compiler( compiler
)
56 , rewriter( compiler
.getSourceManager(), compiler
.getLangOpts())
59 set
< string
> rewriters
;
60 for( vector
< string
>::const_iterator it
= args
.begin();
64 if( it
->size() >= 2 && (*it
)[ 0 ] == '-' && (*it
)[ 1 ] == '-' )
65 handleOption( it
->substr( 2 ));
67 rewriters
.insert( *it
);
69 createPlugins( rewriters
);
70 pluginObjectsCreated
= true;
73 PluginHandler::~PluginHandler()
78 if( plugins
[ i
].object
!= NULL
)
80 // PPCallbacks is owned by preprocessor object, don't delete those
81 if( !plugins
[ i
].isPPCallback
)
82 delete plugins
[ i
].object
;
86 void PluginHandler::handleOption( const string
& option
)
88 if( option
.substr( 0, 6 ) == "scope=" )
90 scope
= option
.substr( 6 );
91 if( scope
== "mainfile" || scope
== "all" )
96 if( stat(( SRCDIR
"/" + scope
).c_str(), &st
) != 0 || !S_ISDIR( st
.st_mode
))
97 report( DiagnosticsEngine::Fatal
, "unknown scope %0 (no such module directory)" ) << scope
;
100 else if( option
.substr( 0, 14 ) == "warnings-only=" )
102 warningsOnly
= option
.substr(14);
105 report( DiagnosticsEngine::Fatal
, "unknown option %0" ) << option
;
108 void PluginHandler::createPlugins( set
< string
> rewriters
)
114 if( rewriters
.erase( plugins
[i
].optionName
) != 0 )
115 plugins
[ i
].object
= plugins
[ i
].create( Plugin::InstantiationData
{ plugins
[ i
].optionName
, *this, compiler
, &rewriter
} );
116 else if( plugins
[ i
].byDefault
)
117 plugins
[ i
].object
= plugins
[ i
].create( Plugin::InstantiationData
{ plugins
[ i
].optionName
, *this, compiler
, NULL
} );
119 for( auto r
: rewriters
)
120 report( DiagnosticsEngine::Fatal
, "unknown plugin tool %0" ) << r
;
123 void PluginHandler::registerPlugin( Plugin
* (*create
)( const Plugin::InstantiationData
& ), const char* optionName
, bool isPPCallback
, bool byDefault
)
125 assert( !pluginObjectsCreated
);
126 assert( pluginCount
< MAX_PLUGINS
);
127 plugins
[ pluginCount
].create
= create
;
128 plugins
[ pluginCount
].object
= NULL
;
129 plugins
[ pluginCount
].optionName
= optionName
;
130 plugins
[ pluginCount
].isPPCallback
= isPPCallback
;
131 plugins
[ pluginCount
].byDefault
= byDefault
;
135 DiagnosticBuilder
PluginHandler::report( DiagnosticsEngine::Level level
, const char* plugin
, StringRef message
, CompilerInstance
& compiler
,
138 DiagnosticsEngine
& diag
= compiler
.getDiagnostics();
139 // Do some mappings (e.g. for -Werror) that clang does not do for custom messages for some reason.
140 if( level
== DiagnosticsEngine::Warning
&& diag
.getWarningsAsErrors() && (plugin
== nullptr || plugin
!= warningsOnly
))
141 level
= DiagnosticsEngine::Error
;
142 if( level
== DiagnosticsEngine::Error
&& diag
.getErrorsAsFatal())
143 level
= DiagnosticsEngine::Fatal
;
144 string fullMessage
= ( message
+ " [loplugin" ).str();
148 fullMessage
+= plugin
;
152 return diag
.Report( loc
, compat::getCustomDiagID(diag
, level
, fullMessage
) );
154 return diag
.Report( compat::getCustomDiagID(diag
, level
, fullMessage
) );
157 DiagnosticBuilder
PluginHandler::report( DiagnosticsEngine::Level level
, StringRef message
, SourceLocation loc
)
159 return report( level
, nullptr, message
, compiler
, loc
);
162 bool PluginHandler::addRemoval( SourceLocation loc
)
164 return removals
.insert( loc
).second
;
167 void PluginHandler::HandleTranslationUnit( ASTContext
& context
)
169 if( context
.getDiagnostics().hasErrorOccurred())
175 if( plugins
[ i
].object
!= NULL
)
176 plugins
[ i
].object
->run();
178 for( Rewriter::buffer_iterator it
= rewriter
.buffer_begin();
179 it
!= rewriter
.buffer_end();
182 const FileEntry
* e
= context
.getSourceManager().getFileEntryForID( it
->first
);
184 continue; // Failed modification because of a macro expansion?
185 /* Check where the file actually is, and warn about cases where modification
186 most probably doesn't matter (generated files in workdir).
187 The order here is important, as INSTDIR and WORKDIR are often in SRCDIR/BUILDDIR,
188 and BUILDDIR is sometimes in SRCDIR. */
190 const char* pathWarning
= NULL
;
192 if( strncmp( e
->getName(), WORKDIR
"/", strlen( WORKDIR
"/" )) == 0 )
193 pathWarning
= "modified source in workdir/ : %0";
194 else if( strcmp( SRCDIR
, BUILDDIR
) != 0 && strncmp( e
->getName(), BUILDDIR
"/", strlen( BUILDDIR
"/" )) == 0 )
195 pathWarning
= "modified source in build dir : %0";
196 else if( strncmp( e
->getName(), SRCDIR
"/", strlen( SRCDIR
"/" )) == 0 )
200 pathWarning
= "modified source in unknown location, not modifying : %0";
203 if( modifyFile
.empty())
204 modifyFile
= e
->getName();
205 // Check whether the modified file is in the wanted scope
206 if( scope
== "mainfile" )
208 if( it
->first
!= context
.getSourceManager().getMainFileID())
211 else if( scope
== "all" )
213 else // scope is module
215 if( !( isPrefix( SRCDIR
"/" + scope
+ "/", modifyFile
) || isPrefix( SRCDIR
"/include/" + scope
+ "/", modifyFile
) ) )
218 // Warn only now, so that files not in scope do not cause warnings.
219 if( pathWarning
!= NULL
)
220 report( DiagnosticsEngine::Warning
, pathWarning
) << e
->getName();
223 char* filename
= new char[ modifyFile
.length() + 100 ];
224 sprintf( filename
, "%s.new.%d", modifyFile
.c_str(), getpid());
227 std::unique_ptr
<raw_fd_ostream
> ostream(
228 compat::create_raw_fd_ostream(filename
, error
) );
231 it
->second
.write( *ostream
);
233 if( !ostream
->has_error() && rename( filename
, modifyFile
.c_str()) == 0 )
236 ostream
->clear_error();
239 report( DiagnosticsEngine::Error
, "cannot write modified source to %0 (%1)" ) << modifyFile
<< error
;
244 #if (__clang_major__ == 3 && __clang_minor__ >= 6) || __clang_major__ > 3
245 std::unique_ptr
<ASTConsumer
> LibreOfficeAction::CreateASTConsumer( CompilerInstance
& Compiler
, StringRef
)
247 return make_unique
<PluginHandler
>( Compiler
, _args
);
250 ASTConsumer
* LibreOfficeAction::CreateASTConsumer( CompilerInstance
& Compiler
, StringRef
)
252 return new PluginHandler( Compiler
, _args
);
256 bool LibreOfficeAction::ParseArgs( const CompilerInstance
&, const vector
< string
>& args
)
262 static FrontendPluginRegistry::Add
< loplugin::LibreOfficeAction
> X( "loplugin", "LibreOffice compile check plugin" );
266 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */