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 <system_error>
16 #include "pluginhandler.hxx"
18 #include <clang/Frontend/CompilerInstance.h>
19 #include <clang/Frontend/FrontendPluginRegistry.h>
20 #include <clang/Lex/PPCallbacks.h>
31 This source file manages all plugin actions. It is not necessary to modify this
32 file when adding new actions.
35 static bool isPrefix( const std::string
& prefix
, const std::string
& full
)
37 return full
.compare(0, prefix
.size(), prefix
) == 0;
45 Plugin
* (*create
)( const InstantiationData
& );
47 const char* optionName
;
54 const int MAX_PLUGINS
= 200;
55 static PluginData plugins
[ MAX_PLUGINS
];
56 static int pluginCount
= 0;
57 static bool bPluginObjectsCreated
= false;
58 static bool unitTestMode
= false;
60 StringRef
initMainFileName(CompilerInstance
& compiler
)
62 StringRef
const& fn(compiler
.getASTContext().getSourceManager().getFileEntryForID(
63 compiler
.getASTContext().getSourceManager().getMainFileID())->getName());
65 // stdin means icecream, so we can rely on -main-file-name containing the full path name
66 return compiler
.getCodeGenOpts().MainFileName
;
68 // this is always a full path name
72 PluginHandler::PluginHandler( CompilerInstance
& compiler
, const std::vector
< std::string
>& args
)
73 : compiler( compiler
)
74 , mainFileName(initMainFileName(compiler
))
75 , rewriter( compiler
.getSourceManager(), compiler
.getLangOpts())
77 , warningsAsErrors( false )
79 std::set
< std::string
> rewriters
;
80 for( std::string
const & arg
: args
)
82 if( arg
.size() >= 2 && arg
[ 0 ] == '-' && arg
[ 1 ] == '-' )
83 handleOption( arg
.substr( 2 ));
85 rewriters
.insert( arg
);
87 createPlugins( rewriters
);
88 bPluginObjectsCreated
= true;
91 PluginHandler::~PluginHandler()
93 for( int i
= 0; i
< pluginCount
; ++i
)
94 if( plugins
[ i
].object
!= NULL
)
96 // PPCallbacks is owned by preprocessor object, don't delete those
97 if( !plugins
[ i
].isPPCallback
)
98 delete plugins
[ i
].object
;
102 bool PluginHandler::isUnitTestMode()
107 void PluginHandler::handleOption( const std::string
& option
)
109 if( option
.substr( 0, 6 ) == "scope=" )
111 scope
= option
.substr( 6 );
112 if( scope
== "mainfile" || scope
== "all" )
116 #if !defined _WIN32 //TODO, S_ISDIR
118 if( stat(( SRCDIR
"/" + scope
).c_str(), &st
) != 0 || !S_ISDIR( st
.st_mode
))
119 report( DiagnosticsEngine::Fatal
, "unknown scope %0 (no such module directory)" ) << scope
;
123 else if( option
.substr( 0, 14 ) == "warnings-only=" )
125 warningsOnly
= option
.substr(14);
127 else if( option
== "warnings-as-errors" )
128 warningsAsErrors
= true;
129 else if( option
== "unit-test-mode" )
131 else if (option
== "debug")
134 report( DiagnosticsEngine::Fatal
, "unknown option %0" ) << option
;
137 void PluginHandler::createPlugins( std::set
< std::string
> rewriters
)
139 for( int i
= 0; i
< pluginCount
; ++i
)
141 const char* name
= plugins
[i
].optionName
;
142 // When in unit-test mode, ignore plugins whose names don't match the filename of the test,
143 // so that we only generate warnings for the plugin that we want to test.
144 if (unitTestMode
&& mainFileName
.find(plugins
[ i
].optionName
) == StringRef::npos
)
146 if( rewriters
.erase( name
) != 0 )
147 plugins
[ i
].object
= plugins
[ i
].create( InstantiationData
{ name
, *this, compiler
, &rewriter
} );
148 else if( plugins
[ i
].byDefault
)
149 plugins
[ i
].object
= plugins
[ i
].create( InstantiationData
{ name
, *this, compiler
, NULL
} );
150 else if( unitTestMode
&& strcmp(name
, "unusedmethodsremove") != 0 && strcmp(name
, "unusedfieldsremove") != 0)
151 plugins
[ i
].object
= plugins
[ i
].create( InstantiationData
{ name
, *this, compiler
, NULL
} );
153 for( auto r
: rewriters
)
154 report( DiagnosticsEngine::Fatal
, "unknown plugin tool %0" ) << r
;
155 // If there is a shared plugin, make it handle all plugins that it can handle.
156 for( int i
= 0; i
< pluginCount
; ++i
)
158 if( plugins
[ i
].isSharedPlugin
&& plugins
[ i
].object
!= nullptr )
160 Plugin
* plugin
= plugins
[ i
].object
;
161 for( int j
= 0; j
< pluginCount
; ++j
)
163 if( plugins
[ j
].object
!= nullptr
164 && plugin
->setSharedPlugin( plugins
[ j
].object
, plugins
[ j
].optionName
))
166 plugins
[ j
].disabledRun
= true;
173 void PluginHandler::registerPlugin( Plugin
* (*create
)( const InstantiationData
& ), const char* optionName
,
174 bool isPPCallback
, bool isSharedPlugin
, bool byDefault
)
176 assert( !bPluginObjectsCreated
);
177 assert( pluginCount
< MAX_PLUGINS
);
178 plugins
[ pluginCount
].create
= create
;
179 plugins
[ pluginCount
].object
= NULL
;
180 plugins
[ pluginCount
].optionName
= optionName
;
181 plugins
[ pluginCount
].isPPCallback
= isPPCallback
;
182 plugins
[ pluginCount
].isSharedPlugin
= isSharedPlugin
;
183 plugins
[ pluginCount
].byDefault
= byDefault
;
184 plugins
[ pluginCount
].disabledRun
= false;
188 DiagnosticBuilder
PluginHandler::report( DiagnosticsEngine::Level level
, const char* plugin
, StringRef message
, CompilerInstance
& compiler
,
191 DiagnosticsEngine
& diag
= compiler
.getDiagnostics();
192 // Do some mappings (e.g. for -Werror) that clang does not do for custom messages for some reason.
193 if( level
== DiagnosticsEngine::Warning
&& ((diag
.getWarningsAsErrors() && (plugin
== nullptr || plugin
!= warningsOnly
)) || warningsAsErrors
))
194 level
= DiagnosticsEngine::Error
;
195 if( level
== DiagnosticsEngine::Error
&& diag
.getErrorsAsFatal())
196 level
= DiagnosticsEngine::Fatal
;
197 std::string fullMessage
= ( message
+ " [loplugin" ).str();
201 fullMessage
+= plugin
;
205 return diag
.Report( loc
, diag
.getDiagnosticIDs()->getCustomDiagID(static_cast<DiagnosticIDs::Level
>(level
), fullMessage
) );
207 return diag
.Report( diag
.getDiagnosticIDs()->getCustomDiagID(static_cast<DiagnosticIDs::Level
>(level
), fullMessage
) );
210 DiagnosticBuilder
PluginHandler::report( DiagnosticsEngine::Level level
, StringRef message
, SourceLocation loc
)
212 return report( level
, nullptr, message
, compiler
, loc
);
215 bool PluginHandler::ignoreLocation(SourceLocation loc
) {
216 auto i
= ignored_
.find(loc
);
217 if (i
== ignored_
.end()) {
218 i
= ignored_
.emplace(loc
, checkIgnoreLocation(loc
)).first
;
223 bool PluginHandler::checkIgnoreLocation(SourceLocation loc
)
225 SourceLocation expansionLoc
= compiler
.getSourceManager().getExpansionLoc( loc
);
226 if( compiler
.getSourceManager().isInSystemHeader( expansionLoc
))
228 PresumedLoc presumedLoc
= compiler
.getSourceManager().getPresumedLoc( expansionLoc
);
229 if( presumedLoc
.isInvalid())
231 const char* bufferName
= presumedLoc
.getFilename();
232 if (bufferName
== NULL
233 || hasPathnamePrefix(bufferName
, SRCDIR
"/external/")
234 || isSamePathname(bufferName
, SRCDIR
"/sdext/source/pdfimport/wrapper/keyword_list") )
235 // workdir/CustomTarget/sdext/pdfimport/hash.cxx is generated from
236 // sdext/source/pdfimport/wrapper/keyword_list by gperf, which
237 // inserts various #line directives denoting the latter into the
238 // former, but fails to add a #line directive returning back to
239 // hash.cxx itself before the gperf generated boilerplate, so
240 // compilers erroneously consider errors in the boilerplate to come
241 // from keyword_list instead of hash.cxx (for Clang on Linux/macOS
242 // this is not an issue due to the '#pragma GCC system_header'
243 // generated into the start of hash.cxx, #if'ed for __GNUC__, but
244 // for clang-cl it is an issue)
246 if( hasPathnamePrefix(bufferName
, WORKDIR
"/") )
248 // workdir/CustomTarget/vcl/unx/kde4/tst_exclude_socket_notifiers.moc
250 // "../../../../../vcl/unx/kde4/tst_exclude_socket_notifiers.hxx",
251 // making the latter file erroneously match here; so strip any ".."
253 if (strstr(bufferName
, "/..") == nullptr) {
256 std::string
s(bufferName
);
257 normalizeDotDotInFilePath(s
);
258 if (hasPathnamePrefix(s
, WORKDIR
"/"))
261 if( hasPathnamePrefix(bufferName
, BUILDDIR
"/")
262 || hasPathnamePrefix(bufferName
, SRCDIR
"/") )
267 // If we overlap with a previous area we modified, we cannot perform this change
268 // without corrupting the source
269 bool PluginHandler::checkOverlap(SourceRange range
)
271 SourceManager
& SM
= compiler
.getSourceManager();
272 char const *p1
= SM
.getCharacterData( range
.getBegin() );
273 char const *p2
= SM
.getCharacterData( range
.getEnd() );
274 for (std::pair
<char const *, char const *> const & rPair
: mvModifiedRanges
)
276 if (rPair
.first
<= p1
&& p1
<= rPair
.second
)
278 if (p1
<= rPair
.second
&& rPair
.first
<= p2
)
284 void PluginHandler::addSourceModification(SourceRange range
)
286 SourceManager
& SM
= compiler
.getSourceManager();
287 char const *p1
= SM
.getCharacterData( range
.getBegin() );
288 char const *p2
= SM
.getCharacterData( range
.getEnd() );
289 mvModifiedRanges
.emplace_back(p1
, p2
);
292 void PluginHandler::HandleTranslationUnit( ASTContext
& context
)
294 if( context
.getDiagnostics().hasErrorOccurred())
296 if (mainFileName
.endswith(".ii"))
298 report(DiagnosticsEngine::Fatal
,
299 "input file has suffix .ii: \"%0\"\nhighly suspicious, probably ccache generated, this will break warning suppressions; export CCACHE_CPP2=1 to prevent this") << mainFileName
;
303 for( int i
= 0; i
< pluginCount
; ++i
)
305 if( plugins
[ i
].object
!= NULL
&& !plugins
[ i
].disabledRun
)
307 plugins
[ i
].object
->run();
311 //TODO: make the call to 'rename' work on Windows (where the renamed-to
312 // original file is probably still held open somehow):
313 rewriter
.overwriteChangedFiles();
315 for( Rewriter::buffer_iterator it
= rewriter
.buffer_begin();
316 it
!= rewriter
.buffer_end();
319 const FileEntry
* e
= context
.getSourceManager().getFileEntryForID( it
->first
);
321 continue; // Failed modification because of a macro expansion?
322 /* Check where the file actually is, and warn about cases where modification
323 most probably doesn't matter (generated files in workdir).
324 The order here is important, as INSTDIR and WORKDIR are often in SRCDIR/BUILDDIR,
325 and BUILDDIR is sometimes in SRCDIR. */
326 std::string modifyFile
;
327 const char* pathWarning
= NULL
;
329 StringRef
const name
= e
->getName();
330 if( name
.startswith(WORKDIR
"/") )
331 pathWarning
= "modified source in workdir/ : %0";
332 else if( strcmp( SRCDIR
, BUILDDIR
) != 0 && name
.startswith(BUILDDIR
"/") )
333 pathWarning
= "modified source in build dir : %0";
334 else if( name
.startswith(SRCDIR
"/") )
338 pathWarning
= "modified source in unknown location, not modifying : %0";
341 if( modifyFile
.empty())
343 // Check whether the modified file is in the wanted scope
344 if( scope
== "mainfile" )
346 if( it
->first
!= context
.getSourceManager().getMainFileID())
349 else if( scope
== "all" )
351 else // scope is module
353 if( !( isPrefix( SRCDIR
"/" + scope
+ "/", modifyFile
) || isPrefix( SRCDIR
"/include/" + scope
+ "/", modifyFile
) ) )
356 // Warn only now, so that files not in scope do not cause warnings.
357 if( pathWarning
!= NULL
)
358 report( DiagnosticsEngine::Warning
, pathWarning
) << name
;
361 char* filename
= new char[ modifyFile
.length() + 100 ];
362 sprintf( filename
, "%s.new.%d", modifyFile
.c_str(), getpid());
366 std::unique_ptr
<raw_fd_ostream
> ostream(
367 new raw_fd_ostream(filename
, ec
, sys::fs::F_None
));
370 it
->second
.write( *ostream
);
372 if( !ostream
->has_error() && rename( filename
, modifyFile
.c_str()) == 0 )
376 error
= "error: " + ec
.message();
377 ostream
->clear_error();
380 report( DiagnosticsEngine::Error
, "cannot write modified source to %0 (%1)" ) << modifyFile
<< error
;
386 std::unique_ptr
<ASTConsumer
> LibreOfficeAction::CreateASTConsumer( CompilerInstance
& Compiler
, StringRef
)
388 return llvm::make_unique
<PluginHandler
>( Compiler
, _args
);
391 bool LibreOfficeAction::ParseArgs( const CompilerInstance
&, const std::vector
< std::string
>& args
)
397 static FrontendPluginRegistry::Add
< loplugin::LibreOfficeAction
> X( "loplugin", "LibreOffice compile check plugin" );
401 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */