bump product version to 6.3.0.0.beta1
[LibreOffice.git] / compilerplugins / clang / plugin.cxx
blob1a1ca44a91ab54eb9c139176cf3fb82b87abc08d
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,
158 bool isPPCallback, bool isSharedPlugin, bool byDefault )
160 PluginHandler::registerPlugin( create, optionName, isPPCallback, isSharedPlugin, byDefault );
163 bool Plugin::evaluate(const Expr* expr, APSInt& x)
165 if (compat::EvaluateAsInt(expr, x, compiler.getASTContext()))
167 return true;
169 if (isa<CXXNullPtrLiteralExpr>(expr)) {
170 x = 0;
171 return true;
173 return false;
176 const Stmt* Plugin::getParentStmt( const Stmt* stmt )
178 auto parentsRange = compiler.getASTContext().getParents(*stmt);
179 if ( parentsRange.begin() == parentsRange.end())
180 return nullptr;
181 return parentsRange.begin()->get<Stmt>();
184 Stmt* Plugin::getParentStmt( Stmt* stmt )
186 auto parentsRange = compiler.getASTContext().getParents(*stmt);
187 if ( parentsRange.begin() == parentsRange.end())
188 return nullptr;
189 return const_cast<Stmt*>(parentsRange.begin()->get<Stmt>());
192 static const Decl* getDeclContext(ASTContext& context, const Stmt* stmt)
194 auto it = context.getParents(*stmt).begin();
196 if (it == context.getParents(*stmt).end())
197 return nullptr;
199 const Decl *aDecl = it->get<Decl>();
200 if (aDecl)
201 return aDecl;
203 const Stmt *aStmt = it->get<Stmt>();
204 if (aStmt)
205 return getDeclContext(context, aStmt);
207 return nullptr;
210 const FunctionDecl* Plugin::getParentFunctionDecl( const Stmt* stmt )
212 const Decl *decl = getDeclContext(compiler.getASTContext(), stmt);
213 if (decl)
214 return static_cast<const FunctionDecl*>(decl->getNonClosureContext());
216 return nullptr;
219 StringRef Plugin::getFileNameOfSpellingLoc(SourceLocation spellingLocation) const
221 // prevent crashes when running the global-analysis plugins
222 if (!spellingLocation.isValid())
223 return "";
225 static enum { NOINIT, STDIN, GOOD } s_Mode(NOINIT);
226 if (s_Mode == GOOD)
228 return compiler.getSourceManager().getFilename(spellingLocation);
230 else if (s_Mode == STDIN
231 || !compiler.getSourceManager().isInMainFile(spellingLocation))
233 const char* bufferName = compiler.getSourceManager().getPresumedLoc(spellingLocation).getFilename();
234 return bufferName;
236 else
238 auto const fn(compiler.getSourceManager().getFilename(spellingLocation));
239 if (!fn.data()) // wtf? happens in sot/source/sdstor/stg.cxx
241 return fn;
243 #if !defined _WIN32
244 assert(fn.startswith("/") || fn == "<stdin>");
245 #endif
246 s_Mode = fn == "<stdin>" ? STDIN : GOOD;
247 return getFileNameOfSpellingLoc(spellingLocation);
251 bool Plugin::isInUnoIncludeFile(SourceLocation spellingLocation) const
253 StringRef name{ getFileNameOfSpellingLoc(spellingLocation) };
254 return compiler.getSourceManager().isInMainFile(spellingLocation)
255 ? (isSamePathname(name, SRCDIR "/cppu/source/cppu/compat.cxx")
256 || isSamePathname(name, SRCDIR "/cppuhelper/source/compat.cxx")
257 || isSamePathname(name, SRCDIR "/sal/osl/all/compat.cxx"))
258 : (hasPathnamePrefix(name, SRCDIR "/include/com/")
259 || hasPathnamePrefix(name, SRCDIR "/include/cppu/")
260 || hasPathnamePrefix(name, SRCDIR "/include/cppuhelper/")
261 || hasPathnamePrefix(name, SRCDIR "/include/osl/")
262 || hasPathnamePrefix(name, SRCDIR "/include/rtl/")
263 || hasPathnamePrefix(name, SRCDIR "/include/sal/")
264 || hasPathnamePrefix(name, SRCDIR "/include/salhelper/")
265 || hasPathnamePrefix(name, SRCDIR "/include/systools/")
266 || hasPathnamePrefix(name, SRCDIR "/include/typelib/")
267 || hasPathnamePrefix(name, SRCDIR "/include/uno/"));
270 bool Plugin::isInUnoIncludeFile(const FunctionDecl* functionDecl) const
272 return isInUnoIncludeFile(compiler.getSourceManager().getSpellingLoc(
273 functionDecl->getCanonicalDecl()->getNameInfo().getLoc()));
276 SourceLocation Plugin::locationAfterToken( SourceLocation location )
278 return Lexer::getLocForEndOfToken( location, 0, compiler.getSourceManager(), compiler.getLangOpts());
281 bool Plugin::isUnitTestMode()
283 return PluginHandler::isUnitTestMode();
286 bool Plugin::containsPreprocessingConditionalInclusion(SourceRange range)
288 auto const begin = compiler.getSourceManager().getExpansionLoc(
289 range.getBegin());
290 auto const end = compiler.getSourceManager().getExpansionLoc(
291 range.getEnd());
292 assert(begin.isFileID() && end.isFileID());
293 if (!(begin == end
294 || compiler.getSourceManager().isBeforeInTranslationUnit(
295 begin, end)))
297 // Conservatively assume "yes" if lexing fails (e.g., due to
298 // macros):
299 return true;
301 auto hash = false;
302 for (auto loc = begin;;) {
303 Token tok;
304 if (Lexer::getRawToken(
305 loc, tok, compiler.getSourceManager(),
306 compiler.getLangOpts(), true))
308 // Conservatively assume "yes" if lexing fails (e.g., due to
309 // macros):
310 return true;
312 if (hash && tok.is(tok::raw_identifier)) {
313 auto const id = tok.getRawIdentifier();
314 if (id == "if" || id == "ifdef" || id == "ifndef"
315 || id == "elif" || id == "else" || id == "endif")
317 return true;
320 if (loc == range.getEnd()) {
321 break;
323 hash = tok.is(tok::hash) && tok.isAtStartOfLine();
324 loc = loc.getLocWithOffset(
325 std::max<unsigned>(
326 Lexer::MeasureTokenLength(
327 loc, compiler.getSourceManager(),
328 compiler.getLangOpts()),
329 1));
331 return false;
334 Plugin::IdenticalDefaultArgumentsResult Plugin::checkIdenticalDefaultArguments(
335 Expr const * argument1, Expr const * argument2)
337 if ((argument1 == nullptr) != (argument2 == nullptr)) {
338 return IdenticalDefaultArgumentsResult::No;
340 if (argument1 == nullptr) {
341 return IdenticalDefaultArgumentsResult::Yes;
343 if (argument1->isNullPointerConstant(compiler.getASTContext(), Expr::NPC_NeverValueDependent)
344 && argument2->isNullPointerConstant(compiler.getASTContext(), Expr::NPC_NeverValueDependent))
346 return IdenticalDefaultArgumentsResult::Yes;
348 APSInt x1, x2;
349 if (evaluate(argument1, x1) && evaluate(argument2, x2))
351 return x1 == x2
352 ? IdenticalDefaultArgumentsResult::Yes
353 : IdenticalDefaultArgumentsResult::No;
355 APFloat f1(0.0f), f2(0.0f);
356 if (argument1->EvaluateAsFloat(f1, compiler.getASTContext())
357 && argument2->EvaluateAsFloat(f2, compiler.getASTContext()))
359 return f1.bitwiseIsEqual(f2)
360 ? IdenticalDefaultArgumentsResult::Yes
361 : IdenticalDefaultArgumentsResult::No;
363 auto const desugared1 = argument1->IgnoreParenImpCasts();
364 auto const desugared2 = argument2->IgnoreParenImpCasts();
365 if (auto const lit1 = dyn_cast<clang::StringLiteral>(desugared1)) {
366 if (auto const lit2 = dyn_cast<clang::StringLiteral>(desugared2)) {
367 return lit1->getBytes() == lit2->getBytes()
368 ? IdenticalDefaultArgumentsResult::Yes
369 : IdenticalDefaultArgumentsResult::No;
372 // catch params with defaults like "= OUString()"
373 for (Expr const * e1 = desugared1, * e2 = desugared2;;) {
374 auto const ce1 = dyn_cast<CXXConstructExpr>(skipImplicit(e1));
375 auto const ce2 = dyn_cast<CXXConstructExpr>(skipImplicit(e2));
376 if (ce1 == nullptr || ce2 == nullptr) {
377 break;
379 if (ce1->getConstructor()->getCanonicalDecl() != ce2->getConstructor()->getCanonicalDecl())
381 return IdenticalDefaultArgumentsResult::No;
383 if (ce1->isElidable() && ce2->isElidable() && ce1->getNumArgs() == 1
384 && ce2->getNumArgs() == 1)
386 assert(ce1->getConstructor()->isCopyOrMoveConstructor());
387 e1 = ce1->getArg(0)->IgnoreImpCasts();
388 e2 = ce2->getArg(0)->IgnoreImpCasts();
389 continue;
391 if (ce1->getNumArgs() == 0 && ce2->getNumArgs() == 0) {
392 return IdenticalDefaultArgumentsResult::Yes;
394 break;
396 // If the EvaluateAsRValue derivatives above failed because the arguments use e.g. (constexpr)
397 // function template specializations that happen to not have been instantiated in this TU, try a
398 // structural comparison of the arguments:
399 if (structurallyIdentical(argument1, argument2)) {
400 return IdenticalDefaultArgumentsResult::Yes;
402 if (isDebugMode()) {
403 report(
404 DiagnosticsEngine::Fatal, "TODO: Unexpected 'IdenticalDefaultArgumentsResult::Maybe'",
405 argument1->getExprLoc())
406 << argument1->getSourceRange();
407 report(
408 DiagnosticsEngine::Note, "TODO: second argument is here", argument2->getExprLoc())
409 << argument2->getSourceRange();
410 argument1->dump();
411 argument2->dump();
413 return IdenticalDefaultArgumentsResult::Maybe;
416 RewritePlugin::RewritePlugin( const InstantiationData& data )
417 : Plugin( data )
418 , rewriter( data.rewriter )
422 bool RewritePlugin::insertText( SourceLocation Loc, StringRef Str, bool InsertAfter, bool indentNewLines )
424 assert( rewriter );
425 if (wouldRewriteWorkdir(Loc))
426 return false;
427 SourceRange Range(SourceRange(Loc, Loc.getLocWithOffset(Str.size())));
428 if( !handler.checkOverlap( Range ) )
430 report( DiagnosticsEngine::Warning, "double code removal, possible plugin error", Range.getBegin());
431 return false;
433 if( rewriter->InsertText( Loc, Str, InsertAfter, indentNewLines ))
434 return reportEditFailure( Loc );
435 handler.addSourceModification(Range);
436 return true;
439 bool RewritePlugin::insertTextAfter( SourceLocation Loc, StringRef Str )
441 assert( rewriter );
442 if (wouldRewriteWorkdir(Loc))
443 return false;
444 SourceRange Range(SourceRange(Loc, Loc.getLocWithOffset(Str.size())));
445 if( !handler.checkOverlap( Range ) )
447 report( DiagnosticsEngine::Warning, "double code removal, possible plugin error", Range.getBegin());
448 return false;
450 if( rewriter->InsertTextAfter( Loc, Str ))
451 return reportEditFailure( Loc );
452 handler.addSourceModification(Range);
453 return true;
456 bool RewritePlugin::insertTextAfterToken( SourceLocation Loc, StringRef Str )
458 assert( rewriter );
459 if (wouldRewriteWorkdir(Loc))
460 return false;
461 SourceRange Range(SourceRange(Loc, Loc.getLocWithOffset(Str.size())));
462 if( !handler.checkOverlap( Range ) )
464 report( DiagnosticsEngine::Warning, "double code removal, possible plugin error", Range.getBegin());
465 return false;
467 if( rewriter->InsertTextAfterToken( Loc, Str ))
468 return reportEditFailure( Loc );
469 handler.addSourceModification(Range);
470 return true;
473 bool RewritePlugin::insertTextBefore( SourceLocation Loc, StringRef Str )
475 assert( rewriter );
476 if (wouldRewriteWorkdir(Loc))
477 return false;
478 SourceRange Range(SourceRange(Loc, Loc.getLocWithOffset(Str.size())));
479 if( !handler.checkOverlap( Range ) )
481 report( DiagnosticsEngine::Warning, "double code removal, possible plugin error", Range.getBegin());
482 return false;
484 if( rewriter->InsertTextBefore( Loc, Str ))
485 return reportEditFailure( Loc );
486 handler.addSourceModification(Range);
487 return true;
490 bool RewritePlugin::removeText( SourceLocation Start, unsigned Length, RewriteOptions opts )
492 CharSourceRange range( SourceRange( Start, Start.getLocWithOffset( Length )), false );
493 return removeText( range, opts );
496 bool RewritePlugin::removeText( SourceRange range, RewriteOptions opts )
498 return removeText( CharSourceRange( range, true ), opts );
501 bool RewritePlugin::removeText( CharSourceRange range, RewriteOptions opts )
503 assert( rewriter );
504 if (wouldRewriteWorkdir(range.getBegin()))
505 return false;
506 if( rewriter->getRangeSize( range, opts ) == -1 )
507 return reportEditFailure( range.getBegin());
508 if( !handler.checkOverlap( range.getAsRange() ) )
510 report( DiagnosticsEngine::Warning, "double code removal, possible plugin error", range.getBegin());
511 return false;
513 if( opts.flags & RemoveWholeStatement || opts.flags & RemoveAllWhitespace )
515 if( !adjustRangeForOptions( &range, opts ))
516 return reportEditFailure( range.getBegin());
518 if( rewriter->RemoveText( range, opts ))
519 return reportEditFailure( range.getBegin());
520 handler.addSourceModification(range.getAsRange());
521 return true;
524 bool RewritePlugin::adjustRangeForOptions( CharSourceRange* range, RewriteOptions opts )
526 assert( rewriter );
527 SourceManager& SM = rewriter->getSourceMgr();
528 SourceLocation fileStartLoc = SM.getLocForStartOfFile( SM.getFileID( range->getBegin()));
529 if( fileStartLoc.isInvalid())
530 return false;
531 bool isInvalid = false;
532 const char* fileBuf = SM.getCharacterData( fileStartLoc, &isInvalid );
533 if( isInvalid )
534 return false;
535 const char* startBuf = SM.getCharacterData( range->getBegin(), &isInvalid );
536 if( isInvalid )
537 return false;
538 SourceLocation locationEnd = range->getEnd();
539 if( range->isTokenRange())
540 locationEnd = locationAfterToken( locationEnd );
541 const char* endBuf = SM.getCharacterData( locationEnd, &isInvalid );
542 if( isInvalid )
543 return false;
544 const char* startPos = startBuf;
545 --startPos;
546 while( startPos >= fileBuf && ( *startPos == ' ' || *startPos == '\t' ))
547 --startPos;
548 if( startPos >= fileBuf && *startPos == '\n' )
549 startPos = startBuf - 1; // do not remove indentation whitespace (RemoveLineIfEmpty can do that)
550 const char* endPos = endBuf;
551 while( *endPos == ' ' || *endPos == '\t' )
552 ++endPos;
553 if( opts.flags & RemoveWholeStatement )
555 if( *endPos == ';' )
556 ++endPos;
557 else
558 return false;
560 *range = CharSourceRange( SourceRange( range->getBegin().getLocWithOffset( startPos - startBuf + 1 ),
561 locationEnd.getLocWithOffset( endPos - endBuf )), false );
562 return true;
565 bool RewritePlugin::replaceText( SourceLocation Start, unsigned OrigLength, StringRef NewStr )
567 assert( rewriter );
568 if (wouldRewriteWorkdir(Start))
569 return false;
570 SourceRange Range(Start, Start.getLocWithOffset(std::max<size_t>(OrigLength, NewStr.size())));
571 if( OrigLength != 0 && !handler.checkOverlap( Range ) )
573 report( DiagnosticsEngine::Warning, "overlapping code replacement, possible plugin error", Start );
574 return false;
576 if( rewriter->ReplaceText( Start, OrigLength, NewStr ))
577 return reportEditFailure( Start );
578 handler.addSourceModification(Range);
579 return true;
582 bool RewritePlugin::replaceText( SourceRange range, StringRef NewStr )
584 assert( rewriter );
585 if (wouldRewriteWorkdir(range.getBegin()))
586 return false;
587 if( rewriter->getRangeSize( range ) == -1 )
588 return reportEditFailure( range.getBegin());
589 if( !handler.checkOverlap( range ) )
591 report( DiagnosticsEngine::Warning, "overlapping code replacement, possible plugin error", range.getBegin());
592 return false;
594 if( rewriter->ReplaceText( range, NewStr ))
595 return reportEditFailure( range.getBegin());
596 handler.addSourceModification(range);
597 return true;
600 bool RewritePlugin::replaceText( SourceRange range, SourceRange replacementRange )
602 assert( rewriter );
603 if (wouldRewriteWorkdir(range.getBegin()))
604 return false;
605 if( rewriter->getRangeSize( range ) == -1 )
606 return reportEditFailure( range.getBegin());
607 if( !handler.checkOverlap( range ) )
609 report( DiagnosticsEngine::Warning, "overlapping code replacement, possible plugin error", range.getBegin());
610 return false;
612 if( rewriter->ReplaceText( range, replacementRange ))
613 return reportEditFailure( range.getBegin());
614 handler.addSourceModification(range);
615 return true;
618 bool RewritePlugin::wouldRewriteWorkdir(SourceLocation loc)
620 if (loc.isInvalid() || loc.isMacroID()) {
621 return false;
623 return
624 compiler.getSourceManager().getFilename(
625 compiler.getSourceManager().getSpellingLoc(loc))
626 .startswith(WORKDIR "/");
629 bool RewritePlugin::reportEditFailure( SourceLocation loc )
631 report( DiagnosticsEngine::Warning, "cannot perform source modification (macro expansion involved?)", loc );
632 return false;
635 namespace {
637 template<typename Fn> bool checkPathname(
638 StringRef pathname, StringRef against, Fn check)
640 if (check(pathname, against)) {
641 return true;
643 #if defined _WIN32
644 for (std::size_t n = 0;;)
646 std::size_t n1 = pathname.find('\\', n);
647 std::size_t n2 = against.find('\\', n);
648 if (n1 <= n2) {
649 if (n1 >= against.size()) {
650 return check(pathname.substr(n), against.substr(n));
652 if ((against[n1] != '/' && against[n1] != '\\')
653 || pathname.substr(n, n1 - n) != against.substr(n, n1 - n))
655 break;
657 n = n1 + 1;
658 } else {
659 if (n2 >= pathname.size()) {
660 return check(pathname.substr(n), against.substr(n));
662 if (pathname[n2] != '/'
663 || pathname.substr(n, n2 - n) != against.substr(n, n2 - n))
665 break;
667 n = n2 + 1;
670 #endif
671 return false;
676 bool hasPathnamePrefix(StringRef pathname, StringRef prefix)
678 return checkPathname(
679 pathname, prefix,
680 [](StringRef p, StringRef a) { return p.startswith(a); });
683 bool isSamePathname(StringRef pathname, StringRef other)
685 return checkPathname(
686 pathname, other, [](StringRef p, StringRef a) { return p == a; });
689 } // namespace
691 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */