94a34d6bdb
and fix some loplugin:simplifypointertobool warnings Change-Id: I3644c390a3339a4cb8d66d6d034a0f043de9320c Reviewed-on: https://gerrit.libreoffice.org/c/core/+/95572 Tested-by: Jenkins Reviewed-by: Noel Grandin <noel.grandin@collabora.co.uk>
870 lines
29 KiB
C++
870 lines
29 KiB
C++
/* -*- 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 whether the object type as written is, or is derived from, std::unique_ptr or
|
|
// std::shared_ptr, in case the get member function is declared at a base class of that std
|
|
// type:
|
|
if (loplugin::isDerivedFrom(
|
|
e->IgnoreImpCasts()->getType()->getAsCXXRecordDecl(),
|
|
[](Decl const * decl) {
|
|
auto const dc = loplugin::DeclCheck(decl);
|
|
return dc.ClassOrStruct("unique_ptr").StdNamespace()
|
|
|| dc.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):
|
|
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()
|
|
|| tc2.Class("intrusive_ptr").Namespace("boost").GlobalNamespace())
|
|
{
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
|
|
} // namespace
|
|
|
|
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
|