summaryrefslogtreecommitdiffstats
path: root/compilerplugins/clang/plugin.cxx
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--compilerplugins/clang/plugin.cxx867
1 files changed, 867 insertions, 0 deletions
diff --git a/compilerplugins/clang/plugin.cxx b/compilerplugins/clang/plugin.cxx
new file mode 100644
index 000000000..5248927e5
--- /dev/null
+++ b/compilerplugins/clang/plugin.cxx
@@ -0,0 +1,867 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * Based on LLVM/Clang.
+ *
+ * This file is distributed under the University of Illinois Open Source
+ * License. See LICENSE.TXT for details.
+ *
+ */
+
+#include "plugin.hxx"
+
+#include <cassert>
+#include <cstddef>
+#include <string>
+
+#include <clang/Basic/FileManager.h>
+#include <clang/Lex/Lexer.h>
+
+#include "config_clang.h"
+
+#include "compat.hxx"
+#include "pluginhandler.hxx"
+#include "check.hxx"
+
+#if CLANG_VERSION >= 110000
+#include "clang/AST/ParentMapContext.h"
+#endif
+
+/*
+Base classes for plugin actions.
+*/
+namespace loplugin
+{
+
+namespace {
+
+Expr const * skipImplicit(Expr const * expr) {
+ if (auto const e = dyn_cast<MaterializeTemporaryExpr>(expr)) {
+ expr = compat::getSubExpr(e)->IgnoreImpCasts();
+ }
+ if (auto const e = dyn_cast<CXXBindTemporaryExpr>(expr)) {
+ expr = e->getSubExpr();
+ }
+ return expr;
+}
+
+bool structurallyIdentical(Stmt const * stmt1, Stmt const * stmt2) {
+ if (stmt1->getStmtClass() != stmt2->getStmtClass()) {
+ return false;
+ }
+ switch (stmt1->getStmtClass()) {
+ case Stmt::CXXConstructExprClass:
+ if (cast<CXXConstructExpr>(stmt1)->getConstructor()->getCanonicalDecl()
+ != cast<CXXConstructExpr>(stmt2)->getConstructor()->getCanonicalDecl())
+ {
+ return false;
+ }
+ break;
+ case Stmt::DeclRefExprClass:
+ if (cast<DeclRefExpr>(stmt1)->getDecl()->getCanonicalDecl()
+ != cast<DeclRefExpr>(stmt2)->getDecl()->getCanonicalDecl())
+ {
+ return false;
+ }
+ break;
+ case Stmt::ImplicitCastExprClass:
+ {
+ auto const e1 = cast<ImplicitCastExpr>(stmt1);
+ auto const e2 = cast<ImplicitCastExpr>(stmt2);
+ if (e1->getCastKind() != e2->getCastKind()
+ || e1->getType().getCanonicalType() != e2->getType().getCanonicalType())
+ {
+ return false;
+ }
+ break;
+ }
+ case Stmt::MemberExprClass:
+ {
+ auto const e1 = cast<MemberExpr>(stmt1);
+ auto const e2 = cast<MemberExpr>(stmt2);
+ if (e1->isArrow() != e2->isArrow()
+ || e1->getType().getCanonicalType() != e2->getType().getCanonicalType())
+ {
+ return false;
+ }
+ break;
+ }
+ case Stmt::CXXMemberCallExprClass:
+ case Stmt::CXXOperatorCallExprClass:
+ if (cast<Expr>(stmt1)->getType().getCanonicalType()
+ != cast<Expr>(stmt2)->getType().getCanonicalType())
+ {
+ return false;
+ }
+ break;
+ case Stmt::MaterializeTemporaryExprClass:
+ case Stmt::CXXBindTemporaryExprClass:
+ case Stmt::ParenExprClass:
+ break;
+ case Stmt::CXXNullPtrLiteralExprClass:
+ return true;
+ default:
+ // Conservatively assume non-identical for expressions that don't happen for us in practice
+ // when compiling the LO code base (and for which the above set of supported classes would
+ // need to be extended):
+ return false;
+ }
+ auto i1 = stmt1->child_begin();
+ auto e1 = stmt1->child_end();
+ auto i2 = stmt2->child_begin();
+ auto e2 = stmt2->child_end();
+ for (; i1 != e1; ++i1, ++i2) {
+ if (i2 == e2 || !structurallyIdentical(*i1, *i2)) {
+ return false;
+ }
+ }
+ return i2 == e2;
+}
+
+}
+
+Plugin::Plugin( const InstantiationData& data )
+ : compiler( data.compiler ), handler( data.handler ), name( data.name )
+{
+}
+
+DiagnosticBuilder Plugin::report( DiagnosticsEngine::Level level, StringRef message, SourceLocation loc ) const
+{
+ return handler.report( level, name, message, compiler, loc );
+}
+
+void normalizeDotDotInFilePath( std::string & s )
+{
+ for (std::string::size_type i = 0;;)
+ {
+ i = s.find("/.", i);
+ if (i == std::string::npos) {
+ break;
+ }
+ if (i + 2 == s.length() || s[i + 2] == '/') {
+ s.erase(i, 2); // [AAA]/.[/CCC] -> [AAA][/CCC]
+ } else if (s[i + 2] == '.'
+ && (i + 3 == s.length() || s[i + 3] == '/'))
+ {
+ if (i == 0) { // /..[/CCC] -> /..[/CCC]
+ break;
+ }
+ auto j = s.rfind('/', i - 1);
+ if (j == std::string::npos)
+ {
+ // BBB/..[/CCC] -> BBB/..[/CCC] (instead of BBB/../CCC ->
+ // CCC, to avoid wrong ../../CCC -> CCC; relative paths
+ // shouldn't happen anyway, and even if they did, wouldn't
+ // match against WORKDIR anyway, as WORKDIR should be
+ // absolute):
+ break;
+ }
+ s.erase(j, i + 3 - j); // AAA/BBB/..[/CCC] -> AAA[/CCC]
+ i = j;
+ } else {
+ i += 2;
+ }
+ }
+}
+
+void Plugin::registerPlugin( Plugin* (*create)( const InstantiationData& ), const char* optionName,
+ bool isPPCallback, bool isSharedPlugin, bool byDefault )
+{
+ PluginHandler::registerPlugin( create, optionName, isPPCallback, isSharedPlugin, byDefault );
+}
+
+bool Plugin::evaluate(const Expr* expr, APSInt& x)
+{
+ if (compat::EvaluateAsInt(expr, x, compiler.getASTContext()))
+ {
+ return true;
+ }
+ if (isa<CXXNullPtrLiteralExpr>(expr)) {
+ x = 0;
+ return true;
+ }
+ return false;
+}
+
+compat::DynTypedNodeList Plugin::getParents(Decl const & decl)
+{
+#if CLANG_VERSION >= 110000
+ if (!parentMapContext_) {
+ parentMapContext_.reset(new ParentMapContext(compiler.getASTContext()));
+ parentMapContext_->setTraversalKind(TK_AsIs);
+ }
+ return parentMapContext_->getParents(decl);
+#else
+ return compiler.getASTContext().getParents(decl);
+#endif
+}
+
+compat::DynTypedNodeList Plugin::getParents(Stmt const & stmt)
+{
+#if CLANG_VERSION >= 110000
+ if (!parentMapContext_) {
+ parentMapContext_.reset(new ParentMapContext(compiler.getASTContext()));
+ parentMapContext_->setTraversalKind(TK_AsIs);
+ }
+ return parentMapContext_->getParents(stmt);
+#else
+ return compiler.getASTContext().getParents(stmt);
+#endif
+}
+
+const Stmt* Plugin::getParentStmt( const Stmt* stmt )
+{
+ auto parentsRange = getParents(*stmt);
+ if ( parentsRange.begin() == parentsRange.end())
+ return nullptr;
+ return parentsRange.begin()->get<Stmt>();
+}
+
+Stmt* Plugin::getParentStmt( Stmt* stmt )
+{
+ auto parentsRange = getParents(*stmt);
+ if ( parentsRange.begin() == parentsRange.end())
+ return nullptr;
+ return const_cast<Stmt*>(parentsRange.begin()->get<Stmt>());
+}
+
+const Decl* Plugin::getFunctionDeclContext(const Stmt* stmt)
+{
+ auto const parents = getParents(*stmt);
+ auto it = parents.begin();
+
+ if (it == parents.end())
+ return nullptr;
+
+ const Decl *decl = it->get<Decl>();
+ if (decl)
+ {
+ if (isa<VarDecl>(decl))
+ return dyn_cast<FunctionDecl>(decl->getDeclContext());
+ return decl;
+ }
+
+ stmt = it->get<Stmt>();
+ if (stmt)
+ return getFunctionDeclContext(stmt);
+
+ return nullptr;
+}
+
+const FunctionDecl* Plugin::getParentFunctionDecl( const Stmt* stmt )
+{
+ const Decl *decl = getFunctionDeclContext(stmt);
+ if (decl)
+ return static_cast<const FunctionDecl*>(decl->getNonClosureContext());
+
+ return nullptr;
+}
+
+StringRef Plugin::getFilenameOfLocation(SourceLocation spellingLocation) const
+{
+ // prevent crashes when running the global-analysis plugins
+ if (!spellingLocation.isValid())
+ return "";
+
+ static enum { NOINIT, STDIN, GOOD } s_Mode(NOINIT);
+ if (s_Mode == GOOD)
+ {
+ return compiler.getSourceManager().getFilename(spellingLocation);
+ }
+ else if (s_Mode == STDIN
+ || !compiler.getSourceManager().isInMainFile(spellingLocation))
+ {
+ const char* bufferName = compiler.getSourceManager().getPresumedLoc(spellingLocation).getFilename();
+ return bufferName;
+ }
+ else
+ {
+ auto const fn(compiler.getSourceManager().getFilename(spellingLocation));
+ if (!fn.data()) // wtf? happens in sot/source/sdstor/stg.cxx
+ {
+ return fn;
+ }
+#if !defined _WIN32
+ assert(fn.startswith("/") || fn == "<stdin>");
+#endif
+ s_Mode = fn == "<stdin>" ? STDIN : GOOD;
+ return getFilenameOfLocation(spellingLocation);
+ }
+}
+
+bool Plugin::isInUnoIncludeFile(SourceLocation spellingLocation) const
+{
+ StringRef name{ getFilenameOfLocation(spellingLocation) };
+ return compiler.getSourceManager().isInMainFile(spellingLocation)
+ ? (isSamePathname(name, SRCDIR "/cppu/source/cppu/compat.cxx")
+ || isSamePathname(name, SRCDIR "/cppuhelper/source/compat.cxx")
+ || isSamePathname(name, SRCDIR "/sal/osl/all/compat.cxx"))
+ : (hasPathnamePrefix(name, SRCDIR "/include/com/")
+ || hasPathnamePrefix(name, SRCDIR "/include/cppu/")
+ || hasPathnamePrefix(name, SRCDIR "/include/cppuhelper/")
+ || hasPathnamePrefix(name, SRCDIR "/include/osl/")
+ || hasPathnamePrefix(name, SRCDIR "/include/rtl/")
+ || hasPathnamePrefix(name, SRCDIR "/include/sal/")
+ || hasPathnamePrefix(name, SRCDIR "/include/salhelper/")
+ || hasPathnamePrefix(name, SRCDIR "/include/systools/")
+ || hasPathnamePrefix(name, SRCDIR "/include/typelib/")
+ || hasPathnamePrefix(name, SRCDIR "/include/uno/"));
+}
+
+bool Plugin::isInUnoIncludeFile(const FunctionDecl* functionDecl) const
+{
+ return isInUnoIncludeFile(compiler.getSourceManager().getSpellingLoc(
+ functionDecl->getCanonicalDecl()->getNameInfo().getLoc()));
+}
+
+SourceLocation Plugin::locationAfterToken( SourceLocation location )
+{
+ return Lexer::getLocForEndOfToken( location, 0, compiler.getSourceManager(), compiler.getLangOpts());
+}
+
+bool Plugin::isUnitTestMode()
+{
+ return PluginHandler::isUnitTestMode();
+}
+
+bool Plugin::containsPreprocessingConditionalInclusion(SourceRange range)
+{
+ // Preprocessing directives (other than _Pragma, which is not relevant here) cannot appear in
+ // macro expansions, so it is safe to just consider the range of expansion locations:
+ auto const begin = compiler.getSourceManager().getExpansionLoc(
+ range.getBegin());
+ auto const end = compiler.getSourceManager().getExpansionLoc(
+ range.getEnd());
+ assert(begin.isFileID() && end.isFileID());
+ if (!(begin == end
+ || compiler.getSourceManager().isBeforeInTranslationUnit(
+ begin, end)))
+ {
+ if (isDebugMode()) {
+ report(
+ DiagnosticsEngine::Fatal,
+ ("unexpected broken range for Plugin::containsPreprocessingConditionalInclusion,"
+ " case 1"),
+ range.getBegin())
+ << range;
+ }
+ // Conservatively assume "yes" if lexing fails:
+ return true;
+ }
+ auto hash = false;
+ for (auto loc = begin;;) {
+ Token tok;
+ if (Lexer::getRawToken(
+ loc, tok, compiler.getSourceManager(),
+ compiler.getLangOpts(), true))
+ {
+ if (isDebugMode()) {
+ report(
+ DiagnosticsEngine::Fatal,
+ ("unexpected broken range for"
+ " Plugin::containsPreprocessingConditionalInclusion, case 2"),
+ loc)
+ << range;
+ }
+ // Conservatively assume "yes" if lexing fails:
+ return true;
+ }
+ if (hash && tok.is(tok::raw_identifier)) {
+ auto const id = tok.getRawIdentifier();
+ if (id == "if" || id == "ifdef" || id == "ifndef"
+ || id == "elif" || id == "else" || id == "endif")
+ {
+ return true;
+ }
+ }
+ if (loc == end) {
+ break;
+ }
+ hash = tok.is(tok::hash) && tok.isAtStartOfLine();
+ loc = loc.getLocWithOffset(
+ std::max<unsigned>(
+ Lexer::MeasureTokenLength(
+ loc, compiler.getSourceManager(),
+ compiler.getLangOpts()),
+ 1));
+ }
+ return false;
+}
+
+Plugin::IdenticalDefaultArgumentsResult Plugin::checkIdenticalDefaultArguments(
+ Expr const * argument1, Expr const * argument2)
+{
+ if ((argument1 == nullptr) != (argument2 == nullptr)) {
+ return IdenticalDefaultArgumentsResult::No;
+ }
+ if (argument1 == nullptr) {
+ return IdenticalDefaultArgumentsResult::Yes;
+ }
+ if (argument1->isNullPointerConstant(compiler.getASTContext(), Expr::NPC_NeverValueDependent)
+ && argument2->isNullPointerConstant(compiler.getASTContext(), Expr::NPC_NeverValueDependent))
+ {
+ return IdenticalDefaultArgumentsResult::Yes;
+ }
+ APSInt x1, x2;
+ if (evaluate(argument1, x1) && evaluate(argument2, x2))
+ {
+ return x1 == x2
+ ? IdenticalDefaultArgumentsResult::Yes
+ : IdenticalDefaultArgumentsResult::No;
+ }
+ APFloat f1(0.0f), f2(0.0f);
+ if (argument1->EvaluateAsFloat(f1, compiler.getASTContext())
+ && argument2->EvaluateAsFloat(f2, compiler.getASTContext()))
+ {
+ return f1.bitwiseIsEqual(f2)
+ ? IdenticalDefaultArgumentsResult::Yes
+ : IdenticalDefaultArgumentsResult::No;
+ }
+ auto const desugared1 = argument1->IgnoreParenImpCasts();
+ auto const desugared2 = argument2->IgnoreParenImpCasts();
+ if (auto const lit1 = dyn_cast<clang::StringLiteral>(desugared1)) {
+ if (auto const lit2 = dyn_cast<clang::StringLiteral>(desugared2)) {
+ return lit1->getBytes() == lit2->getBytes()
+ ? IdenticalDefaultArgumentsResult::Yes
+ : IdenticalDefaultArgumentsResult::No;
+ }
+ }
+ // catch params with defaults like "= OUString()"
+ for (Expr const * e1 = desugared1, * e2 = desugared2;;) {
+ auto const ce1 = dyn_cast<CXXConstructExpr>(skipImplicit(e1));
+ auto const ce2 = dyn_cast<CXXConstructExpr>(skipImplicit(e2));
+ if (ce1 == nullptr || ce2 == nullptr) {
+ break;
+ }
+ if (ce1->getConstructor()->getCanonicalDecl() != ce2->getConstructor()->getCanonicalDecl())
+ {
+ return IdenticalDefaultArgumentsResult::No;
+ }
+ if (ce1->isElidable() && ce2->isElidable() && ce1->getNumArgs() == 1
+ && ce2->getNumArgs() == 1)
+ {
+ assert(ce1->getConstructor()->isCopyOrMoveConstructor());
+ e1 = ce1->getArg(0)->IgnoreImpCasts();
+ e2 = ce2->getArg(0)->IgnoreImpCasts();
+ continue;
+ }
+ if (ce1->getNumArgs() == 0 && ce2->getNumArgs() == 0) {
+ return IdenticalDefaultArgumentsResult::Yes;
+ }
+ break;
+ }
+ // If the EvaluateAsRValue derivatives above failed because the arguments use e.g. (constexpr)
+ // function template specializations that happen to not have been instantiated in this TU, try a
+ // structural comparison of the arguments:
+ if (structurallyIdentical(argument1, argument2)) {
+ return IdenticalDefaultArgumentsResult::Yes;
+ }
+ if (isDebugMode()) {
+ report(
+ DiagnosticsEngine::Fatal, "TODO: Unexpected 'IdenticalDefaultArgumentsResult::Maybe'",
+ argument1->getExprLoc())
+ << argument1->getSourceRange();
+ report(
+ DiagnosticsEngine::Note, "TODO: second argument is here", argument2->getExprLoc())
+ << argument2->getSourceRange();
+ argument1->dump();
+ argument2->dump();
+ }
+ return IdenticalDefaultArgumentsResult::Maybe;
+}
+
+RewritePlugin::RewritePlugin( const InstantiationData& data )
+ : Plugin( data )
+ , rewriter( data.rewriter )
+{
+}
+
+bool RewritePlugin::insertText( SourceLocation Loc, StringRef Str, bool InsertAfter, bool indentNewLines )
+{
+ assert( rewriter );
+ if (wouldRewriteWorkdir(Loc))
+ return false;
+ SourceRange Range(SourceRange(Loc, Loc.getLocWithOffset(Str.size())));
+ if( !handler.checkOverlap( Range ) )
+ {
+ report( DiagnosticsEngine::Warning, "double code removal, possible plugin error", Range.getBegin());
+ return false;
+ }
+ if( rewriter->InsertText( Loc, Str, InsertAfter, indentNewLines ))
+ return reportEditFailure( Loc );
+ handler.addSourceModification(Range);
+ return true;
+}
+
+bool RewritePlugin::insertTextAfter( SourceLocation Loc, StringRef Str )
+{
+ assert( rewriter );
+ if (wouldRewriteWorkdir(Loc))
+ return false;
+ SourceRange Range(SourceRange(Loc, Loc.getLocWithOffset(Str.size())));
+ if( !handler.checkOverlap( Range ) )
+ {
+ report( DiagnosticsEngine::Warning, "double code removal, possible plugin error", Range.getBegin());
+ return false;
+ }
+ if( rewriter->InsertTextAfter( Loc, Str ))
+ return reportEditFailure( Loc );
+ handler.addSourceModification(Range);
+ return true;
+}
+
+bool RewritePlugin::insertTextAfterToken( SourceLocation Loc, StringRef Str )
+{
+ assert( rewriter );
+ if (wouldRewriteWorkdir(Loc))
+ return false;
+ SourceRange Range(SourceRange(Loc, Loc.getLocWithOffset(Str.size())));
+ if( !handler.checkOverlap( Range ) )
+ {
+ report( DiagnosticsEngine::Warning, "double code removal, possible plugin error", Range.getBegin());
+ return false;
+ }
+ if( rewriter->InsertTextAfterToken( Loc, Str ))
+ return reportEditFailure( Loc );
+ handler.addSourceModification(Range);
+ return true;
+}
+
+bool RewritePlugin::insertTextBefore( SourceLocation Loc, StringRef Str )
+{
+ assert( rewriter );
+ if (wouldRewriteWorkdir(Loc))
+ return false;
+ SourceRange Range(SourceRange(Loc, Loc.getLocWithOffset(Str.size())));
+ if( !handler.checkOverlap( Range ) )
+ {
+ report( DiagnosticsEngine::Warning, "double code removal, possible plugin error", Range.getBegin());
+ return false;
+ }
+ if( rewriter->InsertTextBefore( Loc, Str ))
+ return reportEditFailure( Loc );
+ handler.addSourceModification(Range);
+ return true;
+}
+
+bool RewritePlugin::removeText( SourceLocation Start, unsigned Length, RewriteOptions opts )
+{
+ CharSourceRange range( SourceRange( Start, Start.getLocWithOffset( Length )), false );
+ return removeText( range, opts );
+}
+
+bool RewritePlugin::removeText( SourceRange range, RewriteOptions opts )
+{
+ return removeText( CharSourceRange( range, true ), opts );
+}
+
+bool RewritePlugin::removeText( CharSourceRange range, RewriteOptions opts )
+{
+ assert( rewriter );
+ if (wouldRewriteWorkdir(range.getBegin()))
+ return false;
+ if( rewriter->getRangeSize( range, opts ) == -1 )
+ return reportEditFailure( range.getBegin());
+ if( !handler.checkOverlap( range.getAsRange() ) )
+ {
+ report( DiagnosticsEngine::Warning, "double code removal, possible plugin error", range.getBegin());
+ return false;
+ }
+ if( opts.flags & RemoveWholeStatement || opts.flags & RemoveAllWhitespace )
+ {
+ if( !adjustRangeForOptions( &range, opts ))
+ return reportEditFailure( range.getBegin());
+ }
+ if( rewriter->RemoveText( range, opts ))
+ return reportEditFailure( range.getBegin());
+ handler.addSourceModification(range.getAsRange());
+ return true;
+}
+
+bool RewritePlugin::adjustRangeForOptions( CharSourceRange* range, RewriteOptions opts )
+{
+ assert( rewriter );
+ SourceManager& SM = rewriter->getSourceMgr();
+ SourceLocation fileStartLoc = SM.getLocForStartOfFile( SM.getFileID( range->getBegin()));
+ if( fileStartLoc.isInvalid())
+ return false;
+ bool isInvalid = false;
+ const char* fileBuf = SM.getCharacterData( fileStartLoc, &isInvalid );
+ if( isInvalid )
+ return false;
+ const char* startBuf = SM.getCharacterData( range->getBegin(), &isInvalid );
+ if( isInvalid )
+ return false;
+ SourceLocation locationEnd = range->getEnd();
+ if( range->isTokenRange())
+ locationEnd = locationAfterToken( locationEnd );
+ const char* endBuf = SM.getCharacterData( locationEnd, &isInvalid );
+ if( isInvalid )
+ return false;
+ const char* startPos = startBuf;
+ --startPos;
+ while( startPos >= fileBuf && ( *startPos == ' ' || *startPos == '\t' ))
+ --startPos;
+ if( startPos >= fileBuf && *startPos == '\n' )
+ startPos = startBuf - 1; // do not remove indentation whitespace (RemoveLineIfEmpty can do that)
+ const char* endPos = endBuf;
+ while( *endPos == ' ' || *endPos == '\t' )
+ ++endPos;
+ if( opts.flags & RemoveWholeStatement )
+ {
+ if( *endPos == ';' )
+ ++endPos;
+ else
+ return false;
+ }
+ *range = CharSourceRange( SourceRange( range->getBegin().getLocWithOffset( startPos - startBuf + 1 ),
+ locationEnd.getLocWithOffset( endPos - endBuf )), false );
+ return true;
+}
+
+bool RewritePlugin::replaceText( SourceLocation Start, unsigned OrigLength, StringRef NewStr )
+{
+ assert( rewriter );
+ if (wouldRewriteWorkdir(Start))
+ return false;
+ SourceRange Range(Start, Start.getLocWithOffset(std::max<size_t>(OrigLength, NewStr.size())));
+ if( OrigLength != 0 && !handler.checkOverlap( Range ) )
+ {
+ report( DiagnosticsEngine::Warning, "overlapping code replacement, possible plugin error", Start );
+ return false;
+ }
+ if( rewriter->ReplaceText( Start, OrigLength, NewStr ))
+ return reportEditFailure( Start );
+ handler.addSourceModification(Range);
+ return true;
+}
+
+bool RewritePlugin::replaceText( SourceRange range, StringRef NewStr )
+{
+ assert( rewriter );
+ if (wouldRewriteWorkdir(range.getBegin()))
+ return false;
+ if( rewriter->getRangeSize( range ) == -1 )
+ return reportEditFailure( range.getBegin());
+ if( !handler.checkOverlap( range ) )
+ {
+ report( DiagnosticsEngine::Warning, "overlapping code replacement, possible plugin error", range.getBegin());
+ return false;
+ }
+ if( rewriter->ReplaceText( range, NewStr ))
+ return reportEditFailure( range.getBegin());
+ handler.addSourceModification(range);
+ return true;
+}
+
+bool RewritePlugin::replaceText( SourceRange range, SourceRange replacementRange )
+{
+ assert( rewriter );
+ if (wouldRewriteWorkdir(range.getBegin()))
+ return false;
+ if( rewriter->getRangeSize( range ) == -1 )
+ return reportEditFailure( range.getBegin());
+ if( !handler.checkOverlap( range ) )
+ {
+ report( DiagnosticsEngine::Warning, "overlapping code replacement, possible plugin error", range.getBegin());
+ return false;
+ }
+ if( rewriter->ReplaceText( range, replacementRange ))
+ return reportEditFailure( range.getBegin());
+ handler.addSourceModification(range);
+ return true;
+}
+
+bool RewritePlugin::wouldRewriteWorkdir(SourceLocation loc)
+{
+ if (loc.isInvalid() || loc.isMacroID()) {
+ return false;
+ }
+ return
+ getFilenameOfLocation(compiler.getSourceManager().getSpellingLoc(loc))
+ .startswith(WORKDIR "/");
+}
+
+bool RewritePlugin::reportEditFailure( SourceLocation loc )
+{
+ report( DiagnosticsEngine::Warning, "cannot perform source modification (macro expansion involved?)", loc );
+ return false;
+}
+
+namespace {
+
+template<typename Fn> bool checkPathname(
+ StringRef pathname, StringRef against, Fn check)
+{
+ if (check(pathname, against)) {
+ return true;
+ }
+#if defined _WIN32
+ for (std::size_t n = 0;;)
+ {
+ std::size_t n1 = pathname.find('\\', n);
+ std::size_t n2 = against.find('\\', n);
+ if (n1 <= n2) {
+ if (n1 >= against.size()) {
+ return check(pathname.substr(n), against.substr(n));
+ }
+ if ((against[n1] != '/' && against[n1] != '\\')
+ || pathname.substr(n, n1 - n) != against.substr(n, n1 - n))
+ {
+ break;
+ }
+ n = n1 + 1;
+ } else {
+ if (n2 >= pathname.size()) {
+ return check(pathname.substr(n), against.substr(n));
+ }
+ if (pathname[n2] != '/'
+ || pathname.substr(n, n2 - n) != against.substr(n, n2 - n))
+ {
+ break;
+ }
+ n = n2 + 1;
+ }
+ }
+#endif
+ return false;
+}
+
+}
+
+bool hasPathnamePrefix(StringRef pathname, StringRef prefix)
+{
+ return checkPathname(
+ pathname, prefix,
+ [](StringRef p, StringRef a) { return p.startswith(a); });
+}
+
+bool isSamePathname(StringRef pathname, StringRef other)
+{
+ return checkPathname(
+ pathname, other, [](StringRef p, StringRef a) { return p == a; });
+}
+
+bool hasCLanguageLinkageType(FunctionDecl const * decl) {
+ assert(decl != nullptr);
+ if (decl->isExternC()) {
+ return true;
+ }
+ if (decl->isInExternCContext()) {
+ return true;
+ }
+ return false;
+}
+
+static const CXXRecordDecl* stripTypeSugar(QualType qt)
+{
+ const clang::Type* t = qt.getTypePtr();
+ while (auto elaboratedType = dyn_cast<ElaboratedType>(t))
+ t = elaboratedType->desugar().getTypePtr();
+ auto recordType = dyn_cast<RecordType>(t);
+ if (!recordType)
+ return nullptr;
+ return dyn_cast_or_null<CXXRecordDecl>(recordType->getDecl());
+}
+
+int derivedFromCount(const CXXRecordDecl* subclassRecordDecl, const CXXRecordDecl* baseclassRecordDecl)
+{
+ if (!subclassRecordDecl || !baseclassRecordDecl)
+ return 0;
+ int derivedCount = 0;
+ if (subclassRecordDecl == baseclassRecordDecl)
+ derivedCount++;
+ if (!subclassRecordDecl->hasDefinition())
+ return derivedCount;
+ for (auto it = subclassRecordDecl->bases_begin(); it != subclassRecordDecl->bases_end(); ++it)
+ {
+ derivedCount += derivedFromCount(stripTypeSugar(it->getType()), baseclassRecordDecl);
+ // short-circuit, we only care about 0,1,2
+ if (derivedCount > 1)
+ return derivedCount;
+ }
+ for (auto it = subclassRecordDecl->vbases_begin(); it != subclassRecordDecl->vbases_end(); ++it)
+ {
+ derivedCount += derivedFromCount(stripTypeSugar(it->getType()), baseclassRecordDecl);
+ // short-circuit, we only care about 0,1,2
+ if (derivedCount > 1)
+ return derivedCount;
+ }
+ return derivedCount;
+}
+
+int derivedFromCount(QualType subclassQt, QualType baseclassQt)
+{
+ auto baseclassRecordDecl = stripTypeSugar(baseclassQt);
+ if (!baseclassRecordDecl)
+ return 0;
+ auto subclassRecordDecl = stripTypeSugar(subclassQt);
+ if (!subclassRecordDecl)
+ return 0;
+
+ return derivedFromCount(subclassRecordDecl, baseclassRecordDecl);
+}
+
+// It looks like Clang wrongly implements DR 4
+// (<http://www.open-std.org/jtc1/sc22/wg21/docs/cwg_defects.html#4>) and treats
+// a variable declared in an 'extern "..." {...}'-style linkage-specification as
+// if it contained the 'extern' specifier:
+bool hasExternalLinkage(VarDecl const * decl) {
+ if (decl->getLinkageAndVisibility().getLinkage() != ExternalLinkage) {
+ return false;
+ }
+ for (auto ctx = decl->getLexicalDeclContext();
+ ctx->getDeclKind() != Decl::TranslationUnit;
+ ctx = ctx->getLexicalParent())
+ {
+ if (auto ls = dyn_cast<LinkageSpecDecl>(ctx)) {
+ if (!ls->hasBraces()) {
+ return true;
+ }
+ if (auto prev = decl->getPreviousDecl()) {
+ return hasExternalLinkage(prev);
+ }
+ return !decl->isInAnonymousNamespace();
+ }
+ }
+ return true;
+}
+
+bool isSmartPointerType(const Expr* e)
+{
+ // First check the object type as written, in case the get member function is
+ // declared at a base class of std::unique_ptr or std::shared_ptr:
+ auto const t = e->IgnoreImpCasts()->getType();
+ auto const tc1 = loplugin::TypeCheck(t);
+ if (tc1.ClassOrStruct("unique_ptr").StdNamespace()
+ || tc1.ClassOrStruct("shared_ptr").StdNamespace())
+ return true;
+
+ // Then check the object type coerced to the type of the get member function, in
+ // case the type-as-written is derived from one of these types (tools::SvRef is
+ // final, but the rest are not; but note that this will fail when the type-as-
+ // written is derived from std::unique_ptr or std::shared_ptr for which the get
+ // member function is declared at a base class):
+ auto const tc2 = loplugin::TypeCheck(e->getType());
+ if (tc2.ClassOrStruct("unique_ptr").StdNamespace()
+ || tc2.ClassOrStruct("shared_ptr").StdNamespace()
+ || tc2.Class("Reference").Namespace("uno").Namespace("star")
+ .Namespace("sun").Namespace("com").GlobalNamespace()
+ || tc2.Class("Reference").Namespace("rtl").GlobalNamespace()
+ || tc2.Class("SvRef").Namespace("tools").GlobalNamespace()
+ || tc2.Class("WeakReference").Namespace("tools").GlobalNamespace()
+ || tc2.Class("ScopedReadAccess").Namespace("Bitmap").GlobalNamespace()
+ || tc2.Class("ScopedVclPtrInstance").GlobalNamespace()
+ || tc2.Class("VclPtr").GlobalNamespace()
+ || tc2.Class("ScopedVclPtr").GlobalNamespace())
+ {
+ return true;
+ }
+ return false;
+}
+
+
+} // namespace
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */