office-gobmx/compilerplugins/clang/plugin.cxx
Stephan Bergmann e63e769bd3 Introduce a better mechanism to suppress false loplugin warnings
...by annotating occurrences of false warnings with [-loplugin:<name>] comments
in source files and letting individual plugins opt-in to watch out for such
suppression annotations, rather than maintaining lists of excluded source files
in the individual plugins.  (See the new loplugin::Plugin::suppressWarningsAt.)

Instead of making all calls to loplugin::Plugin::report check for suppression
annotations, the intent is that this check will only be added opt-in to those
places in the plugins that are prone to emitting false warnings.  In general it
is better to have plugins that don't produce false warnings in the first place,
or at least let those warnings be addressed with trivial and harmless source
code modifications, avoiding the need for any suppression mechanism.

As a proof of concept, I have removed the exclude list from
loplugin:redundantfcast and instead annotated the relevant source code.  (And
thereby found that three of the six originally excluded files didn't need to be
excluded any more at all?)

For now, this mechanism looks for comments (both //... and /*...*/, even
documentation-style /**...*/) that overlap the current and/or the preceding
line, because at least for code controlled by clang-format it is often easier to
move comments to a line of their own, preceding the commented code.  Looking
also at the current line (and not only at the preceding one) opens the door for
erroneous over-eager annotation, where an annotation that was meant to address a
false warning on the current line would also silence a potentially true warning
on the following line.  This probably doesn't cause much trouble in practice,
but is up for potential change.

Change-Id: I91ce7a0e5248886a60b471b1a153867f16bb5cea
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/133365
Tested-by: Jenkins
Reviewed-by: Stephan Bergmann <sbergman@redhat.com>
2022-04-25 18:21:24 +02:00

1081 lines
38 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/AST/ParentMapContext.h>
#include <clang/Basic/FileManager.h>
#include <clang/Lex/Lexer.h>
#include "config_clang.h"
#include "compat.hxx"
#include "pluginhandler.hxx"
#include "check.hxx"
/*
Base classes for plugin actions.
*/
namespace loplugin
{
namespace {
Expr const * skipImplicit(Expr const * expr) {
if (auto const e = dyn_cast<MaterializeTemporaryExpr>(expr)) {
expr = e->getSubExpr()->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 );
}
bool Plugin::suppressWarningAt(SourceLocation location) const {
auto const start = compiler.getSourceManager().getSpellingLoc(location);
auto const startInfo = compiler.getSourceManager().getDecomposedLoc(start);
auto invalid = false;
auto const buf = compiler.getSourceManager().getBufferData(startInfo.first, &invalid);
if (invalid) {
if (isDebugMode()) {
report(DiagnosticsEngine::Fatal, "failed to getBufferData", start);
}
return false;
}
auto const label = std::string("[-loplugin:").append(name).append("]");
// Look back to the beginning of the previous line:
auto loc = start;
auto locInfo = startInfo;
auto cur = loc;
enum class State { Normal, Slash, Comment };
auto state = State::Normal;
auto newlines = 0;
for (auto prev = cur;;) {
auto prevInfo = compiler.getSourceManager().getDecomposedLoc(prev);
if (prev == compiler.getSourceManager().getLocForStartOfFile(prevInfo.first)) {
if (state == State::Comment && isDebugMode()) {
report(
DiagnosticsEngine::Fatal,
"beginning of file while looking for beginning of comment", prev);
}
break;
}
Token tok;
if (Lexer::getRawToken(
Lexer::GetBeginningOfToken(
prev.getLocWithOffset(-1), compiler.getSourceManager(), compiler.getLangOpts()),
tok, compiler.getSourceManager(), compiler.getLangOpts(), true))
{
if (isDebugMode()) {
report(
DiagnosticsEngine::Fatal, "failed to getRawToken",
prev.getLocWithOffset(-1));
}
break;
}
if (tok.getLocation() == cur) {
// Keep walking back, character by character, through whitespace preceding the current
// token, which Clang treats as nominally belonging to that token (so the above
// Lexer::getRawToken/Lexer::GetBeginningOfToken will have produced just the same tok
// again):
prev = prev.getLocWithOffset(-1);
continue;
}
cur = tok.getLocation();
prev = cur;
if (state == State::Comment) {
// Lexer::GetBeginningOfToken (at least towards Clang 15, still) only re-scans from the
// start of the current line, so if we saw the end of a multi-line /*...*/ comment, we
// saw that as individual '/' and '*' faux-tokens, at which point we must (hopefully?)
// actually be at the end of such a multi-line comment, so we keep walking back to the
// first '/*' we encounter (TODO: which still need not be the real start of the comment,
// if the comment contains embedded '/*', but we could determine that only if we
// re-scanned from the start of the file):
if (!tok.is(tok::comment)) {
continue;
}
SmallVector<char, 256> tmp;
bool invalid = false;
auto const spell = Lexer::getSpelling(
prev, tmp, compiler.getSourceManager(), compiler.getLangOpts(), &invalid);
if (invalid) {
if (isDebugMode()) {
report(DiagnosticsEngine::Fatal, "failed to getSpelling", prev);
}
} else if (!spell.startswith("/*")) {
continue;
}
}
prevInfo = compiler.getSourceManager().getDecomposedLoc(prev);
auto const end = prev.getLocWithOffset(tok.getLength());
auto const endInfo = compiler.getSourceManager().getDecomposedLoc(end);
assert(prevInfo.first == endInfo .first);
assert(prevInfo.second <= endInfo.second);
assert(endInfo.first == locInfo.first);
// Whitespace between tokens is found at the end of prev, from end to loc (unless this is a
// multi-line comment, in which case the whitespace has already been inspected as the
// whitespace following the comment's final '/' faux-token):
StringRef ws;
if (state != State::Comment) {
assert(endInfo.second <= locInfo.second);
ws = buf.substr(endInfo.second, locInfo.second - endInfo.second);
}
for (std::size_t i = 0;;) {
auto const j = ws.find('\n', i);
if (j == StringRef::npos) {
break;
}
++newlines;
if (newlines == 2) {
break;
}
i = j + 1;
}
if (newlines == 2) {
break;
}
auto str = buf.substr(prevInfo.second, endInfo.second - prevInfo.second);
if (tok.is(tok::comment) && str.contains(label)) {
return true;
}
for (std::size_t i = 0;;) {
auto const j = str.find('\n', i);
if (j == StringRef::npos) {
break;
}
++newlines;
if (newlines == 2) {
break;
}
i = j + 1;
}
if (newlines == 2) {
break;
}
loc = prev;
locInfo = prevInfo;
switch (state) {
case State::Normal:
if (tok.is(tok::slash)) {
state = State::Slash;
}
break;
case State::Slash:
state = tok.is(tok::star) && ws.empty() ? State::Comment : State::Normal;
//TODO: check for "ws is only folding whitespace" rather than for `ws.empty()` (but
// then, we must not count newlines in that whitespace twice, first as part of the
// whitespace following the comment's semi-final '*' faux-token and then as part of
// the comment token's content)
break;
case State::Comment:
state = State::Normal;
}
}
// Look forward to the end of the current line:
loc = start;
locInfo = startInfo;
for (;;) {
Token tok;
if (Lexer::getRawToken(loc, tok, compiler.getSourceManager(), compiler.getLangOpts(), true))
{
if (isDebugMode()) {
report(DiagnosticsEngine::Fatal, "failed to getRawToken", loc);
}
break;
}
// Whitespace between tokens is found at the beginning, from loc to beg:
auto const beg = tok.getLocation();
auto const begInfo = compiler.getSourceManager().getDecomposedLoc(beg);
assert(begInfo.first == locInfo.first);
assert(begInfo.second >= locInfo.second);
if (buf.substr(locInfo.second, begInfo.second - locInfo.second).contains('\n')) {
break;
}
auto const next = beg.getLocWithOffset(tok.getLength());
auto const nextInfo = compiler.getSourceManager().getDecomposedLoc(next);
assert(nextInfo.first == begInfo.first);
assert(nextInfo.second >= begInfo.second);
auto const str = buf.substr(begInfo.second, nextInfo.second - begInfo.second);
if (tok.is(tok::comment) && str.contains(label)) {
return true;
}
if (tok.is(tok::eof) || str.contains('\n')) {
break;
}
loc = next;
locInfo = nextInfo;
}
return false;
}
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;
}
const Stmt* Plugin::getParentStmt( const Stmt* stmt )
{
auto parentsRange = compiler.getASTContext().getParents(*stmt);
if ( parentsRange.begin() == parentsRange.end())
return nullptr;
return parentsRange.begin()->get<Stmt>();
}
Stmt* Plugin::getParentStmt( Stmt* stmt )
{
auto parentsRange = compiler.getASTContext().getParents(*stmt);
if ( parentsRange.begin() == parentsRange.end())
return nullptr;
return const_cast<Stmt*>(parentsRange.begin()->get<Stmt>());
}
const Decl* getFunctionDeclContext(ASTContext& context, const Stmt* stmt)
{
auto const parents = context.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(context, stmt);
return nullptr;
}
const FunctionDecl* Plugin::getParentFunctionDecl( const Stmt* stmt )
{
const Decl *decl = getFunctionDeclContext(compiler.getASTContext(), 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
{
char const*const pCXX = getenv("CXX");
if (pCXX && strstr(pCXX, "sccache"))
{ // heuristic; sccache passes file with -frewrite-directives by name
s_Mode = STDIN;
return getFilenameOfLocation(spellingLocation);
}
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/typelib/")
|| hasPathnamePrefix(name, SRCDIR "/include/uno/")
|| hasPathnamePrefix(name, SDKDIR "/include/"));
}
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 isSameUnoIncludePathname(StringRef fullPathname, StringRef includePathname)
{
llvm::SmallVector<char, 256> buf;
if (isSamePathname(fullPathname, (SRCDIR "/include/" + includePathname).toStringRef(buf))) {
return true;
}
buf.clear();
return isSamePathname(fullPathname, (SDKDIR "/include/" + includePathname).toStringRef(buf));
}
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();
do
{
if (auto elaboratedType = dyn_cast<ElaboratedType>(t))
t = elaboratedType->desugar().getTypePtr();
else if (auto tsType = dyn_cast<TemplateSpecializationType>(t))
t = tsType->desugar().getTypePtr();
else if (auto sttpType = dyn_cast<SubstTemplateTypeParmType>(t))
t = sttpType->desugar().getTypePtr();
else if (auto tdType = dyn_cast<TypedefType>(t))
t = tdType->desugar().getTypePtr();
else
break;
} while(true);
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(QualType qt)
{
// 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(
qt->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(qt);
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;
}
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: */