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 "config_clang.h"
20 #include "pluginhandler.hxx"
22 #include <clang/Frontend/CompilerInstance.h>
23 #include <clang/Frontend/FrontendPluginRegistry.h>
24 #include <clang/Lex/PPCallbacks.h>
25 #include <llvm/ADT/StringExtras.h>
26 #include <llvm/Support/TimeProfiler.h>
36 This source file manages all plugin actions. It is not necessary to modify this
37 file when adding new actions.
40 static bool isPrefix( const std::string
& prefix
, const std::string
& full
)
42 return full
.compare(0, prefix
.size(), prefix
) == 0;
50 Plugin
* (*create
)( const InstantiationData
& );
52 const char* optionName
;
59 const int MAX_PLUGINS
= 200;
60 static PluginData plugins
[ MAX_PLUGINS
];
61 static int pluginCount
= 0;
62 static bool bPluginObjectsCreated
= false;
63 static bool unitTestMode
= false;
65 StringRef
initMainFileName(CompilerInstance
& compiler
)
67 StringRef
const& fn(compiler
.getASTContext().getSourceManager().getFileEntryRefForID(
68 compiler
.getASTContext().getSourceManager().getMainFileID())->getName());
70 // stdin means icecream, so we can rely on -main-file-name containing the full path name
71 return compiler
.getCodeGenOpts().MainFileName
;
73 // this is always a full path name
77 PluginHandler::PluginHandler( CompilerInstance
& compiler
, const std::vector
< std::string
>& args
)
78 : compiler( compiler
)
79 , mainFileName(initMainFileName(compiler
))
80 , rewriter( compiler
.getSourceManager(), compiler
.getLangOpts())
82 , warningsAsErrors( false )
84 std::set
< std::string
> rewriters
;
85 for( std::string
const & arg
: args
)
87 if( arg
.size() >= 2 && arg
[ 0 ] == '-' && arg
[ 1 ] == '-' )
88 handleOption( arg
.substr( 2 ));
90 rewriters
.insert( arg
);
92 createPlugins( rewriters
);
93 bPluginObjectsCreated
= true;
96 PluginHandler::~PluginHandler()
98 for( int i
= 0; i
< pluginCount
; ++i
)
99 if( plugins
[ i
].object
!= NULL
)
101 // PPCallbacks is owned by preprocessor object, don't delete those
102 if( !plugins
[ i
].isPPCallback
)
103 delete plugins
[ i
].object
;
107 bool PluginHandler::isUnitTestMode()
112 void PluginHandler::handleOption( const std::string
& option
)
114 if( option
.substr( 0, 6 ) == "scope=" )
116 scope
= option
.substr( 6 );
117 if( scope
== "mainfile" || scope
== "all" )
121 #if !defined _WIN32 //TODO, S_ISDIR
123 if( stat(( SRCDIR
"/" + scope
).c_str(), &st
) != 0 || !S_ISDIR( st
.st_mode
))
124 report( DiagnosticsEngine::Fatal
, "unknown scope %0 (no such module directory)" ) << scope
;
128 else if( option
.substr( 0, 14 ) == "warnings-only=" )
130 warningsOnly
= option
.substr(14);
132 else if( option
== "warnings-as-errors" )
133 warningsAsErrors
= true;
134 else if( option
== "unit-test-mode" )
136 else if (option
== "debug")
139 report( DiagnosticsEngine::Fatal
, "unknown option %0" ) << option
;
142 void PluginHandler::createPlugins( std::set
< std::string
> rewriters
)
144 for( int i
= 0; i
< pluginCount
; ++i
)
146 const char* name
= plugins
[i
].optionName
;
147 // When in unit-test mode, ignore plugins whose names don't match the filename of the test,
148 // so that we only generate warnings for the plugin that we want to test.
149 // Sharedvisitor plugins still need to remain enabled, they don't do anything on their own,
150 // but sharing-capable plugins need them to actually work (if compiled so) and they register
151 // with them in the code below.
152 if (unitTestMode
&& mainFileName
.find(plugins
[ i
].optionName
) == StringRef::npos
153 && !plugins
[ i
].isSharedPlugin
)
155 if( rewriters
.erase( name
) != 0 )
156 plugins
[ i
].object
= plugins
[ i
].create( InstantiationData
{ name
, *this, compiler
, &rewriter
} );
157 else if( plugins
[ i
].byDefault
)
158 plugins
[ i
].object
= plugins
[ i
].create( InstantiationData
{ name
, *this, compiler
, NULL
} );
159 else if( unitTestMode
&& strcmp(name
, "unusedmethodsremove") != 0 && strcmp(name
, "unusedfieldsremove") != 0)
160 plugins
[ i
].object
= plugins
[ i
].create( InstantiationData
{ name
, *this, compiler
, NULL
} );
162 for( auto r
: rewriters
)
163 report( DiagnosticsEngine::Fatal
, "unknown plugin tool %0" ) << r
;
164 // If there is a shared plugin, make it handle all plugins that it can handle.
165 for( int i
= 0; i
< pluginCount
; ++i
)
167 if( plugins
[ i
].isSharedPlugin
&& plugins
[ i
].object
!= nullptr )
169 Plugin
* plugin
= plugins
[ i
].object
;
170 for( int j
= 0; j
< pluginCount
; ++j
)
172 if( plugins
[ j
].object
!= nullptr
173 && plugin
->setSharedPlugin( plugins
[ j
].object
, plugins
[ j
].optionName
))
175 plugins
[ j
].disabledRun
= true;
182 void PluginHandler::registerPlugin( Plugin
* (*create
)( const InstantiationData
& ), const char* optionName
,
183 bool isPPCallback
, bool isSharedPlugin
, bool byDefault
)
185 assert( !bPluginObjectsCreated
);
186 assert( pluginCount
< MAX_PLUGINS
);
187 plugins
[ pluginCount
].create
= create
;
188 plugins
[ pluginCount
].object
= NULL
;
189 plugins
[ pluginCount
].optionName
= optionName
;
190 plugins
[ pluginCount
].isPPCallback
= isPPCallback
;
191 plugins
[ pluginCount
].isSharedPlugin
= isSharedPlugin
;
192 plugins
[ pluginCount
].byDefault
= byDefault
;
193 plugins
[ pluginCount
].disabledRun
= false;
197 DiagnosticBuilder
PluginHandler::report( DiagnosticsEngine::Level level
, const char* plugin
, StringRef message
, CompilerInstance
& compiler
,
200 DiagnosticsEngine
& diag
= compiler
.getDiagnostics();
201 // Do some mappings (e.g. for -Werror) that clang does not do for custom messages for some reason.
202 if( level
== DiagnosticsEngine::Warning
&& ((diag
.getWarningsAsErrors() && (plugin
== nullptr || plugin
!= warningsOnly
)) || warningsAsErrors
))
203 level
= DiagnosticsEngine::Error
;
204 if( level
== DiagnosticsEngine::Error
&& diag
.getErrorsAsFatal())
205 level
= DiagnosticsEngine::Fatal
;
206 std::string fullMessage
= ( message
+ " [loplugin" ).str();
210 fullMessage
+= plugin
;
214 return diag
.Report( loc
, diag
.getDiagnosticIDs()->getCustomDiagID(static_cast<DiagnosticIDs::Level
>(level
), fullMessage
) );
216 return diag
.Report( diag
.getDiagnosticIDs()->getCustomDiagID(static_cast<DiagnosticIDs::Level
>(level
), fullMessage
) );
219 DiagnosticBuilder
PluginHandler::report( DiagnosticsEngine::Level level
, StringRef message
, SourceLocation loc
)
221 return report( level
, nullptr, message
, compiler
, loc
);
224 bool PluginHandler::ignoreLocation(SourceLocation loc
) {
225 auto i
= ignored_
.find(loc
);
226 if (i
== ignored_
.end()) {
227 i
= ignored_
.emplace(loc
, checkIgnoreLocation(loc
)).first
;
232 bool PluginHandler::checkIgnoreLocation(SourceLocation loc
)
234 // The tree-wide analysis plugins (like unusedmethods) don't want
235 // this logic, they only want to ignore external code
236 if (!treeWideAnalysisMode
)
238 // If a location comes from a PCH, it is not necessary to check it
239 // in every compilation using the PCH, since with Clang we use
240 // -building-pch-with-obj to build a separate precompiled_foo.cxx file
241 // for the PCH, and so it is known that everything in the PCH will
242 // be checked while compiling this file. Skip the checks for all
243 // other files using the PCH.
244 if( !compiler
.getSourceManager().isLocalSourceLocation( loc
))
246 if( !compiler
.getLangOpts().BuildingPCHWithObjectFile
)
250 SourceLocation expansionLoc
= compiler
.getSourceManager().getExpansionLoc( loc
);
251 if( compiler
.getSourceManager().isInSystemHeader( expansionLoc
))
253 PresumedLoc presumedLoc
= compiler
.getSourceManager().getPresumedLoc( expansionLoc
);
254 if( presumedLoc
.isInvalid())
256 const char* bufferName
= presumedLoc
.getFilename();
257 if (bufferName
== NULL
258 || hasPathnamePrefix(bufferName
, SRCDIR
"/external/")
259 || isSamePathname(bufferName
, SRCDIR
"/sdext/source/pdfimport/wrapper/keyword_list") )
260 // workdir/CustomTarget/sdext/pdfimport/hash.cxx is generated from
261 // sdext/source/pdfimport/wrapper/keyword_list by gperf, which
262 // inserts various #line directives denoting the latter into the
263 // former, but fails to add a #line directive returning back to
264 // hash.cxx itself before the gperf generated boilerplate, so
265 // compilers erroneously consider errors in the boilerplate to come
266 // from keyword_list instead of hash.cxx (for Clang on Linux/macOS
267 // this is not an issue due to the '#pragma GCC system_header'
268 // generated into the start of hash.cxx, #if'ed for __GNUC__, but
269 // for clang-cl it is an issue)
271 if( hasPathnamePrefix(bufferName
, WORKDIR
"/") )
273 // workdir/CustomTarget/vcl/unx/kde4/tst_exclude_socket_notifiers.moc
275 // "../../../../../vcl/unx/kde4/tst_exclude_socket_notifiers.hxx",
276 // making the latter file erroneously match here; so strip any ".."
278 if (strstr(bufferName
, "/..") == nullptr) {
281 std::string
s(bufferName
);
282 normalizeDotDotInFilePath(s
);
283 if (hasPathnamePrefix(s
, WORKDIR
"/"))
286 if( hasPathnamePrefix(bufferName
, BUILDDIR
"/")
287 || hasPathnamePrefix(bufferName
, SRCDIR
"/") )
292 // If we overlap with a previous area we modified, we cannot perform this change
293 // without corrupting the source
294 bool PluginHandler::checkOverlap(SourceRange range
)
296 SourceManager
& SM
= compiler
.getSourceManager();
297 char const *p1
= SM
.getCharacterData( range
.getBegin() );
298 char const *p2
= SM
.getCharacterData( range
.getEnd() );
299 for (std::pair
<char const *, char const *> const & rPair
: mvModifiedRanges
)
301 if (rPair
.first
<= p1
&& p1
<= rPair
.second
)
303 if (p1
<= rPair
.second
&& rPair
.first
<= p2
)
309 void PluginHandler::addSourceModification(SourceRange range
)
311 SourceManager
& SM
= compiler
.getSourceManager();
312 char const *p1
= SM
.getCharacterData( range
.getBegin() );
313 char const *p2
= SM
.getCharacterData( range
.getEnd() );
314 mvModifiedRanges
.emplace_back(p1
, p2
);
317 void PluginHandler::HandleTranslationUnit( ASTContext
& context
)
319 llvm::TimeTraceScope
mainTimeScope("LOPluginMain", StringRef(""));
320 if( context
.getDiagnostics().hasErrorOccurred())
322 if (compat::ends_with(mainFileName
, ".ii"))
324 report(DiagnosticsEngine::Fatal
,
325 "input file has suffix .ii: \"%0\"\nhighly suspicious, probably ccache generated, this will break warning suppressions; export CCACHE_CPP2=1 to prevent this") << mainFileName
;
329 for( int i
= 0; i
< pluginCount
; ++i
)
331 if( plugins
[ i
].object
!= NULL
&& !plugins
[ i
].disabledRun
)
333 llvm::TimeTraceScope
timeScope("LOPlugin", [&]() { return plugins
[i
].optionName
; });
334 plugins
[ i
].object
->run();
338 //TODO: make the call to 'rename' work on Windows (where the renamed-to
339 // original file is probably still held open somehow):
340 rewriter
.overwriteChangedFiles();
342 for( Rewriter::buffer_iterator it
= rewriter
.buffer_begin();
343 it
!= rewriter
.buffer_end();
346 auto e
= context
.getSourceManager().getFileEntryRefForID( it
->first
);
348 continue; // Failed modification because of a macro expansion?
349 /* Check where the file actually is, and warn about cases where modification
350 most probably doesn't matter (generated files in workdir).
351 The order here is important, as INSTDIR and WORKDIR are often in SRCDIR/BUILDDIR,
352 and BUILDDIR is sometimes in SRCDIR. */
353 std::string modifyFile
;
354 const char* pathWarning
= NULL
;
356 StringRef
const name
= e
->getName();
357 if( compat::starts_with(name
, WORKDIR
"/") )
358 pathWarning
= "modified source in workdir/ : %0";
359 else if( strcmp( SRCDIR
, BUILDDIR
) != 0 && compat::starts_with(name
, BUILDDIR
"/") )
360 pathWarning
= "modified source in build dir : %0";
361 else if( compat::starts_with(name
, SRCDIR
"/") )
365 pathWarning
= "modified source in unknown location, not modifying : %0";
368 if( modifyFile
.empty())
369 modifyFile
= name
.str();
370 // Check whether the modified file is in the wanted scope
371 if( scope
== "mainfile" )
373 if( it
->first
!= context
.getSourceManager().getMainFileID())
376 else if( scope
== "all" )
378 else // scope is module
380 if( !( isPrefix( SRCDIR
"/" + scope
+ "/", modifyFile
) || isPrefix( SRCDIR
"/include/" + scope
+ "/", modifyFile
) ) )
383 // Warn only now, so that files not in scope do not cause warnings.
384 if( pathWarning
!= NULL
)
385 report( DiagnosticsEngine::Warning
, pathWarning
) << name
;
388 auto const filename
= modifyFile
+ ".new." + itostr(getpid());
392 std::unique_ptr
<raw_fd_ostream
> ostream(
393 new raw_fd_ostream(filename
, ec
, sys::fs::OF_None
));
396 it
->second
.write( *ostream
);
398 if( !ostream
->has_error() && rename( filename
.c_str(), modifyFile
.c_str()) == 0 )
402 error
= "error: " + ec
.message();
403 ostream
->clear_error();
404 unlink( filename
.c_str() );
406 report( DiagnosticsEngine::Error
, "cannot write modified source to %0 (%1)" ) << modifyFile
<< error
;
413 // BEGIN code copied from LLVM's clang/lib/Sema/Sema.cpp
415 /// Returns true, if all methods and nested classes of the given
416 /// CXXRecordDecl are defined in this translation unit.
418 /// Should only be called from ActOnEndOfTranslationUnit so that all
419 /// definitions are actually read.
420 static bool MethodsAndNestedClassesComplete(const CXXRecordDecl
*RD
,
421 RecordCompleteMap
&MNCComplete
) {
422 RecordCompleteMap::iterator Cache
= MNCComplete
.find(RD
);
423 if (Cache
!= MNCComplete
.end())
424 return Cache
->second
;
425 if (!RD
->isCompleteDefinition())
427 bool Complete
= true;
428 for (DeclContext::decl_iterator I
= RD
->decls_begin(),
430 I
!= E
&& Complete
; ++I
) {
431 if (const CXXMethodDecl
*M
= dyn_cast
<CXXMethodDecl
>(*I
))
432 Complete
= M
->isDefined() || M
->isDefaulted() ||
433 (compat::isPureVirtual(M
) && !isa
<CXXDestructorDecl
>(M
));
434 else if (const FunctionTemplateDecl
*F
= dyn_cast
<FunctionTemplateDecl
>(*I
))
435 // If the template function is marked as late template parsed at this
436 // point, it has not been instantiated and therefore we have not
437 // performed semantic analysis on it yet, so we cannot know if the type
438 // can be considered complete.
439 Complete
= !F
->getTemplatedDecl()->isLateTemplateParsed() &&
440 F
->getTemplatedDecl()->isDefined();
441 else if (const CXXRecordDecl
*R
= dyn_cast
<CXXRecordDecl
>(*I
)) {
442 if (R
->isInjectedClassName())
444 if (R
->hasDefinition())
445 Complete
= MethodsAndNestedClassesComplete(R
->getDefinition(),
451 MNCComplete
[RD
] = Complete
;
455 /// Returns true, if the given CXXRecordDecl is fully defined in this
456 /// translation unit, i.e. all methods are defined or pure virtual and all
457 /// friends, friend functions and nested classes are fully defined in this
458 /// translation unit.
460 /// Should only be called from ActOnEndOfTranslationUnit so that all
461 /// definitions are actually read.
462 static bool IsRecordFullyDefined(const CXXRecordDecl
*RD
,
463 RecordCompleteMap
&RecordsComplete
,
464 RecordCompleteMap
&MNCComplete
) {
465 RecordCompleteMap::iterator Cache
= RecordsComplete
.find(RD
);
466 if (Cache
!= RecordsComplete
.end())
467 return Cache
->second
;
468 bool Complete
= MethodsAndNestedClassesComplete(RD
, MNCComplete
);
469 for (CXXRecordDecl::friend_iterator I
= RD
->friend_begin(),
470 E
= RD
->friend_end();
471 I
!= E
&& Complete
; ++I
) {
472 // Check if friend classes and methods are complete.
473 if (TypeSourceInfo
*TSI
= (*I
)->getFriendType()) {
474 // Friend classes are available as the TypeSourceInfo of the FriendDecl.
475 if (CXXRecordDecl
*FriendD
= TSI
->getType()->getAsCXXRecordDecl())
476 Complete
= MethodsAndNestedClassesComplete(FriendD
, MNCComplete
);
480 // Friend functions are available through the NamedDecl of FriendDecl.
481 if (const FunctionDecl
*FD
=
482 dyn_cast
<FunctionDecl
>((*I
)->getFriendDecl()))
483 Complete
= FD
->isDefined();
485 // This is a template friend, give up.
489 RecordsComplete
[RD
] = Complete
;
493 // END code copied from LLVM's clang/lib/Sema/Sema.cpp
497 bool PluginHandler::isAllRelevantCodeDefined(NamedDecl
const * decl
) {
498 switch (decl
->getAccess()) {
500 if (!cast
<CXXRecordDecl
>(decl
->getDeclContext())->hasAttr
<FinalAttr
>()) {
505 if (IsRecordFullyDefined(
506 cast
<CXXRecordDecl
>(decl
->getDeclContext()), RecordsComplete_
, MNCComplete_
))
514 return !decl
->isExternallyVisible();
517 std::unique_ptr
<ASTConsumer
> LibreOfficeAction::CreateASTConsumer( CompilerInstance
& Compiler
, StringRef
)
519 #if __cplusplus >= 201402L
520 return std::make_unique
<PluginHandler
>( Compiler
, _args
);
522 return llvm::make_unique
<PluginHandler
>( Compiler
, _args
);
526 bool LibreOfficeAction::ParseArgs( const CompilerInstance
&, const std::vector
< std::string
>& args
)
532 static FrontendPluginRegistry::Add
< loplugin::LibreOfficeAction
> X( "loplugin", "LibreOffice compile check plugin" );
536 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */