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"
19 #include "pluginhandler.hxx"
21 #include <clang/Frontend/CompilerInstance.h>
22 #include <clang/Frontend/FrontendPluginRegistry.h>
23 #include <clang/Lex/PPCallbacks.h>
24 #include <llvm/ADT/StringExtras.h>
25 #include <llvm/Support/TimeProfiler.h>
35 This source file manages all plugin actions. It is not necessary to modify this
36 file when adding new actions.
39 static bool isPrefix( const std::string
& prefix
, const std::string
& full
)
41 return full
.compare(0, prefix
.size(), prefix
) == 0;
49 Plugin
* (*create
)( const InstantiationData
& );
51 const char* optionName
;
58 const int MAX_PLUGINS
= 200;
59 static PluginData plugins
[ MAX_PLUGINS
];
60 static int pluginCount
= 0;
61 static bool bPluginObjectsCreated
= false;
62 static bool unitTestMode
= false;
64 StringRef
initMainFileName(CompilerInstance
& compiler
)
66 StringRef
const& fn(compiler
.getASTContext().getSourceManager().getFileEntryForID(
67 compiler
.getASTContext().getSourceManager().getMainFileID())->getName());
69 // stdin means icecream, so we can rely on -main-file-name containing the full path name
70 return compiler
.getCodeGenOpts().MainFileName
;
72 // this is always a full path name
76 PluginHandler::PluginHandler( CompilerInstance
& compiler
, const std::vector
< std::string
>& args
)
77 : compiler( compiler
)
78 , mainFileName(initMainFileName(compiler
))
79 , rewriter( compiler
.getSourceManager(), compiler
.getLangOpts())
81 , warningsAsErrors( false )
83 std::set
< std::string
> rewriters
;
84 for( std::string
const & arg
: args
)
86 if( arg
.size() >= 2 && arg
[ 0 ] == '-' && arg
[ 1 ] == '-' )
87 handleOption( arg
.substr( 2 ));
89 rewriters
.insert( arg
);
91 createPlugins( rewriters
);
92 bPluginObjectsCreated
= true;
95 PluginHandler::~PluginHandler()
97 for( int i
= 0; i
< pluginCount
; ++i
)
98 if( plugins
[ i
].object
!= NULL
)
100 // PPCallbacks is owned by preprocessor object, don't delete those
101 if( !plugins
[ i
].isPPCallback
)
102 delete plugins
[ i
].object
;
106 bool PluginHandler::isUnitTestMode()
111 void PluginHandler::handleOption( const std::string
& option
)
113 if( option
.substr( 0, 6 ) == "scope=" )
115 scope
= option
.substr( 6 );
116 if( scope
== "mainfile" || scope
== "all" )
120 #if !defined _WIN32 //TODO, S_ISDIR
122 if( stat(( SRCDIR
"/" + scope
).c_str(), &st
) != 0 || !S_ISDIR( st
.st_mode
))
123 report( DiagnosticsEngine::Fatal
, "unknown scope %0 (no such module directory)" ) << scope
;
127 else if( option
.substr( 0, 14 ) == "warnings-only=" )
129 warningsOnly
= option
.substr(14);
131 else if( option
== "warnings-as-errors" )
132 warningsAsErrors
= true;
133 else if( option
== "unit-test-mode" )
135 else if (option
== "debug")
138 report( DiagnosticsEngine::Fatal
, "unknown option %0" ) << option
;
141 void PluginHandler::createPlugins( std::set
< std::string
> rewriters
)
143 for( int i
= 0; i
< pluginCount
; ++i
)
145 const char* name
= plugins
[i
].optionName
;
146 // When in unit-test mode, ignore plugins whose names don't match the filename of the test,
147 // so that we only generate warnings for the plugin that we want to test.
148 // Sharedvisitor plugins still need to remain enabled, they don't do anything on their own,
149 // but sharing-capable plugins need them to actually work (if compiled so) and they register
150 // with them in the code below.
151 if (unitTestMode
&& mainFileName
.find(plugins
[ i
].optionName
) == StringRef::npos
152 && !plugins
[ i
].isSharedPlugin
)
154 if( rewriters
.erase( name
) != 0 )
155 plugins
[ i
].object
= plugins
[ i
].create( InstantiationData
{ name
, *this, compiler
, &rewriter
} );
156 else if( plugins
[ i
].byDefault
)
157 plugins
[ i
].object
= plugins
[ i
].create( InstantiationData
{ name
, *this, compiler
, NULL
} );
158 else if( unitTestMode
&& strcmp(name
, "unusedmethodsremove") != 0 && strcmp(name
, "unusedfieldsremove") != 0)
159 plugins
[ i
].object
= plugins
[ i
].create( InstantiationData
{ name
, *this, compiler
, NULL
} );
161 for( auto r
: rewriters
)
162 report( DiagnosticsEngine::Fatal
, "unknown plugin tool %0" ) << r
;
163 // If there is a shared plugin, make it handle all plugins that it can handle.
164 for( int i
= 0; i
< pluginCount
; ++i
)
166 if( plugins
[ i
].isSharedPlugin
&& plugins
[ i
].object
!= nullptr )
168 Plugin
* plugin
= plugins
[ i
].object
;
169 for( int j
= 0; j
< pluginCount
; ++j
)
171 if( plugins
[ j
].object
!= nullptr
172 && plugin
->setSharedPlugin( plugins
[ j
].object
, plugins
[ j
].optionName
))
174 plugins
[ j
].disabledRun
= true;
181 void PluginHandler::registerPlugin( Plugin
* (*create
)( const InstantiationData
& ), const char* optionName
,
182 bool isPPCallback
, bool isSharedPlugin
, bool byDefault
)
184 assert( !bPluginObjectsCreated
);
185 assert( pluginCount
< MAX_PLUGINS
);
186 plugins
[ pluginCount
].create
= create
;
187 plugins
[ pluginCount
].object
= NULL
;
188 plugins
[ pluginCount
].optionName
= optionName
;
189 plugins
[ pluginCount
].isPPCallback
= isPPCallback
;
190 plugins
[ pluginCount
].isSharedPlugin
= isSharedPlugin
;
191 plugins
[ pluginCount
].byDefault
= byDefault
;
192 plugins
[ pluginCount
].disabledRun
= false;
196 DiagnosticBuilder
PluginHandler::report( DiagnosticsEngine::Level level
, const char* plugin
, StringRef message
, CompilerInstance
& compiler
,
199 DiagnosticsEngine
& diag
= compiler
.getDiagnostics();
200 // Do some mappings (e.g. for -Werror) that clang does not do for custom messages for some reason.
201 if( level
== DiagnosticsEngine::Warning
&& ((diag
.getWarningsAsErrors() && (plugin
== nullptr || plugin
!= warningsOnly
)) || warningsAsErrors
))
202 level
= DiagnosticsEngine::Error
;
203 if( level
== DiagnosticsEngine::Error
&& diag
.getErrorsAsFatal())
204 level
= DiagnosticsEngine::Fatal
;
205 std::string fullMessage
= ( message
+ " [loplugin" ).str();
209 fullMessage
+= plugin
;
213 return diag
.Report( loc
, diag
.getDiagnosticIDs()->getCustomDiagID(static_cast<DiagnosticIDs::Level
>(level
), fullMessage
) );
215 return diag
.Report( diag
.getDiagnosticIDs()->getCustomDiagID(static_cast<DiagnosticIDs::Level
>(level
), fullMessage
) );
218 DiagnosticBuilder
PluginHandler::report( DiagnosticsEngine::Level level
, StringRef message
, SourceLocation loc
)
220 return report( level
, nullptr, message
, compiler
, loc
);
223 bool PluginHandler::ignoreLocation(SourceLocation loc
) {
224 auto i
= ignored_
.find(loc
);
225 if (i
== ignored_
.end()) {
226 i
= ignored_
.emplace(loc
, checkIgnoreLocation(loc
)).first
;
231 bool PluginHandler::checkIgnoreLocation(SourceLocation loc
)
233 // The tree-wide analysis plugins (like unusedmethods) don't want
234 // this logic, they only want to ignore external code
235 if (!treeWideAnalysisMode
)
237 // If a location comes from a PCH, it is not necessary to check it
238 // in every compilation using the PCH, since with Clang we use
239 // -building-pch-with-obj to build a separate precompiled_foo.cxx file
240 // for the PCH, and so it is known that everything in the PCH will
241 // be checked while compiling this file. Skip the checks for all
242 // other files using the PCH.
243 if( !compiler
.getSourceManager().isLocalSourceLocation( loc
))
245 if( !compiler
.getLangOpts().BuildingPCHWithObjectFile
)
249 SourceLocation expansionLoc
= compiler
.getSourceManager().getExpansionLoc( loc
);
250 if( compiler
.getSourceManager().isInSystemHeader( expansionLoc
))
252 PresumedLoc presumedLoc
= compiler
.getSourceManager().getPresumedLoc( expansionLoc
);
253 if( presumedLoc
.isInvalid())
255 const char* bufferName
= presumedLoc
.getFilename();
256 if (bufferName
== NULL
257 || hasPathnamePrefix(bufferName
, SRCDIR
"/external/")
258 || isSamePathname(bufferName
, SRCDIR
"/sdext/source/pdfimport/wrapper/keyword_list") )
259 // workdir/CustomTarget/sdext/pdfimport/hash.cxx is generated from
260 // sdext/source/pdfimport/wrapper/keyword_list by gperf, which
261 // inserts various #line directives denoting the latter into the
262 // former, but fails to add a #line directive returning back to
263 // hash.cxx itself before the gperf generated boilerplate, so
264 // compilers erroneously consider errors in the boilerplate to come
265 // from keyword_list instead of hash.cxx (for Clang on Linux/macOS
266 // this is not an issue due to the '#pragma GCC system_header'
267 // generated into the start of hash.cxx, #if'ed for __GNUC__, but
268 // for clang-cl it is an issue)
270 if( hasPathnamePrefix(bufferName
, WORKDIR
"/") )
272 // workdir/CustomTarget/vcl/unx/kde4/tst_exclude_socket_notifiers.moc
274 // "../../../../../vcl/unx/kde4/tst_exclude_socket_notifiers.hxx",
275 // making the latter file erroneously match here; so strip any ".."
277 if (strstr(bufferName
, "/..") == nullptr) {
280 std::string
s(bufferName
);
281 normalizeDotDotInFilePath(s
);
282 if (hasPathnamePrefix(s
, WORKDIR
"/"))
285 if( hasPathnamePrefix(bufferName
, BUILDDIR
"/")
286 || hasPathnamePrefix(bufferName
, SRCDIR
"/") )
291 // If we overlap with a previous area we modified, we cannot perform this change
292 // without corrupting the source
293 bool PluginHandler::checkOverlap(SourceRange range
)
295 SourceManager
& SM
= compiler
.getSourceManager();
296 char const *p1
= SM
.getCharacterData( range
.getBegin() );
297 char const *p2
= SM
.getCharacterData( range
.getEnd() );
298 for (std::pair
<char const *, char const *> const & rPair
: mvModifiedRanges
)
300 if (rPair
.first
<= p1
&& p1
<= rPair
.second
)
302 if (p1
<= rPair
.second
&& rPair
.first
<= p2
)
308 void PluginHandler::addSourceModification(SourceRange range
)
310 SourceManager
& SM
= compiler
.getSourceManager();
311 char const *p1
= SM
.getCharacterData( range
.getBegin() );
312 char const *p2
= SM
.getCharacterData( range
.getEnd() );
313 mvModifiedRanges
.emplace_back(p1
, p2
);
316 void PluginHandler::HandleTranslationUnit( ASTContext
& context
)
318 llvm::TimeTraceScope
mainTimeScope("LOPluginMain", StringRef(""));
319 if( context
.getDiagnostics().hasErrorOccurred())
321 if (mainFileName
.endswith(".ii"))
323 report(DiagnosticsEngine::Fatal
,
324 "input file has suffix .ii: \"%0\"\nhighly suspicious, probably ccache generated, this will break warning suppressions; export CCACHE_CPP2=1 to prevent this") << mainFileName
;
328 for( int i
= 0; i
< pluginCount
; ++i
)
330 if( plugins
[ i
].object
!= NULL
&& !plugins
[ i
].disabledRun
)
332 llvm::TimeTraceScope
timeScope("LOPlugin", [&]() { return plugins
[i
].optionName
; });
333 plugins
[ i
].object
->run();
337 //TODO: make the call to 'rename' work on Windows (where the renamed-to
338 // original file is probably still held open somehow):
339 rewriter
.overwriteChangedFiles();
341 for( Rewriter::buffer_iterator it
= rewriter
.buffer_begin();
342 it
!= rewriter
.buffer_end();
345 const FileEntry
* e
= context
.getSourceManager().getFileEntryForID( it
->first
);
347 continue; // Failed modification because of a macro expansion?
348 /* Check where the file actually is, and warn about cases where modification
349 most probably doesn't matter (generated files in workdir).
350 The order here is important, as INSTDIR and WORKDIR are often in SRCDIR/BUILDDIR,
351 and BUILDDIR is sometimes in SRCDIR. */
352 std::string modifyFile
;
353 const char* pathWarning
= NULL
;
355 StringRef
const name
= e
->getName();
356 if( name
.startswith(WORKDIR
"/") )
357 pathWarning
= "modified source in workdir/ : %0";
358 else if( strcmp( SRCDIR
, BUILDDIR
) != 0 && name
.startswith(BUILDDIR
"/") )
359 pathWarning
= "modified source in build dir : %0";
360 else if( name
.startswith(SRCDIR
"/") )
364 pathWarning
= "modified source in unknown location, not modifying : %0";
367 if( modifyFile
.empty())
368 modifyFile
= name
.str();
369 // Check whether the modified file is in the wanted scope
370 if( scope
== "mainfile" )
372 if( it
->first
!= context
.getSourceManager().getMainFileID())
375 else if( scope
== "all" )
377 else // scope is module
379 if( !( isPrefix( SRCDIR
"/" + scope
+ "/", modifyFile
) || isPrefix( SRCDIR
"/include/" + scope
+ "/", modifyFile
) ) )
382 // Warn only now, so that files not in scope do not cause warnings.
383 if( pathWarning
!= NULL
)
384 report( DiagnosticsEngine::Warning
, pathWarning
) << name
;
387 auto const filename
= modifyFile
+ ".new." + itostr(getpid());
391 std::unique_ptr
<raw_fd_ostream
> ostream(
392 new raw_fd_ostream(filename
, ec
, sys::fs::OF_None
));
395 it
->second
.write( *ostream
);
397 if( !ostream
->has_error() && rename( filename
.c_str(), modifyFile
.c_str()) == 0 )
401 error
= "error: " + ec
.message();
402 ostream
->clear_error();
403 unlink( filename
.c_str() );
405 report( DiagnosticsEngine::Error
, "cannot write modified source to %0 (%1)" ) << modifyFile
<< error
;
412 // BEGIN code copied from LLVM's clang/lib/Sema/Sema.cpp
414 /// Returns true, if all methods and nested classes of the given
415 /// CXXRecordDecl are defined in this translation unit.
417 /// Should only be called from ActOnEndOfTranslationUnit so that all
418 /// definitions are actually read.
419 static bool MethodsAndNestedClassesComplete(const CXXRecordDecl
*RD
,
420 RecordCompleteMap
&MNCComplete
) {
421 RecordCompleteMap::iterator Cache
= MNCComplete
.find(RD
);
422 if (Cache
!= MNCComplete
.end())
423 return Cache
->second
;
424 if (!RD
->isCompleteDefinition())
426 bool Complete
= true;
427 for (DeclContext::decl_iterator I
= RD
->decls_begin(),
429 I
!= E
&& Complete
; ++I
) {
430 if (const CXXMethodDecl
*M
= dyn_cast
<CXXMethodDecl
>(*I
))
431 Complete
= M
->isDefined() || M
->isDefaulted() ||
432 (M
->isPure() && !isa
<CXXDestructorDecl
>(M
));
433 else if (const FunctionTemplateDecl
*F
= dyn_cast
<FunctionTemplateDecl
>(*I
))
434 // If the template function is marked as late template parsed at this
435 // point, it has not been instantiated and therefore we have not
436 // performed semantic analysis on it yet, so we cannot know if the type
437 // can be considered complete.
438 Complete
= !F
->getTemplatedDecl()->isLateTemplateParsed() &&
439 F
->getTemplatedDecl()->isDefined();
440 else if (const CXXRecordDecl
*R
= dyn_cast
<CXXRecordDecl
>(*I
)) {
441 if (R
->isInjectedClassName())
443 if (R
->hasDefinition())
444 Complete
= MethodsAndNestedClassesComplete(R
->getDefinition(),
450 MNCComplete
[RD
] = Complete
;
454 /// Returns true, if the given CXXRecordDecl is fully defined in this
455 /// translation unit, i.e. all methods are defined or pure virtual and all
456 /// friends, friend functions and nested classes are fully defined in this
457 /// translation unit.
459 /// Should only be called from ActOnEndOfTranslationUnit so that all
460 /// definitions are actually read.
461 static bool IsRecordFullyDefined(const CXXRecordDecl
*RD
,
462 RecordCompleteMap
&RecordsComplete
,
463 RecordCompleteMap
&MNCComplete
) {
464 RecordCompleteMap::iterator Cache
= RecordsComplete
.find(RD
);
465 if (Cache
!= RecordsComplete
.end())
466 return Cache
->second
;
467 bool Complete
= MethodsAndNestedClassesComplete(RD
, MNCComplete
);
468 for (CXXRecordDecl::friend_iterator I
= RD
->friend_begin(),
469 E
= RD
->friend_end();
470 I
!= E
&& Complete
; ++I
) {
471 // Check if friend classes and methods are complete.
472 if (TypeSourceInfo
*TSI
= (*I
)->getFriendType()) {
473 // Friend classes are available as the TypeSourceInfo of the FriendDecl.
474 if (CXXRecordDecl
*FriendD
= TSI
->getType()->getAsCXXRecordDecl())
475 Complete
= MethodsAndNestedClassesComplete(FriendD
, MNCComplete
);
479 // Friend functions are available through the NamedDecl of FriendDecl.
480 if (const FunctionDecl
*FD
=
481 dyn_cast
<FunctionDecl
>((*I
)->getFriendDecl()))
482 Complete
= FD
->isDefined();
484 // This is a template friend, give up.
488 RecordsComplete
[RD
] = Complete
;
492 // END code copied from LLVM's clang/lib/Sema/Sema.cpp
496 bool PluginHandler::isAllRelevantCodeDefined(NamedDecl
const * decl
) {
497 switch (decl
->getAccess()) {
499 if (!cast
<CXXRecordDecl
>(decl
->getDeclContext())->hasAttr
<FinalAttr
>()) {
504 if (IsRecordFullyDefined(
505 cast
<CXXRecordDecl
>(decl
->getDeclContext()), RecordsComplete_
, MNCComplete_
))
513 return !decl
->isExternallyVisible();
516 std::unique_ptr
<ASTConsumer
> LibreOfficeAction::CreateASTConsumer( CompilerInstance
& Compiler
, StringRef
)
518 #if __cplusplus >= 201402L
519 return std::make_unique
<PluginHandler
>( Compiler
, _args
);
521 return llvm::make_unique
<PluginHandler
>( Compiler
, _args
);
525 bool LibreOfficeAction::ParseArgs( const CompilerInstance
&, const std::vector
< std::string
>& args
)
531 static FrontendPluginRegistry::Add
< loplugin::LibreOfficeAction
> X( "loplugin", "LibreOffice compile check plugin" );
535 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */