lok: getSlideShowInfo: interactions: check that properties are available
[LibreOffice.git] / compilerplugins / clang / pluginhandler.cxx
blob0fb62eb56c48c3ed2bf50dacf8593c9defc17431
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 "compat.hxx"
19 #include "plugin.hxx"
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>
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().getFileEntryRefForID(
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 // 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 )
247 return true;
250 SourceLocation expansionLoc = compiler.getSourceManager().getExpansionLoc( loc );
251 if( compiler.getSourceManager().isInSystemHeader( expansionLoc ))
252 return true;
253 PresumedLoc presumedLoc = compiler.getSourceManager().getPresumedLoc( expansionLoc );
254 if( presumedLoc.isInvalid())
255 return true;
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)
270 return true;
271 if( hasPathnamePrefix(bufferName, WORKDIR "/") )
273 // workdir/CustomTarget/vcl/unx/kde4/tst_exclude_socket_notifiers.moc
274 // includes
275 // "../../../../../vcl/unx/kde4/tst_exclude_socket_notifiers.hxx",
276 // making the latter file erroneously match here; so strip any ".."
277 // segments:
278 if (strstr(bufferName, "/..") == nullptr) {
279 return true;
281 std::string s(bufferName);
282 normalizeDotDotInFilePath(s);
283 if (hasPathnamePrefix(s, WORKDIR "/"))
284 return true;
286 if( hasPathnamePrefix(bufferName, BUILDDIR "/")
287 || hasPathnamePrefix(bufferName, SRCDIR "/") )
288 return false; // ok
289 return true;
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)
302 return false;
303 if (p1 <= rPair.second && rPair.first <= p2)
304 return false;
306 return true;
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())
321 return;
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;
326 return;
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();
337 #if defined _WIN32
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();
341 #else
342 for( Rewriter::buffer_iterator it = rewriter.buffer_begin();
343 it != rewriter.buffer_end();
344 ++it )
346 auto e = context.getSourceManager().getFileEntryRefForID( it->first );
347 if( !e )
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;
355 bool bSkip = false;
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 "/") )
362 ; // ok
363 else
365 pathWarning = "modified source in unknown location, not modifying : %0";
366 bSkip = true;
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())
374 continue;
376 else if( scope == "all" )
377 ; // ok
378 else // scope is module
380 if( !( isPrefix( SRCDIR "/" + scope + "/", modifyFile ) || isPrefix( SRCDIR "/include/" + scope + "/", modifyFile ) ) )
381 continue;
383 // Warn only now, so that files not in scope do not cause warnings.
384 if( pathWarning != NULL )
385 report( DiagnosticsEngine::Warning, pathWarning ) << name;
386 if( bSkip )
387 continue;
388 auto const filename = modifyFile + ".new." + itostr(getpid());
389 std::string error;
390 bool bOk = false;
391 std::error_code ec;
392 std::unique_ptr<raw_fd_ostream> ostream(
393 new raw_fd_ostream(filename, ec, sys::fs::OF_None));
394 if( !ec)
396 it->second.write( *ostream );
397 ostream->close();
398 if( !ostream->has_error() && rename( filename.c_str(), modifyFile.c_str()) == 0 )
399 bOk = true;
401 else
402 error = "error: " + ec.message();
403 ostream->clear_error();
404 unlink( filename.c_str() );
405 if( !bOk )
406 report( DiagnosticsEngine::Error, "cannot write modified source to %0 (%1)" ) << modifyFile << error;
408 #endif
411 namespace {
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())
426 return false;
427 bool Complete = true;
428 for (DeclContext::decl_iterator I = RD->decls_begin(),
429 E = RD->decls_end();
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())
443 continue;
444 if (R->hasDefinition())
445 Complete = MethodsAndNestedClassesComplete(R->getDefinition(),
446 MNCComplete);
447 else
448 Complete = false;
451 MNCComplete[RD] = Complete;
452 return 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);
477 else
478 Complete = false;
479 } else {
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();
484 else
485 // This is a template friend, give up.
486 Complete = false;
489 RecordsComplete[RD] = Complete;
490 return Complete;
493 // END code copied from LLVM's clang/lib/Sema/Sema.cpp
497 bool PluginHandler::isAllRelevantCodeDefined(NamedDecl const * decl) {
498 switch (decl->getAccess()) {
499 case AS_protected:
500 if (!cast<CXXRecordDecl>(decl->getDeclContext())->hasAttr<FinalAttr>()) {
501 break;
503 LLVM_FALLTHROUGH;
504 case AS_private:
505 if (IsRecordFullyDefined(
506 cast<CXXRecordDecl>(decl->getDeclContext()), RecordsComplete_, MNCComplete_))
508 return true;
510 break;
511 default:
512 break;
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 );
521 #else
522 return llvm::make_unique<PluginHandler>( Compiler, _args );
523 #endif
526 bool LibreOfficeAction::ParseArgs( const CompilerInstance&, const std::vector< std::string >& args )
528 _args = args;
529 return true;
532 static FrontendPluginRegistry::Add< loplugin::LibreOfficeAction > X( "loplugin", "LibreOffice compile check plugin" );
534 } // namespace
536 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */