bump product version to 7.2.5.1
[LibreOffice.git] / compilerplugins / clang / pluginhandler.cxx
blob315062969b2514c3f699f4920e7d4be902d956f1
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 "plugin.hxx"
17 #include "pluginhandler.hxx"
19 #include <clang/Frontend/CompilerInstance.h>
20 #include <clang/Frontend/FrontendPluginRegistry.h>
21 #include <clang/Lex/PPCallbacks.h>
22 #include <stdio.h>
24 #if CLANG_VERSION >= 90000
25 #include <llvm/Support/TimeProfiler.h>
26 #endif
28 #if defined _WIN32
29 #include <process.h>
30 #else
31 #include <sys/stat.h>
32 #include <unistd.h>
33 #endif
35 /**
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;
45 namespace loplugin
48 struct PluginData
50 Plugin* (*create)( const InstantiationData& );
51 Plugin* object;
52 const char* optionName;
53 bool isPPCallback;
54 bool isSharedPlugin;
55 bool byDefault;
56 bool disabledRun;
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().getFileEntryForID(
68 compiler.getASTContext().getSourceManager().getMainFileID())->getName());
69 if (fn == "<stdin>")
70 // stdin means icecream, so we can rely on -main-file-name containing the full path name
71 return compiler.getCodeGenOpts().MainFileName;
72 else
73 // this is always a full path name
74 return fn;
77 PluginHandler::PluginHandler( CompilerInstance& compiler, const std::vector< std::string >& args )
78 : compiler( compiler )
79 , mainFileName(initMainFileName(compiler))
80 , rewriter( compiler.getSourceManager(), compiler.getLangOpts())
81 , scope( "mainfile" )
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 ));
89 else
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()
109 return unitTestMode;
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" )
118 ; // ok
119 else
121 #if !defined _WIN32 //TODO, S_ISDIR
122 struct stat st;
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;
125 #endif
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" )
135 unitTestMode = true;
136 else if (option == "debug")
137 debugMode = true;
138 else
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)
154 continue;
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;
194 ++pluginCount;
197 DiagnosticBuilder PluginHandler::report( DiagnosticsEngine::Level level, const char* plugin, StringRef message, CompilerInstance& compiler,
198 SourceLocation loc )
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();
207 if( plugin )
209 fullMessage += ":";
210 fullMessage += plugin;
212 fullMessage += "]";
213 if( loc.isValid())
214 return diag.Report( loc, diag.getDiagnosticIDs()->getCustomDiagID(static_cast<DiagnosticIDs::Level>(level), fullMessage) );
215 else
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;
229 return i->second;
232 bool PluginHandler::checkIgnoreLocation(SourceLocation loc)
234 #if CLANG_VERSION >= 80000
235 // If a location comes from a PCH, it is not necessary to check it
236 // in every compilation using the PCH, since with Clang we use
237 // -building-pch-with-obj to build a separate precompiled_foo.cxx file
238 // for the PCH, and so it is known that everything in the PCH will
239 // be checked while compiling this file. Skip the checks for all
240 // other files using the PCH.
241 if( !compiler.getSourceManager().isLocalSourceLocation( loc ))
243 if( !compiler.getLangOpts().BuildingPCHWithObjectFile )
244 return true;
246 #endif
247 SourceLocation expansionLoc = compiler.getSourceManager().getExpansionLoc( loc );
248 if( compiler.getSourceManager().isInSystemHeader( expansionLoc ))
249 return true;
250 PresumedLoc presumedLoc = compiler.getSourceManager().getPresumedLoc( expansionLoc );
251 if( presumedLoc.isInvalid())
252 return true;
253 const char* bufferName = presumedLoc.getFilename();
254 if (bufferName == NULL
255 || hasPathnamePrefix(bufferName, SRCDIR "/external/")
256 || isSamePathname(bufferName, SRCDIR "/sdext/source/pdfimport/wrapper/keyword_list") )
257 // workdir/CustomTarget/sdext/pdfimport/hash.cxx is generated from
258 // sdext/source/pdfimport/wrapper/keyword_list by gperf, which
259 // inserts various #line directives denoting the latter into the
260 // former, but fails to add a #line directive returning back to
261 // hash.cxx itself before the gperf generated boilerplate, so
262 // compilers erroneously consider errors in the boilerplate to come
263 // from keyword_list instead of hash.cxx (for Clang on Linux/macOS
264 // this is not an issue due to the '#pragma GCC system_header'
265 // generated into the start of hash.cxx, #if'ed for __GNUC__, but
266 // for clang-cl it is an issue)
267 return true;
268 if( hasPathnamePrefix(bufferName, WORKDIR "/") )
270 // workdir/CustomTarget/vcl/unx/kde4/tst_exclude_socket_notifiers.moc
271 // includes
272 // "../../../../../vcl/unx/kde4/tst_exclude_socket_notifiers.hxx",
273 // making the latter file erroneously match here; so strip any ".."
274 // segments:
275 if (strstr(bufferName, "/..") == nullptr) {
276 return true;
278 std::string s(bufferName);
279 normalizeDotDotInFilePath(s);
280 if (hasPathnamePrefix(s, WORKDIR "/"))
281 return true;
283 if( hasPathnamePrefix(bufferName, BUILDDIR "/")
284 || hasPathnamePrefix(bufferName, SRCDIR "/") )
285 return false; // ok
286 return true;
289 // If we overlap with a previous area we modified, we cannot perform this change
290 // without corrupting the source
291 bool PluginHandler::checkOverlap(SourceRange range)
293 SourceManager& SM = compiler.getSourceManager();
294 char const *p1 = SM.getCharacterData( range.getBegin() );
295 char const *p2 = SM.getCharacterData( range.getEnd() );
296 for (std::pair<char const *, char const *> const & rPair : mvModifiedRanges)
298 if (rPair.first <= p1 && p1 <= rPair.second)
299 return false;
300 if (p1 <= rPair.second && rPair.first <= p2)
301 return false;
303 return true;
306 void PluginHandler::addSourceModification(SourceRange range)
308 SourceManager& SM = compiler.getSourceManager();
309 char const *p1 = SM.getCharacterData( range.getBegin() );
310 char const *p2 = SM.getCharacterData( range.getEnd() );
311 mvModifiedRanges.emplace_back(p1, p2);
314 void PluginHandler::HandleTranslationUnit( ASTContext& context )
316 #if CLANG_VERSION >= 90000
317 llvm::TimeTraceScope mainTimeScope("LOPluginMain", StringRef(""));
318 #endif
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 #if CLANG_VERSION >= 90000
333 llvm::TimeTraceScope timeScope("LOPlugin", [&]() { return plugins[i].optionName; });
334 #endif
335 plugins[ i ].object->run();
338 #if defined _WIN32
339 //TODO: make the call to 'rename' work on Windows (where the renamed-to
340 // original file is probably still held open somehow):
341 rewriter.overwriteChangedFiles();
342 #else
343 for( Rewriter::buffer_iterator it = rewriter.buffer_begin();
344 it != rewriter.buffer_end();
345 ++it )
347 const FileEntry* e = context.getSourceManager().getFileEntryForID( it->first );
348 if( e == NULL )
349 continue; // Failed modification because of a macro expansion?
350 /* Check where the file actually is, and warn about cases where modification
351 most probably doesn't matter (generated files in workdir).
352 The order here is important, as INSTDIR and WORKDIR are often in SRCDIR/BUILDDIR,
353 and BUILDDIR is sometimes in SRCDIR. */
354 std::string modifyFile;
355 const char* pathWarning = NULL;
356 bool bSkip = false;
357 StringRef const name = e->getName();
358 if( name.startswith(WORKDIR "/") )
359 pathWarning = "modified source in workdir/ : %0";
360 else if( strcmp( SRCDIR, BUILDDIR ) != 0 && name.startswith(BUILDDIR "/") )
361 pathWarning = "modified source in build dir : %0";
362 else if( name.startswith(SRCDIR "/") )
363 ; // ok
364 else
366 pathWarning = "modified source in unknown location, not modifying : %0";
367 bSkip = true;
369 if( modifyFile.empty())
370 modifyFile = name.str();
371 // Check whether the modified file is in the wanted scope
372 if( scope == "mainfile" )
374 if( it->first != context.getSourceManager().getMainFileID())
375 continue;
377 else if( scope == "all" )
378 ; // ok
379 else // scope is module
381 if( !( isPrefix( SRCDIR "/" + scope + "/", modifyFile ) || isPrefix( SRCDIR "/include/" + scope + "/", modifyFile ) ) )
382 continue;
384 // Warn only now, so that files not in scope do not cause warnings.
385 if( pathWarning != NULL )
386 report( DiagnosticsEngine::Warning, pathWarning ) << name;
387 if( bSkip )
388 continue;
389 char* filename = new char[ modifyFile.length() + 100 ];
390 sprintf( filename, "%s.new.%d", modifyFile.c_str(), getpid());
391 std::string error;
392 bool bOk = false;
393 std::error_code ec;
394 std::unique_ptr<raw_fd_ostream> ostream(
395 new raw_fd_ostream(filename, ec, sys::fs::F_None));
396 if( !ec)
398 it->second.write( *ostream );
399 ostream->close();
400 if( !ostream->has_error() && rename( filename, modifyFile.c_str()) == 0 )
401 bOk = true;
403 else
404 error = "error: " + ec.message();
405 ostream->clear_error();
406 unlink( filename );
407 if( !bOk )
408 report( DiagnosticsEngine::Error, "cannot write modified source to %0 (%1)" ) << modifyFile << error;
409 delete[] filename;
411 #endif
414 namespace {
416 // BEGIN code copied from LLVM's clang/lib/Sema/Sema.cpp
418 /// Returns true, if all methods and nested classes of the given
419 /// CXXRecordDecl are defined in this translation unit.
421 /// Should only be called from ActOnEndOfTranslationUnit so that all
422 /// definitions are actually read.
423 static bool MethodsAndNestedClassesComplete(const CXXRecordDecl *RD,
424 RecordCompleteMap &MNCComplete) {
425 RecordCompleteMap::iterator Cache = MNCComplete.find(RD);
426 if (Cache != MNCComplete.end())
427 return Cache->second;
428 if (!RD->isCompleteDefinition())
429 return false;
430 bool Complete = true;
431 for (DeclContext::decl_iterator I = RD->decls_begin(),
432 E = RD->decls_end();
433 I != E && Complete; ++I) {
434 if (const CXXMethodDecl *M = dyn_cast<CXXMethodDecl>(*I))
435 Complete = M->isDefined() || M->isDefaulted() ||
436 (M->isPure() && !isa<CXXDestructorDecl>(M));
437 else if (const FunctionTemplateDecl *F = dyn_cast<FunctionTemplateDecl>(*I))
438 // If the template function is marked as late template parsed at this
439 // point, it has not been instantiated and therefore we have not
440 // performed semantic analysis on it yet, so we cannot know if the type
441 // can be considered complete.
442 Complete = !F->getTemplatedDecl()->isLateTemplateParsed() &&
443 F->getTemplatedDecl()->isDefined();
444 else if (const CXXRecordDecl *R = dyn_cast<CXXRecordDecl>(*I)) {
445 if (R->isInjectedClassName())
446 continue;
447 if (R->hasDefinition())
448 Complete = MethodsAndNestedClassesComplete(R->getDefinition(),
449 MNCComplete);
450 else
451 Complete = false;
454 MNCComplete[RD] = Complete;
455 return Complete;
458 /// Returns true, if the given CXXRecordDecl is fully defined in this
459 /// translation unit, i.e. all methods are defined or pure virtual and all
460 /// friends, friend functions and nested classes are fully defined in this
461 /// translation unit.
463 /// Should only be called from ActOnEndOfTranslationUnit so that all
464 /// definitions are actually read.
465 static bool IsRecordFullyDefined(const CXXRecordDecl *RD,
466 RecordCompleteMap &RecordsComplete,
467 RecordCompleteMap &MNCComplete) {
468 RecordCompleteMap::iterator Cache = RecordsComplete.find(RD);
469 if (Cache != RecordsComplete.end())
470 return Cache->second;
471 bool Complete = MethodsAndNestedClassesComplete(RD, MNCComplete);
472 for (CXXRecordDecl::friend_iterator I = RD->friend_begin(),
473 E = RD->friend_end();
474 I != E && Complete; ++I) {
475 // Check if friend classes and methods are complete.
476 if (TypeSourceInfo *TSI = (*I)->getFriendType()) {
477 // Friend classes are available as the TypeSourceInfo of the FriendDecl.
478 if (CXXRecordDecl *FriendD = TSI->getType()->getAsCXXRecordDecl())
479 Complete = MethodsAndNestedClassesComplete(FriendD, MNCComplete);
480 else
481 Complete = false;
482 } else {
483 // Friend functions are available through the NamedDecl of FriendDecl.
484 if (const FunctionDecl *FD =
485 dyn_cast<FunctionDecl>((*I)->getFriendDecl()))
486 Complete = FD->isDefined();
487 else
488 // This is a template friend, give up.
489 Complete = false;
492 RecordsComplete[RD] = Complete;
493 return Complete;
496 // END code copied from LLVM's clang/lib/Sema/Sema.cpp
500 bool PluginHandler::isAllRelevantCodeDefined(NamedDecl const * decl) {
501 switch (decl->getAccess()) {
502 case AS_protected:
503 if (!cast<CXXRecordDecl>(decl->getDeclContext())->hasAttr<FinalAttr>()) {
504 break;
506 LLVM_FALLTHROUGH;
507 case AS_private:
508 if (IsRecordFullyDefined(
509 cast<CXXRecordDecl>(decl->getDeclContext()), RecordsComplete_, MNCComplete_))
511 return true;
513 break;
514 default:
515 break;
517 return !decl->isExternallyVisible();
520 std::unique_ptr<ASTConsumer> LibreOfficeAction::CreateASTConsumer( CompilerInstance& Compiler, StringRef )
522 #if __cplusplus >= 201402L
523 return std::make_unique<PluginHandler>( Compiler, _args );
524 #else
525 return llvm::make_unique<PluginHandler>( Compiler, _args );
526 #endif
529 bool LibreOfficeAction::ParseArgs( const CompilerInstance&, const std::vector< std::string >& args )
531 _args = args;
532 return true;
535 static FrontendPluginRegistry::Add< loplugin::LibreOfficeAction > X( "loplugin", "LibreOffice compile check plugin" );
537 } // namespace
539 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */