Version 6.1.0.2, tag libreoffice-6.1.0.2
[LibreOffice.git] / compilerplugins / clang / plugin.cxx
blob56d40e337bf900a197d110d12946aa9b120b388e
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 "plugin.hxx"
14 #include <cassert>
15 #include <cstddef>
16 #include <string>
18 #include <clang/Basic/FileManager.h>
19 #include <clang/Lex/Lexer.h>
21 #include "pluginhandler.hxx"
24 Base classes for plugin actions.
26 namespace loplugin
29 namespace {
31 Expr const * skipImplicit(Expr const * expr) {
32 if (auto const e = dyn_cast<MaterializeTemporaryExpr>(expr)) {
33 expr = e->GetTemporaryExpr()->IgnoreImpCasts();
35 if (auto const e = dyn_cast<CXXBindTemporaryExpr>(expr)) {
36 expr = e->getSubExpr();
38 return expr;
41 bool structurallyIdentical(Stmt const * stmt1, Stmt const * stmt2) {
42 if (stmt1->getStmtClass() != stmt2->getStmtClass()) {
43 return false;
45 switch (stmt1->getStmtClass()) {
46 case Stmt::CXXConstructExprClass:
47 if (cast<CXXConstructExpr>(stmt1)->getConstructor()->getCanonicalDecl()
48 != cast<CXXConstructExpr>(stmt2)->getConstructor()->getCanonicalDecl())
50 return false;
52 break;
53 case Stmt::DeclRefExprClass:
54 if (cast<DeclRefExpr>(stmt1)->getDecl()->getCanonicalDecl()
55 != cast<DeclRefExpr>(stmt2)->getDecl()->getCanonicalDecl())
57 return false;
59 break;
60 case Stmt::ImplicitCastExprClass:
62 auto const e1 = cast<ImplicitCastExpr>(stmt1);
63 auto const e2 = cast<ImplicitCastExpr>(stmt2);
64 if (e1->getCastKind() != e2->getCastKind()
65 || e1->getType().getCanonicalType() != e2->getType().getCanonicalType())
67 return false;
69 break;
71 case Stmt::MemberExprClass:
73 auto const e1 = cast<MemberExpr>(stmt1);
74 auto const e2 = cast<MemberExpr>(stmt2);
75 if (e1->isArrow() != e2->isArrow()
76 || e1->getType().getCanonicalType() != e2->getType().getCanonicalType())
78 return false;
80 break;
82 case Stmt::CXXMemberCallExprClass:
83 case Stmt::CXXOperatorCallExprClass:
84 if (cast<Expr>(stmt1)->getType().getCanonicalType()
85 != cast<Expr>(stmt2)->getType().getCanonicalType())
87 return false;
89 break;
90 case Stmt::MaterializeTemporaryExprClass:
91 case Stmt::ParenExprClass:
92 break;
93 default:
94 // Conservatively assume non-identical for expressions that don't happen for us in practice
95 // when compiling the LO code base (and for which the above set of supported classes would
96 // need to be extended):
97 return false;
99 auto i1 = stmt1->child_begin();
100 auto e1 = stmt1->child_end();
101 auto i2 = stmt2->child_begin();
102 auto e2 = stmt2->child_end();
103 for (; i1 != e1; ++i1, ++i2) {
104 if (i2 == e2 || !structurallyIdentical(*i1, *i2)) {
105 return false;
108 return i2 == e2;
113 Plugin::Plugin( const InstantiationData& data )
114 : compiler( data.compiler ), handler( data.handler ), name( data.name )
118 DiagnosticBuilder Plugin::report( DiagnosticsEngine::Level level, StringRef message, SourceLocation loc ) const
120 return handler.report( level, name, message, compiler, loc );
123 void normalizeDotDotInFilePath( std::string & s )
125 for (std::string::size_type i = 0;;)
127 i = s.find("/.", i);
128 if (i == std::string::npos) {
129 break;
131 if (i + 2 == s.length() || s[i + 2] == '/') {
132 s.erase(i, 2); // [AAA]/.[/CCC] -> [AAA][/CCC]
133 } else if (s[i + 2] == '.'
134 && (i + 3 == s.length() || s[i + 3] == '/'))
136 if (i == 0) { // /..[/CCC] -> /..[/CCC]
137 break;
139 auto j = s.rfind('/', i - 1);
140 if (j == std::string::npos)
142 // BBB/..[/CCC] -> BBB/..[/CCC] (instead of BBB/../CCC ->
143 // CCC, to avoid wrong ../../CCC -> CCC; relative paths
144 // shouldn't happen anyway, and even if they did, wouldn't
145 // match against WORKDIR anyway, as WORKDIR should be
146 // absolute):
147 break;
149 s.erase(j, i + 3 - j); // AAA/BBB/..[/CCC] -> AAA[/CCC]
150 i = j;
151 } else {
152 i += 2;
157 void Plugin::registerPlugin( Plugin* (*create)( const InstantiationData& ), const char* optionName, bool isPPCallback, bool byDefault )
159 PluginHandler::registerPlugin( create, optionName, isPPCallback, byDefault );
162 bool Plugin::evaluate(const Expr* expr, APSInt& x)
164 if (expr->EvaluateAsInt(x, compiler.getASTContext()))
166 return true;
168 if (isa<CXXNullPtrLiteralExpr>(expr)) {
169 x = 0;
170 return true;
172 return false;
175 const Stmt* Plugin::getParentStmt( const Stmt* stmt )
177 auto parentsRange = compiler.getASTContext().getParents(*stmt);
178 if ( parentsRange.begin() == parentsRange.end())
179 return nullptr;
180 return parentsRange.begin()->get<Stmt>();
183 Stmt* Plugin::getParentStmt( Stmt* stmt )
185 auto parentsRange = compiler.getASTContext().getParents(*stmt);
186 if ( parentsRange.begin() == parentsRange.end())
187 return nullptr;
188 return const_cast<Stmt*>(parentsRange.begin()->get<Stmt>());
191 static const Decl* getDeclContext(ASTContext& context, const Stmt* stmt)
193 auto it = context.getParents(*stmt).begin();
195 if (it == context.getParents(*stmt).end())
196 return nullptr;
198 const Decl *aDecl = it->get<Decl>();
199 if (aDecl)
200 return aDecl;
202 const Stmt *aStmt = it->get<Stmt>();
203 if (aStmt)
204 return getDeclContext(context, aStmt);
206 return nullptr;
209 const FunctionDecl* Plugin::getParentFunctionDecl( const Stmt* stmt )
211 const Decl *decl = getDeclContext(compiler.getASTContext(), stmt);
212 if (decl)
213 return static_cast<const FunctionDecl*>(decl->getNonClosureContext());
215 return nullptr;
219 bool Plugin::isInUnoIncludeFile(SourceLocation spellingLocation) const
221 StringRef name {
222 compiler.getSourceManager().getFilename(spellingLocation) };
223 return compiler.getSourceManager().isInMainFile(spellingLocation)
224 ? (isSamePathname(name, SRCDIR "/cppu/source/cppu/compat.cxx")
225 || isSamePathname(name, SRCDIR "/cppuhelper/source/compat.cxx")
226 || isSamePathname(name, SRCDIR "/sal/osl/all/compat.cxx"))
227 : (hasPathnamePrefix(name, SRCDIR "/include/com/")
228 || hasPathnamePrefix(name, SRCDIR "/include/cppu/")
229 || hasPathnamePrefix(name, SRCDIR "/include/cppuhelper/")
230 || hasPathnamePrefix(name, SRCDIR "/include/osl/")
231 || hasPathnamePrefix(name, SRCDIR "/include/rtl/")
232 || hasPathnamePrefix(name, SRCDIR "/include/sal/")
233 || hasPathnamePrefix(name, SRCDIR "/include/salhelper/")
234 || hasPathnamePrefix(name, SRCDIR "/include/systools/")
235 || hasPathnamePrefix(name, SRCDIR "/include/typelib/")
236 || hasPathnamePrefix(name, SRCDIR "/include/uno/"));
239 bool Plugin::isInUnoIncludeFile(const FunctionDecl* functionDecl) const
241 return isInUnoIncludeFile(compiler.getSourceManager().getSpellingLoc(
242 functionDecl->getCanonicalDecl()->getNameInfo().getLoc()));
245 SourceLocation Plugin::locationAfterToken( SourceLocation location )
247 return Lexer::getLocForEndOfToken( location, 0, compiler.getSourceManager(), compiler.getLangOpts());
250 bool Plugin::isUnitTestMode()
252 return PluginHandler::isUnitTestMode();
255 bool Plugin::containsPreprocessingConditionalInclusion(SourceRange range)
257 auto const begin = compiler.getSourceManager().getExpansionLoc(
258 range.getBegin());
259 auto const end = compiler.getSourceManager().getExpansionLoc(
260 range.getEnd());
261 assert(begin.isFileID() && end.isFileID());
262 if (!(begin == end
263 || compiler.getSourceManager().isBeforeInTranslationUnit(
264 begin, end)))
266 // Conservatively assume "yes" if lexing fails (e.g., due to
267 // macros):
268 return true;
270 auto hash = false;
271 for (auto loc = begin;;) {
272 Token tok;
273 if (Lexer::getRawToken(
274 loc, tok, compiler.getSourceManager(),
275 compiler.getLangOpts(), true))
277 // Conservatively assume "yes" if lexing fails (e.g., due to
278 // macros):
279 return true;
281 if (hash && tok.is(tok::raw_identifier)) {
282 auto const id = tok.getRawIdentifier();
283 if (id == "if" || id == "ifdef" || id == "ifndef"
284 || id == "elif" || id == "else" || id == "endif")
286 return true;
289 if (loc == range.getEnd()) {
290 break;
292 hash = tok.is(tok::hash) && tok.isAtStartOfLine();
293 loc = loc.getLocWithOffset(
294 std::max<unsigned>(
295 Lexer::MeasureTokenLength(
296 loc, compiler.getSourceManager(),
297 compiler.getLangOpts()),
298 1));
300 return false;
303 Plugin::IdenticalDefaultArgumentsResult Plugin::checkIdenticalDefaultArguments(
304 Expr const * argument1, Expr const * argument2)
306 if ((argument1 == nullptr) != (argument2 == nullptr)) {
307 return IdenticalDefaultArgumentsResult::No;
309 if (argument1 == nullptr) {
310 return IdenticalDefaultArgumentsResult::Yes;
312 if (argument1->isNullPointerConstant(compiler.getASTContext(), Expr::NPC_NeverValueDependent)
313 && argument2->isNullPointerConstant(compiler.getASTContext(), Expr::NPC_NeverValueDependent))
315 return IdenticalDefaultArgumentsResult::Yes;
317 APSInt x1, x2;
318 if (evaluate(argument1, x1) && evaluate(argument2, x2))
320 return x1 == x2
321 ? IdenticalDefaultArgumentsResult::Yes
322 : IdenticalDefaultArgumentsResult::No;
324 #if CLANG_VERSION >= 30900
325 APFloat f1(0.0f), f2(0.0f);
326 if (argument1->EvaluateAsFloat(f1, compiler.getASTContext())
327 && argument2->EvaluateAsFloat(f2, compiler.getASTContext()))
329 return f1.bitwiseIsEqual(f2)
330 ? IdenticalDefaultArgumentsResult::Yes
331 : IdenticalDefaultArgumentsResult::No;
333 #endif
334 auto const desugared1 = argument1->IgnoreParenImpCasts();
335 auto const desugared2 = argument2->IgnoreParenImpCasts();
336 if (auto const lit1 = dyn_cast<clang::StringLiteral>(desugared1)) {
337 if (auto const lit2 = dyn_cast<clang::StringLiteral>(desugared2)) {
338 return lit1->getBytes() == lit2->getBytes()
339 ? IdenticalDefaultArgumentsResult::Yes
340 : IdenticalDefaultArgumentsResult::No;
343 // catch params with defaults like "= OUString()"
344 for (Expr const * e1 = desugared1, * e2 = desugared2;;) {
345 auto const ce1 = dyn_cast<CXXConstructExpr>(skipImplicit(e1));
346 auto const ce2 = dyn_cast<CXXConstructExpr>(skipImplicit(e2));
347 if (ce1 == nullptr || ce2 == nullptr) {
348 break;
350 if (ce1->getConstructor()->getCanonicalDecl() != ce2->getConstructor()->getCanonicalDecl())
352 return IdenticalDefaultArgumentsResult::No;
354 if (ce1->isElidable() && ce2->isElidable() && ce1->getNumArgs() == 1
355 && ce2->getNumArgs() == 1)
357 assert(ce1->getConstructor()->isCopyOrMoveConstructor());
358 e1 = ce1->getArg(0)->IgnoreImpCasts();
359 e2 = ce2->getArg(0)->IgnoreImpCasts();
360 continue;
362 if (ce1->getNumArgs() == 0 && ce2->getNumArgs() == 0) {
363 return IdenticalDefaultArgumentsResult::Yes;
365 break;
367 // If the EvaluateAsRValue derivatives above failed because the arguments use e.g. (constexpr)
368 // function template specializations that happen to not have been instantiated in this TU, try a
369 // structural comparison of the arguments:
370 if (structurallyIdentical(argument1, argument2)) {
371 return IdenticalDefaultArgumentsResult::Yes;
373 if (isDebugMode()) {
374 report(
375 DiagnosticsEngine::Fatal, "TODO: Unexpected 'IdenticalDefaultArgumentsResult::Maybe'",
376 argument1->getExprLoc())
377 << argument1->getSourceRange();
378 report(
379 DiagnosticsEngine::Note, "TODO: second argument is here", argument2->getExprLoc())
380 << argument2->getSourceRange();
381 argument1->dump();
382 argument2->dump();
384 return IdenticalDefaultArgumentsResult::Maybe;
387 RewritePlugin::RewritePlugin( const InstantiationData& data )
388 : Plugin( data )
389 , rewriter( data.rewriter )
393 bool RewritePlugin::insertText( SourceLocation Loc, StringRef Str, bool InsertAfter, bool indentNewLines )
395 assert( rewriter );
396 if (wouldRewriteWorkdir(Loc))
397 return false;
398 SourceRange Range(SourceRange(Loc, Loc.getLocWithOffset(Str.size())));
399 if( !handler.checkOverlap( Range ) )
401 report( DiagnosticsEngine::Warning, "double code removal, possible plugin error", Range.getBegin());
402 return false;
404 if( rewriter->InsertText( Loc, Str, InsertAfter, indentNewLines ))
405 return reportEditFailure( Loc );
406 handler.addSourceModification(Range);
407 return true;
410 bool RewritePlugin::insertTextAfter( SourceLocation Loc, StringRef Str )
412 assert( rewriter );
413 if (wouldRewriteWorkdir(Loc))
414 return false;
415 SourceRange Range(SourceRange(Loc, Loc.getLocWithOffset(Str.size())));
416 if( !handler.checkOverlap( Range ) )
418 report( DiagnosticsEngine::Warning, "double code removal, possible plugin error", Range.getBegin());
419 return false;
421 if( rewriter->InsertTextAfter( Loc, Str ))
422 return reportEditFailure( Loc );
423 handler.addSourceModification(Range);
424 return true;
427 bool RewritePlugin::insertTextAfterToken( SourceLocation Loc, StringRef Str )
429 assert( rewriter );
430 if (wouldRewriteWorkdir(Loc))
431 return false;
432 SourceRange Range(SourceRange(Loc, Loc.getLocWithOffset(Str.size())));
433 if( !handler.checkOverlap( Range ) )
435 report( DiagnosticsEngine::Warning, "double code removal, possible plugin error", Range.getBegin());
436 return false;
438 if( rewriter->InsertTextAfterToken( Loc, Str ))
439 return reportEditFailure( Loc );
440 handler.addSourceModification(Range);
441 return true;
444 bool RewritePlugin::insertTextBefore( SourceLocation Loc, StringRef Str )
446 assert( rewriter );
447 if (wouldRewriteWorkdir(Loc))
448 return false;
449 SourceRange Range(SourceRange(Loc, Loc.getLocWithOffset(Str.size())));
450 if( !handler.checkOverlap( Range ) )
452 report( DiagnosticsEngine::Warning, "double code removal, possible plugin error", Range.getBegin());
453 return false;
455 if( rewriter->InsertTextBefore( Loc, Str ))
456 return reportEditFailure( Loc );
457 handler.addSourceModification(Range);
458 return true;
461 bool RewritePlugin::removeText( SourceLocation Start, unsigned Length, RewriteOptions opts )
463 CharSourceRange range( SourceRange( Start, Start.getLocWithOffset( Length )), false );
464 return removeText( range, opts );
467 bool RewritePlugin::removeText( SourceRange range, RewriteOptions opts )
469 return removeText( CharSourceRange( range, true ), opts );
472 bool RewritePlugin::removeText( CharSourceRange range, RewriteOptions opts )
474 assert( rewriter );
475 if (wouldRewriteWorkdir(range.getBegin()))
476 return false;
477 if( rewriter->getRangeSize( range, opts ) == -1 )
478 return reportEditFailure( range.getBegin());
479 if( !handler.checkOverlap( range.getAsRange() ) )
481 report( DiagnosticsEngine::Warning, "double code removal, possible plugin error", range.getBegin());
482 return false;
484 if( opts.flags & RemoveWholeStatement || opts.flags & RemoveAllWhitespace )
486 if( !adjustRangeForOptions( &range, opts ))
487 return reportEditFailure( range.getBegin());
489 if( rewriter->RemoveText( range, opts ))
490 return reportEditFailure( range.getBegin());
491 handler.addSourceModification(range.getAsRange());
492 return true;
495 bool RewritePlugin::adjustRangeForOptions( CharSourceRange* range, RewriteOptions opts )
497 assert( rewriter );
498 SourceManager& SM = rewriter->getSourceMgr();
499 SourceLocation fileStartLoc = SM.getLocForStartOfFile( SM.getFileID( range->getBegin()));
500 if( fileStartLoc.isInvalid())
501 return false;
502 bool isInvalid = false;
503 const char* fileBuf = SM.getCharacterData( fileStartLoc, &isInvalid );
504 if( isInvalid )
505 return false;
506 const char* startBuf = SM.getCharacterData( range->getBegin(), &isInvalid );
507 if( isInvalid )
508 return false;
509 SourceLocation locationEnd = range->getEnd();
510 if( range->isTokenRange())
511 locationEnd = locationAfterToken( locationEnd );
512 const char* endBuf = SM.getCharacterData( locationEnd, &isInvalid );
513 if( isInvalid )
514 return false;
515 const char* startPos = startBuf;
516 --startPos;
517 while( startPos >= fileBuf && ( *startPos == ' ' || *startPos == '\t' ))
518 --startPos;
519 if( startPos >= fileBuf && *startPos == '\n' )
520 startPos = startBuf - 1; // do not remove indentation whitespace (RemoveLineIfEmpty can do that)
521 const char* endPos = endBuf;
522 while( *endPos == ' ' || *endPos == '\t' )
523 ++endPos;
524 if( opts.flags & RemoveWholeStatement )
526 if( *endPos == ';' )
527 ++endPos;
528 else
529 return false;
531 *range = CharSourceRange( SourceRange( range->getBegin().getLocWithOffset( startPos - startBuf + 1 ),
532 locationEnd.getLocWithOffset( endPos - endBuf )), false );
533 return true;
536 bool RewritePlugin::replaceText( SourceLocation Start, unsigned OrigLength, StringRef NewStr )
538 assert( rewriter );
539 if (wouldRewriteWorkdir(Start))
540 return false;
541 SourceRange Range(Start, Start.getLocWithOffset(std::max<size_t>(OrigLength, NewStr.size())));
542 if( OrigLength != 0 && !handler.checkOverlap( Range ) )
544 report( DiagnosticsEngine::Warning, "overlapping code replacement, possible plugin error", Start );
545 return false;
547 if( rewriter->ReplaceText( Start, OrigLength, NewStr ))
548 return reportEditFailure( Start );
549 handler.addSourceModification(Range);
550 return true;
553 bool RewritePlugin::replaceText( SourceRange range, StringRef NewStr )
555 assert( rewriter );
556 if (wouldRewriteWorkdir(range.getBegin()))
557 return false;
558 if( rewriter->getRangeSize( range ) == -1 )
559 return reportEditFailure( range.getBegin());
560 if( !handler.checkOverlap( range ) )
562 report( DiagnosticsEngine::Warning, "overlapping code replacement, possible plugin error", range.getBegin());
563 return false;
565 if( rewriter->ReplaceText( range, NewStr ))
566 return reportEditFailure( range.getBegin());
567 handler.addSourceModification(range);
568 return true;
571 bool RewritePlugin::replaceText( SourceRange range, SourceRange replacementRange )
573 assert( rewriter );
574 if (wouldRewriteWorkdir(range.getBegin()))
575 return false;
576 if( rewriter->getRangeSize( range ) == -1 )
577 return reportEditFailure( range.getBegin());
578 if( !handler.checkOverlap( range ) )
580 report( DiagnosticsEngine::Warning, "overlapping code replacement, possible plugin error", range.getBegin());
581 return false;
583 if( rewriter->ReplaceText( range, replacementRange ))
584 return reportEditFailure( range.getBegin());
585 handler.addSourceModification(range);
586 return true;
589 bool RewritePlugin::wouldRewriteWorkdir(SourceLocation loc)
591 if (loc.isInvalid() || loc.isMacroID()) {
592 return false;
594 return
595 compiler.getSourceManager().getFilename(
596 compiler.getSourceManager().getSpellingLoc(loc))
597 .startswith(WORKDIR "/");
600 bool RewritePlugin::reportEditFailure( SourceLocation loc )
602 report( DiagnosticsEngine::Warning, "cannot perform source modification (macro expansion involved?)", loc );
603 return false;
606 namespace {
608 template<typename Fn> bool checkPathname(
609 StringRef pathname, StringRef against, Fn check)
611 if (check(pathname, against)) {
612 return true;
614 #if defined _WIN32
615 for (std::size_t n = 0;;)
617 std::size_t n1 = pathname.find('\\', n);
618 std::size_t n2 = against.find('\\', n);
619 if (n1 <= n2) {
620 if (n1 >= against.size()) {
621 return check(pathname.substr(n), against.substr(n));
623 if ((against[n1] != '/' && against[n1] != '\\')
624 || pathname.substr(n, n1 - n) != against.substr(n, n1 - n))
626 break;
628 n = n1 + 1;
629 } else {
630 if (n2 >= pathname.size()) {
631 return check(pathname.substr(n), against.substr(n));
633 if (pathname[n2] != '/'
634 || pathname.substr(n, n2 - n) != against.substr(n, n2 - n))
636 break;
638 n = n2 + 1;
641 #endif
642 return false;
647 bool hasPathnamePrefix(StringRef pathname, StringRef prefix)
649 return checkPathname(
650 pathname, prefix,
651 [](StringRef p, StringRef a) { return p.startswith(a); });
654 bool isSamePathname(StringRef pathname, StringRef other)
656 return checkPathname(
657 pathname, other, [](StringRef p, StringRef a) { return p == a; });
660 } // namespace
662 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */