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 bPluginObjectsCreated
= false;
54 PluginHandler::PluginHandler( CompilerInstance
& compiler
, const vector
< string
>& args
)
55 : compiler( compiler
)
56 , rewriter( compiler
.getSourceManager(), compiler
.getLangOpts())
58 , warningsAsErrors( false )
60 set
< string
> rewriters
;
61 for( vector
< string
>::const_iterator it
= args
.begin();
65 if( it
->size() >= 2 && (*it
)[ 0 ] == '-' && (*it
)[ 1 ] == '-' )
66 handleOption( it
->substr( 2 ));
68 rewriters
.insert( *it
);
70 createPlugins( rewriters
);
71 bPluginObjectsCreated
= true;
74 PluginHandler::~PluginHandler()
79 if( plugins
[ i
].object
!= NULL
)
81 // PPCallbacks is owned by preprocessor object, don't delete those
82 if( !plugins
[ i
].isPPCallback
)
83 delete plugins
[ i
].object
;
87 void PluginHandler::handleOption( const string
& option
)
89 if( option
.substr( 0, 6 ) == "scope=" )
91 scope
= option
.substr( 6 );
92 if( scope
== "mainfile" || scope
== "all" )
97 if( stat(( SRCDIR
"/" + scope
).c_str(), &st
) != 0 || !S_ISDIR( st
.st_mode
))
98 report( DiagnosticsEngine::Fatal
, "unknown scope %0 (no such module directory)" ) << scope
;
101 else if( option
.substr( 0, 14 ) == "warnings-only=" )
103 warningsOnly
= option
.substr(14);
105 else if( option
== "warnings-as-errors" )
106 warningsAsErrors
= true;
108 report( DiagnosticsEngine::Fatal
, "unknown option %0" ) << option
;
111 void PluginHandler::createPlugins( set
< string
> rewriters
)
117 if( rewriters
.erase( plugins
[i
].optionName
) != 0 )
118 plugins
[ i
].object
= plugins
[ i
].create( Plugin::InstantiationData
{ plugins
[ i
].optionName
, *this, compiler
, &rewriter
} );
119 else if( plugins
[ i
].byDefault
)
120 plugins
[ i
].object
= plugins
[ i
].create( Plugin::InstantiationData
{ plugins
[ i
].optionName
, *this, compiler
, NULL
} );
122 for( auto r
: rewriters
)
123 report( DiagnosticsEngine::Fatal
, "unknown plugin tool %0" ) << r
;
126 void PluginHandler::registerPlugin( Plugin
* (*create
)( const Plugin::InstantiationData
& ), const char* optionName
, bool isPPCallback
, bool byDefault
)
128 assert( !bPluginObjectsCreated
);
129 assert( pluginCount
< MAX_PLUGINS
);
130 plugins
[ pluginCount
].create
= create
;
131 plugins
[ pluginCount
].object
= NULL
;
132 plugins
[ pluginCount
].optionName
= optionName
;
133 plugins
[ pluginCount
].isPPCallback
= isPPCallback
;
134 plugins
[ pluginCount
].byDefault
= byDefault
;
138 DiagnosticBuilder
PluginHandler::report( DiagnosticsEngine::Level level
, const char* plugin
, StringRef message
, CompilerInstance
& compiler
,
141 DiagnosticsEngine
& diag
= compiler
.getDiagnostics();
142 // Do some mappings (e.g. for -Werror) that clang does not do for custom messages for some reason.
143 if( level
== DiagnosticsEngine::Warning
&& ((diag
.getWarningsAsErrors() && (plugin
== nullptr || plugin
!= warningsOnly
)) || warningsAsErrors
))
144 level
= DiagnosticsEngine::Error
;
145 if( level
== DiagnosticsEngine::Error
&& diag
.getErrorsAsFatal())
146 level
= DiagnosticsEngine::Fatal
;
147 string fullMessage
= ( message
+ " [loplugin" ).str();
151 fullMessage
+= plugin
;
155 return diag
.Report( loc
, compat::getCustomDiagID(diag
, level
, fullMessage
) );
157 return diag
.Report( compat::getCustomDiagID(diag
, level
, fullMessage
) );
160 DiagnosticBuilder
PluginHandler::report( DiagnosticsEngine::Level level
, StringRef message
, SourceLocation loc
)
162 return report( level
, nullptr, message
, compiler
, loc
);
165 bool PluginHandler::addRemoval( SourceLocation loc
)
167 return removals
.insert( loc
).second
;
170 void PluginHandler::HandleTranslationUnit( ASTContext
& context
)
172 if( context
.getDiagnostics().hasErrorOccurred())
174 StringRef
const mainFileName
= context
.getSourceManager().getFileEntryForID(context
.getSourceManager().getMainFileID())->getName();
175 if (mainFileName
.endswith(".ii"))
177 report(DiagnosticsEngine::Fatal
,
178 "input file has suffix .ii: \"%0\"\nhighly suspicious, probably ccache generated, this will break warning suppressions; export CCACHE_CPP2=1 to prevent this") << mainFileName
;
186 if( plugins
[ i
].object
!= NULL
)
187 plugins
[ i
].object
->run();
189 for( Rewriter::buffer_iterator it
= rewriter
.buffer_begin();
190 it
!= rewriter
.buffer_end();
193 const FileEntry
* e
= context
.getSourceManager().getFileEntryForID( it
->first
);
195 continue; // Failed modification because of a macro expansion?
196 /* Check where the file actually is, and warn about cases where modification
197 most probably doesn't matter (generated files in workdir).
198 The order here is important, as INSTDIR and WORKDIR are often in SRCDIR/BUILDDIR,
199 and BUILDDIR is sometimes in SRCDIR. */
201 const char* pathWarning
= NULL
;
203 StringRef
const name
= e
->getName();
204 if( name
.startswith(WORKDIR
"/") )
205 pathWarning
= "modified source in workdir/ : %0";
206 else if( strcmp( SRCDIR
, BUILDDIR
) != 0 && name
.startswith(BUILDDIR
"/") )
207 pathWarning
= "modified source in build dir : %0";
208 else if( name
.startswith(SRCDIR
"/") )
212 pathWarning
= "modified source in unknown location, not modifying : %0";
215 if( modifyFile
.empty())
217 // Check whether the modified file is in the wanted scope
218 if( scope
== "mainfile" )
220 if( it
->first
!= context
.getSourceManager().getMainFileID())
223 else if( scope
== "all" )
225 else // scope is module
227 if( !( isPrefix( SRCDIR
"/" + scope
+ "/", modifyFile
) || isPrefix( SRCDIR
"/include/" + scope
+ "/", modifyFile
) ) )
230 // Warn only now, so that files not in scope do not cause warnings.
231 if( pathWarning
!= NULL
)
232 report( DiagnosticsEngine::Warning
, pathWarning
) << name
;
235 char* filename
= new char[ modifyFile
.length() + 100 ];
236 sprintf( filename
, "%s.new.%d", modifyFile
.c_str(), getpid());
239 std::unique_ptr
<raw_fd_ostream
> ostream(
240 compat::create_raw_fd_ostream(filename
, error
) );
243 it
->second
.write( *ostream
);
245 if( !ostream
->has_error() && rename( filename
, modifyFile
.c_str()) == 0 )
248 ostream
->clear_error();
251 report( DiagnosticsEngine::Error
, "cannot write modified source to %0 (%1)" ) << modifyFile
<< error
;
256 #if CLANG_VERSION >= 30600
257 std::unique_ptr
<ASTConsumer
> LibreOfficeAction::CreateASTConsumer( CompilerInstance
& Compiler
, StringRef
)
259 return llvm::make_unique
<PluginHandler
>( Compiler
, _args
);
262 ASTConsumer
* LibreOfficeAction::CreateASTConsumer( CompilerInstance
& Compiler
, StringRef
)
264 return new PluginHandler( Compiler
, _args
);
268 bool LibreOfficeAction::ParseArgs( const CompilerInstance
&, const vector
< string
>& args
)
274 static FrontendPluginRegistry::Add
< loplugin::LibreOfficeAction
> X( "loplugin", "LibreOffice compile check plugin" );
278 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */