bump product version to 6.3.0.0.beta1
[LibreOffice.git] / compilerplugins / clang / pluginhandler.cxx
blob4296b981f76774a4cf1192e26eb6e51cf58adab5
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>
15 #include "plugin.hxx"
16 #include "pluginhandler.hxx"
18 #include <clang/Frontend/CompilerInstance.h>
19 #include <clang/Frontend/FrontendPluginRegistry.h>
20 #include <clang/Lex/PPCallbacks.h>
21 #include <stdio.h>
23 #if defined _WIN32
24 #include <process.h>
25 #else
26 #include <sys/stat.h>
27 #include <unistd.h>
28 #endif
30 /**
31 This source file manages all plugin actions. It is not necessary to modify this
32 file when adding new actions.
35 static bool isPrefix( const std::string& prefix, const std::string& full)
37 return full.compare(0, prefix.size(), prefix) == 0;
40 namespace loplugin
43 struct PluginData
45 Plugin* (*create)( const InstantiationData& );
46 Plugin* object;
47 const char* optionName;
48 bool isPPCallback;
49 bool isSharedPlugin;
50 bool byDefault;
51 bool disabledRun;
54 const int MAX_PLUGINS = 200;
55 static PluginData plugins[ MAX_PLUGINS ];
56 static int pluginCount = 0;
57 static bool bPluginObjectsCreated = false;
58 static bool unitTestMode = false;
60 StringRef initMainFileName(CompilerInstance& compiler)
62 StringRef const& fn(compiler.getASTContext().getSourceManager().getFileEntryForID(
63 compiler.getASTContext().getSourceManager().getMainFileID())->getName());
64 if (fn == "<stdin>")
65 // stdin means icecream, so we can rely on -main-file-name containing the full path name
66 return compiler.getCodeGenOpts().MainFileName;
67 else
68 // this is always a full path name
69 return fn;
72 PluginHandler::PluginHandler( CompilerInstance& compiler, const std::vector< std::string >& args )
73 : compiler( compiler )
74 , mainFileName(initMainFileName(compiler))
75 , rewriter( compiler.getSourceManager(), compiler.getLangOpts())
76 , scope( "mainfile" )
77 , warningsAsErrors( false )
79 std::set< std::string > rewriters;
80 for( std::string const & arg : args )
82 if( arg.size() >= 2 && arg[ 0 ] == '-' && arg[ 1 ] == '-' )
83 handleOption( arg.substr( 2 ));
84 else
85 rewriters.insert( arg );
87 createPlugins( rewriters );
88 bPluginObjectsCreated = true;
91 PluginHandler::~PluginHandler()
93 for( int i = 0; i < pluginCount; ++i )
94 if( plugins[ i ].object != NULL )
96 // PPCallbacks is owned by preprocessor object, don't delete those
97 if( !plugins[ i ].isPPCallback )
98 delete plugins[ i ].object;
102 bool PluginHandler::isUnitTestMode()
104 return unitTestMode;
107 void PluginHandler::handleOption( const std::string& option )
109 if( option.substr( 0, 6 ) == "scope=" )
111 scope = option.substr( 6 );
112 if( scope == "mainfile" || scope == "all" )
113 ; // ok
114 else
116 #if !defined _WIN32 //TODO, S_ISDIR
117 struct stat st;
118 if( stat(( SRCDIR "/" + scope ).c_str(), &st ) != 0 || !S_ISDIR( st.st_mode ))
119 report( DiagnosticsEngine::Fatal, "unknown scope %0 (no such module directory)" ) << scope;
120 #endif
123 else if( option.substr( 0, 14 ) == "warnings-only=" )
125 warningsOnly = option.substr(14);
127 else if( option == "warnings-as-errors" )
128 warningsAsErrors = true;
129 else if( option == "unit-test-mode" )
130 unitTestMode = true;
131 else if (option == "debug")
132 debugMode = true;
133 else
134 report( DiagnosticsEngine::Fatal, "unknown option %0" ) << option;
137 void PluginHandler::createPlugins( std::set< std::string > rewriters )
139 for( int i = 0; i < pluginCount; ++i )
141 const char* name = plugins[i].optionName;
142 // When in unit-test mode, ignore plugins whose names don't match the filename of the test,
143 // so that we only generate warnings for the plugin that we want to test.
144 if (unitTestMode && mainFileName.find(plugins[ i ].optionName) == StringRef::npos)
145 continue;
146 if( rewriters.erase( name ) != 0 )
147 plugins[ i ].object = plugins[ i ].create( InstantiationData { name, *this, compiler, &rewriter } );
148 else if( plugins[ i ].byDefault )
149 plugins[ i ].object = plugins[ i ].create( InstantiationData { name, *this, compiler, NULL } );
150 else if( unitTestMode && strcmp(name, "unusedmethodsremove") != 0 && strcmp(name, "unusedfieldsremove") != 0)
151 plugins[ i ].object = plugins[ i ].create( InstantiationData { name, *this, compiler, NULL } );
153 for( auto r: rewriters )
154 report( DiagnosticsEngine::Fatal, "unknown plugin tool %0" ) << r;
155 // If there is a shared plugin, make it handle all plugins that it can handle.
156 for( int i = 0; i < pluginCount; ++i )
158 if( plugins[ i ].isSharedPlugin && plugins[ i ].object != nullptr )
160 Plugin* plugin = plugins[ i ].object;
161 for( int j = 0; j < pluginCount; ++j )
163 if( plugins[ j ].object != nullptr
164 && plugin->setSharedPlugin( plugins[ j ].object, plugins[ j ].optionName ))
166 plugins[ j ].disabledRun = true;
173 void PluginHandler::registerPlugin( Plugin* (*create)( const InstantiationData& ), const char* optionName,
174 bool isPPCallback, bool isSharedPlugin, bool byDefault )
176 assert( !bPluginObjectsCreated );
177 assert( pluginCount < MAX_PLUGINS );
178 plugins[ pluginCount ].create = create;
179 plugins[ pluginCount ].object = NULL;
180 plugins[ pluginCount ].optionName = optionName;
181 plugins[ pluginCount ].isPPCallback = isPPCallback;
182 plugins[ pluginCount ].isSharedPlugin = isSharedPlugin;
183 plugins[ pluginCount ].byDefault = byDefault;
184 plugins[ pluginCount ].disabledRun = false;
185 ++pluginCount;
188 DiagnosticBuilder PluginHandler::report( DiagnosticsEngine::Level level, const char* plugin, StringRef message, CompilerInstance& compiler,
189 SourceLocation loc )
191 DiagnosticsEngine& diag = compiler.getDiagnostics();
192 // Do some mappings (e.g. for -Werror) that clang does not do for custom messages for some reason.
193 if( level == DiagnosticsEngine::Warning && ((diag.getWarningsAsErrors() && (plugin == nullptr || plugin != warningsOnly)) || warningsAsErrors))
194 level = DiagnosticsEngine::Error;
195 if( level == DiagnosticsEngine::Error && diag.getErrorsAsFatal())
196 level = DiagnosticsEngine::Fatal;
197 std::string fullMessage = ( message + " [loplugin" ).str();
198 if( plugin )
200 fullMessage += ":";
201 fullMessage += plugin;
203 fullMessage += "]";
204 if( loc.isValid())
205 return diag.Report( loc, diag.getDiagnosticIDs()->getCustomDiagID(static_cast<DiagnosticIDs::Level>(level), fullMessage) );
206 else
207 return diag.Report( diag.getDiagnosticIDs()->getCustomDiagID(static_cast<DiagnosticIDs::Level>(level), fullMessage) );
210 DiagnosticBuilder PluginHandler::report( DiagnosticsEngine::Level level, StringRef message, SourceLocation loc )
212 return report( level, nullptr, message, compiler, loc );
215 bool PluginHandler::ignoreLocation(SourceLocation loc) {
216 auto i = ignored_.find(loc);
217 if (i == ignored_.end()) {
218 i = ignored_.emplace(loc, checkIgnoreLocation(loc)).first;
220 return i->second;
223 bool PluginHandler::checkIgnoreLocation(SourceLocation loc)
225 SourceLocation expansionLoc = compiler.getSourceManager().getExpansionLoc( loc );
226 if( compiler.getSourceManager().isInSystemHeader( expansionLoc ))
227 return true;
228 PresumedLoc presumedLoc = compiler.getSourceManager().getPresumedLoc( expansionLoc );
229 if( presumedLoc.isInvalid())
230 return true;
231 const char* bufferName = presumedLoc.getFilename();
232 if (bufferName == NULL
233 || hasPathnamePrefix(bufferName, SRCDIR "/external/")
234 || isSamePathname(bufferName, SRCDIR "/sdext/source/pdfimport/wrapper/keyword_list") )
235 // workdir/CustomTarget/sdext/pdfimport/hash.cxx is generated from
236 // sdext/source/pdfimport/wrapper/keyword_list by gperf, which
237 // inserts various #line directives denoting the latter into the
238 // former, but fails to add a #line directive returning back to
239 // hash.cxx itself before the gperf generated boilerplate, so
240 // compilers erroneously consider errors in the boilerplate to come
241 // from keyword_list instead of hash.cxx (for Clang on Linux/macOS
242 // this is not an issue due to the '#pragma GCC system_header'
243 // generated into the start of hash.cxx, #if'ed for __GNUC__, but
244 // for clang-cl it is an issue)
245 return true;
246 if( hasPathnamePrefix(bufferName, WORKDIR "/") )
248 // workdir/CustomTarget/vcl/unx/kde4/tst_exclude_socket_notifiers.moc
249 // includes
250 // "../../../../../vcl/unx/kde4/tst_exclude_socket_notifiers.hxx",
251 // making the latter file erroneously match here; so strip any ".."
252 // segments:
253 if (strstr(bufferName, "/..") == nullptr) {
254 return true;
256 std::string s(bufferName);
257 normalizeDotDotInFilePath(s);
258 if (hasPathnamePrefix(s, WORKDIR "/"))
259 return true;
261 if( hasPathnamePrefix(bufferName, BUILDDIR "/")
262 || hasPathnamePrefix(bufferName, SRCDIR "/") )
263 return false; // ok
264 return true;
267 // If we overlap with a previous area we modified, we cannot perform this change
268 // without corrupting the source
269 bool PluginHandler::checkOverlap(SourceRange range)
271 SourceManager& SM = compiler.getSourceManager();
272 char const *p1 = SM.getCharacterData( range.getBegin() );
273 char const *p2 = SM.getCharacterData( range.getEnd() );
274 for (std::pair<char const *, char const *> const & rPair : mvModifiedRanges)
276 if (rPair.first <= p1 && p1 <= rPair.second)
277 return false;
278 if (p1 <= rPair.second && rPair.first <= p2)
279 return false;
281 return true;
284 void PluginHandler::addSourceModification(SourceRange range)
286 SourceManager& SM = compiler.getSourceManager();
287 char const *p1 = SM.getCharacterData( range.getBegin() );
288 char const *p2 = SM.getCharacterData( range.getEnd() );
289 mvModifiedRanges.emplace_back(p1, p2);
292 void PluginHandler::HandleTranslationUnit( ASTContext& context )
294 if( context.getDiagnostics().hasErrorOccurred())
295 return;
296 if (mainFileName.endswith(".ii"))
298 report(DiagnosticsEngine::Fatal,
299 "input file has suffix .ii: \"%0\"\nhighly suspicious, probably ccache generated, this will break warning suppressions; export CCACHE_CPP2=1 to prevent this") << mainFileName;
300 return;
303 for( int i = 0; i < pluginCount; ++i )
305 if( plugins[ i ].object != NULL && !plugins[ i ].disabledRun )
307 plugins[ i ].object->run();
310 #if defined _WIN32
311 //TODO: make the call to 'rename' work on Windows (where the renamed-to
312 // original file is probably still held open somehow):
313 rewriter.overwriteChangedFiles();
314 #else
315 for( Rewriter::buffer_iterator it = rewriter.buffer_begin();
316 it != rewriter.buffer_end();
317 ++it )
319 const FileEntry* e = context.getSourceManager().getFileEntryForID( it->first );
320 if( e == NULL )
321 continue; // Failed modification because of a macro expansion?
322 /* Check where the file actually is, and warn about cases where modification
323 most probably doesn't matter (generated files in workdir).
324 The order here is important, as INSTDIR and WORKDIR are often in SRCDIR/BUILDDIR,
325 and BUILDDIR is sometimes in SRCDIR. */
326 std::string modifyFile;
327 const char* pathWarning = NULL;
328 bool bSkip = false;
329 StringRef const name = e->getName();
330 if( name.startswith(WORKDIR "/") )
331 pathWarning = "modified source in workdir/ : %0";
332 else if( strcmp( SRCDIR, BUILDDIR ) != 0 && name.startswith(BUILDDIR "/") )
333 pathWarning = "modified source in build dir : %0";
334 else if( name.startswith(SRCDIR "/") )
335 ; // ok
336 else
338 pathWarning = "modified source in unknown location, not modifying : %0";
339 bSkip = true;
341 if( modifyFile.empty())
342 modifyFile = name;
343 // Check whether the modified file is in the wanted scope
344 if( scope == "mainfile" )
346 if( it->first != context.getSourceManager().getMainFileID())
347 continue;
349 else if( scope == "all" )
350 ; // ok
351 else // scope is module
353 if( !( isPrefix( SRCDIR "/" + scope + "/", modifyFile ) || isPrefix( SRCDIR "/include/" + scope + "/", modifyFile ) ) )
354 continue;
356 // Warn only now, so that files not in scope do not cause warnings.
357 if( pathWarning != NULL )
358 report( DiagnosticsEngine::Warning, pathWarning ) << name;
359 if( bSkip )
360 continue;
361 char* filename = new char[ modifyFile.length() + 100 ];
362 sprintf( filename, "%s.new.%d", modifyFile.c_str(), getpid());
363 std::string error;
364 bool bOk = false;
365 std::error_code ec;
366 std::unique_ptr<raw_fd_ostream> ostream(
367 new raw_fd_ostream(filename, ec, sys::fs::F_None));
368 if( !ec)
370 it->second.write( *ostream );
371 ostream->close();
372 if( !ostream->has_error() && rename( filename, modifyFile.c_str()) == 0 )
373 bOk = true;
375 else
376 error = "error: " + ec.message();
377 ostream->clear_error();
378 unlink( filename );
379 if( !bOk )
380 report( DiagnosticsEngine::Error, "cannot write modified source to %0 (%1)" ) << modifyFile << error;
381 delete[] filename;
383 #endif
386 std::unique_ptr<ASTConsumer> LibreOfficeAction::CreateASTConsumer( CompilerInstance& Compiler, StringRef )
388 return llvm::make_unique<PluginHandler>( Compiler, _args );
391 bool LibreOfficeAction::ParseArgs( const CompilerInstance&, const std::vector< std::string >& args )
393 _args = args;
394 return true;
397 static FrontendPluginRegistry::Add< loplugin::LibreOfficeAction > X( "loplugin", "LibreOffice compile check plugin" );
399 } // namespace
401 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */