Version 7.6.3.2-android, tag libreoffice-7.6.3.2-android
[LibreOffice.git] / compilerplugins / clang / pluginhandler.cxx
blobf5d27dd76b2ba9701b5b7a4c7a1c28a3c7b6f969
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
3 * This file is part of the LibreOffice project.
5 * Based on LLVM/Clang.
7 * This file is distributed under the University of Illinois Open Source
8 * License. See LICENSE.TXT for details.
12 #include <memory>
13 #include <system_error>
14 #include <utility>
16 #include "config_clang.h"
18 #include "plugin.hxx"
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>
27 #if defined _WIN32
28 #include <process.h>
29 #else
30 #include <sys/stat.h>
31 #include <unistd.h>
32 #endif
34 /**
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;
44 namespace loplugin
47 struct PluginData
49 Plugin* (*create)( const InstantiationData& );
50 Plugin* object;
51 const char* optionName;
52 bool isPPCallback;
53 bool isSharedPlugin;
54 bool byDefault;
55 bool disabledRun;
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());
68 if (fn == "<stdin>")
69 // stdin means icecream, so we can rely on -main-file-name containing the full path name
70 return compiler.getCodeGenOpts().MainFileName;
71 else
72 // this is always a full path name
73 return fn;
76 PluginHandler::PluginHandler( CompilerInstance& compiler, const std::vector< std::string >& args )
77 : compiler( compiler )
78 , mainFileName(initMainFileName(compiler))
79 , rewriter( compiler.getSourceManager(), compiler.getLangOpts())
80 , scope( "mainfile" )
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 ));
88 else
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()
108 return unitTestMode;
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" )
117 ; // ok
118 else
120 #if !defined _WIN32 //TODO, S_ISDIR
121 struct stat st;
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;
124 #endif
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" )
134 unitTestMode = true;
135 else if (option == "debug")
136 debugMode = true;
137 else
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)
153 continue;
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;
193 ++pluginCount;
196 DiagnosticBuilder PluginHandler::report( DiagnosticsEngine::Level level, const char* plugin, StringRef message, CompilerInstance& compiler,
197 SourceLocation loc )
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();
206 if( plugin )
208 fullMessage += ":";
209 fullMessage += plugin;
211 fullMessage += "]";
212 if( loc.isValid())
213 return diag.Report( loc, diag.getDiagnosticIDs()->getCustomDiagID(static_cast<DiagnosticIDs::Level>(level), fullMessage) );
214 else
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;
228 return i->second;
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 )
246 return true;
249 SourceLocation expansionLoc = compiler.getSourceManager().getExpansionLoc( loc );
250 if( compiler.getSourceManager().isInSystemHeader( expansionLoc ))
251 return true;
252 PresumedLoc presumedLoc = compiler.getSourceManager().getPresumedLoc( expansionLoc );
253 if( presumedLoc.isInvalid())
254 return true;
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)
269 return true;
270 if( hasPathnamePrefix(bufferName, WORKDIR "/") )
272 // workdir/CustomTarget/vcl/unx/kde4/tst_exclude_socket_notifiers.moc
273 // includes
274 // "../../../../../vcl/unx/kde4/tst_exclude_socket_notifiers.hxx",
275 // making the latter file erroneously match here; so strip any ".."
276 // segments:
277 if (strstr(bufferName, "/..") == nullptr) {
278 return true;
280 std::string s(bufferName);
281 normalizeDotDotInFilePath(s);
282 if (hasPathnamePrefix(s, WORKDIR "/"))
283 return true;
285 if( hasPathnamePrefix(bufferName, BUILDDIR "/")
286 || hasPathnamePrefix(bufferName, SRCDIR "/") )
287 return false; // ok
288 return true;
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)
301 return false;
302 if (p1 <= rPair.second && rPair.first <= p2)
303 return false;
305 return true;
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())
320 return;
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;
325 return;
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();
336 #if defined _WIN32
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();
340 #else
341 for( Rewriter::buffer_iterator it = rewriter.buffer_begin();
342 it != rewriter.buffer_end();
343 ++it )
345 const FileEntry* e = context.getSourceManager().getFileEntryForID( it->first );
346 if( e == NULL )
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;
354 bool bSkip = false;
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 "/") )
361 ; // ok
362 else
364 pathWarning = "modified source in unknown location, not modifying : %0";
365 bSkip = true;
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())
373 continue;
375 else if( scope == "all" )
376 ; // ok
377 else // scope is module
379 if( !( isPrefix( SRCDIR "/" + scope + "/", modifyFile ) || isPrefix( SRCDIR "/include/" + scope + "/", modifyFile ) ) )
380 continue;
382 // Warn only now, so that files not in scope do not cause warnings.
383 if( pathWarning != NULL )
384 report( DiagnosticsEngine::Warning, pathWarning ) << name;
385 if( bSkip )
386 continue;
387 auto const filename = modifyFile + ".new." + itostr(getpid());
388 std::string error;
389 bool bOk = false;
390 std::error_code ec;
391 std::unique_ptr<raw_fd_ostream> ostream(
392 new raw_fd_ostream(filename, ec, sys::fs::OF_None));
393 if( !ec)
395 it->second.write( *ostream );
396 ostream->close();
397 if( !ostream->has_error() && rename( filename.c_str(), modifyFile.c_str()) == 0 )
398 bOk = true;
400 else
401 error = "error: " + ec.message();
402 ostream->clear_error();
403 unlink( filename.c_str() );
404 if( !bOk )
405 report( DiagnosticsEngine::Error, "cannot write modified source to %0 (%1)" ) << modifyFile << error;
407 #endif
410 namespace {
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())
425 return false;
426 bool Complete = true;
427 for (DeclContext::decl_iterator I = RD->decls_begin(),
428 E = RD->decls_end();
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())
442 continue;
443 if (R->hasDefinition())
444 Complete = MethodsAndNestedClassesComplete(R->getDefinition(),
445 MNCComplete);
446 else
447 Complete = false;
450 MNCComplete[RD] = Complete;
451 return 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);
476 else
477 Complete = false;
478 } else {
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();
483 else
484 // This is a template friend, give up.
485 Complete = false;
488 RecordsComplete[RD] = Complete;
489 return Complete;
492 // END code copied from LLVM's clang/lib/Sema/Sema.cpp
496 bool PluginHandler::isAllRelevantCodeDefined(NamedDecl const * decl) {
497 switch (decl->getAccess()) {
498 case AS_protected:
499 if (!cast<CXXRecordDecl>(decl->getDeclContext())->hasAttr<FinalAttr>()) {
500 break;
502 LLVM_FALLTHROUGH;
503 case AS_private:
504 if (IsRecordFullyDefined(
505 cast<CXXRecordDecl>(decl->getDeclContext()), RecordsComplete_, MNCComplete_))
507 return true;
509 break;
510 default:
511 break;
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 );
520 #else
521 return llvm::make_unique<PluginHandler>( Compiler, _args );
522 #endif
525 bool LibreOfficeAction::ParseArgs( const CompilerInstance&, const std::vector< std::string >& args )
527 _args = args;
528 return true;
531 static FrontendPluginRegistry::Add< loplugin::LibreOfficeAction > X( "loplugin", "LibreOffice compile check plugin" );
533 } // namespace
535 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */