office-gobmx/compilerplugins/clang/simplifypointertobool.cxx
Stephan Bergmann d2c5490210 -Werror,-Wdeprecated-declarations
> compilerplugins/clang/casttovoid.cxx:452:18: error: 'endswith' is deprecated: Use ends_with instead [-Werror,-Wdeprecated-declarations]
>                 .endswith(".h"));
>                  ^~~~~~~~
>                  ends_with
> ~/llvm/inst/include/llvm/ADT/StringRef.h:276:19: note: 'endswith' has been explicitly marked deprecated here
>     [[nodiscard]] LLVM_DEPRECATED(
>                   ^

etc. after
<5ac12951b4>
"[ADT] Deprecate StringRef::{starts,ends}with (#75491)" on Clang 18 trunk, where
<1b97645e56>
"[ADT] Introduce StringRef::{starts,ends}_width{,_insensitive}" had been added
towards Clang 16

Change-Id: Icb3e43b7d6be6f877815285913d846f766eddebf
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/160919
Tested-by: Jenkins
Reviewed-by: Stephan Bergmann <stephan.bergmann@allotropia.de>
2023-12-18 17:40:26 +01:00

451 lines
15 KiB
C++

/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*
* This file is part of the LibreOffice project.
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
#include <algorithm>
#include <cassert>
#include <deque>
#include <string>
#include <iostream>
#include <fstream>
#include <set>
#include <clang/AST/CXXInheritance.h>
#include "plugin.hxx"
#include "check.hxx"
#include "compat.hxx"
/**
Simplify boolean expressions involving smart pointers e.g.
if (x.get())
can be
if (x)
*/
//TODO: Make this a shared plugin for Clang 12 (and possibly even for older Clang) again.
namespace
{
class SimplifyPointerToBool : public loplugin::FilteringRewritePlugin<SimplifyPointerToBool>
{
public:
explicit SimplifyPointerToBool(loplugin::InstantiationData const& data)
: FilteringRewritePlugin(data)
{
}
virtual void run() override
{
if (preRun())
TraverseDecl(compiler.getASTContext().getTranslationUnitDecl());
}
bool VisitImplicitCastExpr(ImplicitCastExpr const*);
bool VisitBinaryOperator(BinaryOperator const*);
bool PreTraverseUnaryOperator(UnaryOperator* expr)
{
if (expr->getOpcode() == UO_LNot)
{
contextuallyConvertedExprs_.push_back(expr->getSubExpr()->IgnoreParenImpCasts());
}
return true;
}
bool PostTraverseUnaryOperator(UnaryOperator* expr, bool)
{
if (expr->getOpcode() == UO_LNot)
{
assert(!contextuallyConvertedExprs_.empty());
contextuallyConvertedExprs_.pop_back();
}
return true;
}
bool TraverseUnaryOperator(UnaryOperator* expr)
{
auto res = PreTraverseUnaryOperator(expr);
assert(res);
res = FilteringRewritePlugin::TraverseUnaryOperator(expr);
PostTraverseUnaryOperator(expr, res);
return res;
}
bool PreTraverseBinaryOperator(BinaryOperator* expr)
{
auto const op = expr->getOpcode();
if (op == BO_LAnd || op == BO_LOr)
{
contextuallyConvertedExprs_.push_back(expr->getLHS()->IgnoreParenImpCasts());
contextuallyConvertedExprs_.push_back(expr->getRHS()->IgnoreParenImpCasts());
}
return true;
}
bool PostTraverseBinaryOperator(BinaryOperator* expr, bool)
{
auto const op = expr->getOpcode();
if (op == BO_LAnd || op == BO_LOr)
{
assert(contextuallyConvertedExprs_.size() >= 2);
contextuallyConvertedExprs_.pop_back();
contextuallyConvertedExprs_.pop_back();
}
return true;
}
bool TraverseBinaryOperator(BinaryOperator* expr)
{
auto res = PreTraverseBinaryOperator(expr);
assert(res);
res = FilteringRewritePlugin::TraverseBinaryOperator(expr);
PostTraverseBinaryOperator(expr, res);
return res;
}
bool PreTraverseConditionalOperator(ConditionalOperator* expr)
{
contextuallyConvertedExprs_.push_back(expr->getCond()->IgnoreParenImpCasts());
return true;
}
bool PostTraverseConditionalOperator(ConditionalOperator*, bool)
{
assert(!contextuallyConvertedExprs_.empty());
contextuallyConvertedExprs_.pop_back();
return true;
}
bool TraverseConditionalOperator(ConditionalOperator* expr)
{
auto res = PreTraverseConditionalOperator(expr);
assert(res);
res = FilteringRewritePlugin::TraverseConditionalOperator(expr);
PostTraverseConditionalOperator(expr, res);
return res;
}
bool PreTraverseIfStmt(IfStmt* stmt)
{
if (auto const cond = stmt->getCond())
{
contextuallyConvertedExprs_.push_back(cond->IgnoreParenImpCasts());
}
return true;
}
bool PostTraverseIfStmt(IfStmt* stmt, bool)
{
if (stmt->getCond() != nullptr)
{
assert(!contextuallyConvertedExprs_.empty());
contextuallyConvertedExprs_.pop_back();
}
return true;
}
bool TraverseIfStmt(IfStmt* stmt)
{
auto res = PreTraverseIfStmt(stmt);
assert(res);
res = FilteringRewritePlugin::TraverseIfStmt(stmt);
PostTraverseIfStmt(stmt, res);
return res;
}
bool PreTraverseWhileStmt(WhileStmt* stmt)
{
contextuallyConvertedExprs_.push_back(stmt->getCond()->IgnoreParenImpCasts());
return true;
}
bool PostTraverseWhileStmt(WhileStmt*, bool)
{
assert(!contextuallyConvertedExprs_.empty());
contextuallyConvertedExprs_.pop_back();
return true;
}
bool TraverseWhileStmt(WhileStmt* stmt)
{
auto res = PreTraverseWhileStmt(stmt);
assert(res);
res = FilteringRewritePlugin::TraverseWhileStmt(stmt);
PostTraverseWhileStmt(stmt, res);
return res;
}
bool PreTraverseDoStmt(DoStmt* stmt)
{
contextuallyConvertedExprs_.push_back(stmt->getCond()->IgnoreParenImpCasts());
return true;
}
bool PostTraverseDoStmt(DoStmt*, bool)
{
assert(!contextuallyConvertedExprs_.empty());
contextuallyConvertedExprs_.pop_back();
return true;
}
bool TraverseDoStmt(DoStmt* stmt)
{
auto res = PreTraverseDoStmt(stmt);
assert(res);
res = FilteringRewritePlugin::TraverseDoStmt(stmt);
PostTraverseDoStmt(stmt, res);
return res;
}
bool PreTraverseForStmt(ForStmt* stmt)
{
auto const e = stmt->getCond();
if (e != nullptr)
{
contextuallyConvertedExprs_.push_back(e->IgnoreParenImpCasts());
}
return true;
}
bool PostTraverseForStmt(ForStmt* stmt, bool)
{
if (stmt->getCond() != nullptr)
{
assert(!contextuallyConvertedExprs_.empty());
contextuallyConvertedExprs_.pop_back();
}
return true;
}
bool TraverseForStmt(ForStmt* stmt)
{
auto res = PreTraverseForStmt(stmt);
assert(res);
res = FilteringRewritePlugin::TraverseForStmt(stmt);
PostTraverseForStmt(stmt, res);
return res;
}
private:
bool isContextuallyConverted(Expr const* expr) const
{
return std::find(contextuallyConvertedExprs_.begin(), contextuallyConvertedExprs_.end(),
expr)
!= contextuallyConvertedExprs_.end();
}
// Get the source range starting at the "."or "->" (plus any preceding non-comment white space):
SourceRange getCallSourceRange(CXXMemberCallExpr const* expr) const
{
if (expr->getImplicitObjectArgument() == nullptr)
{
//TODO: Arguably, such a call of a `get` member function from within some member
// function (so that syntactically no caller is mentioned) should already be handled
// differently when reporting it (just "drop the get()" does not make sense), instead of
// being filtered here:
return {};
}
// CXXMemberCallExpr::getExprLoc happens to return the location following the "." or "->":
auto start = compiler.getSourceManager().getSpellingLoc(expr->getExprLoc());
if (!start.isValid())
{
return {};
}
for (;;)
{
start = Lexer::GetBeginningOfToken(start.getLocWithOffset(-1),
compiler.getSourceManager(), compiler.getLangOpts());
auto const s = StringRef(compiler.getSourceManager().getCharacterData(start),
Lexer::MeasureTokenLength(start, compiler.getSourceManager(),
compiler.getLangOpts()));
if (s.empty() || compat::starts_with(s, "\\\n"))
{
continue;
}
if (s != "." && s != "->")
{
return {};
}
break;
}
for (;;)
{
auto start1 = Lexer::GetBeginningOfToken(
start.getLocWithOffset(-1), compiler.getSourceManager(), compiler.getLangOpts());
auto const s = StringRef(compiler.getSourceManager().getCharacterData(start1),
Lexer::MeasureTokenLength(start1, compiler.getSourceManager(),
compiler.getLangOpts()));
if (!(s.empty() || compat::starts_with(s, "\\\n")))
{
break;
}
start = start1;
}
return SourceRange(start, compiler.getSourceManager().getSpellingLoc(expr->getEndLoc()));
}
//TODO: There are some more places where an expression is contextually converted to bool, but
// those are probably not relevant for our needs here.
std::deque<Expr const*> contextuallyConvertedExprs_;
};
bool SimplifyPointerToBool::VisitImplicitCastExpr(ImplicitCastExpr const* castExpr)
{
if (ignoreLocation(castExpr))
return true;
if (castExpr->getCastKind() != CK_PointerToBoolean)
return true;
auto memberCallExpr
= dyn_cast<CXXMemberCallExpr>(castExpr->getSubExpr()->IgnoreParenImpCasts());
if (!memberCallExpr)
return true;
auto methodDecl = memberCallExpr->getMethodDecl();
if (!methodDecl || !methodDecl->getIdentifier() || methodDecl->getName() != "get")
return true;
// castExpr->dump();
// methodDecl->getParent()->getTypeForDecl()->dump();
if (!loplugin::isSmartPointerType(memberCallExpr->getImplicitObjectArgument()))
return true;
// if (isa<CXXOperatorCallExpr>(callExpr))
// return true;
// const FunctionDecl* functionDecl;
// if (isa<CXXMemberCallExpr>(callExpr))
// {
// functionDecl = dyn_cast<CXXMemberCallExpr>(callExpr)->getMethodDecl();
// }
// else
// {
// functionDecl = callExpr->getDirectCallee();
// }
// if (!functionDecl)
// return true;
//
// unsigned len = std::min(callExpr->getNumArgs(), functionDecl->getNumParams());
// for (unsigned i = 0; i < len; ++i)
// {
// auto param = functionDecl->getParamDecl(i);
// auto paramTC = loplugin::TypeCheck(param->getType());
// if (!paramTC.AnyBoolean())
// continue;
// auto arg = callExpr->getArg(i)->IgnoreImpCasts();
// auto argTC = loplugin::TypeCheck(arg->getType());
// if (argTC.AnyBoolean())
// continue;
// // sal_Bool is sometimes disguised
// if (isa<SubstTemplateTypeParmType>(arg->getType()))
// if (arg->getType()->getUnqualifiedDesugaredType()->isSpecificBuiltinType(
// clang::BuiltinType::UChar))
// continue;
// if (arg->getType()->isDependentType())
// continue;
// if (arg->getType()->isIntegerType())
// {
// auto ret = getCallValue(arg);
// if (ret.hasValue() && (ret.getValue() == 1 || ret.getValue() == 0))
// continue;
// // something like: priv->m_nLOKFeatures & LOK_FEATURE_DOCUMENT_PASSWORD
// if (isa<BinaryOperator>(arg->IgnoreParenImpCasts()))
// continue;
// // something like: pbEmbolden ? FcTrue : FcFalse
// if (isa<ConditionalOperator>(arg->IgnoreParenImpCasts()))
// continue;
// }
if (isContextuallyConverted(memberCallExpr))
{
if (rewriter)
{
auto const range = getCallSourceRange(memberCallExpr);
if (range.isValid() && removeText(range))
{
return true;
}
}
report(DiagnosticsEngine::Warning, "simplify, drop the get()", memberCallExpr->getExprLoc())
<< memberCallExpr->getSourceRange();
}
else if (isa<ParenExpr>(castExpr->getSubExpr()))
{
if (rewriter)
{
auto const loc
= compiler.getSourceManager().getSpellingLoc(memberCallExpr->getBeginLoc());
auto const range = getCallSourceRange(memberCallExpr);
if (loc.isValid() && range.isValid() && insertText(loc, "bool") && removeText(range))
{
//TODO: atomically only change both or neither
return true;
}
}
report(DiagnosticsEngine::Warning,
"simplify, drop the get() and turn the surrounding parentheses into a functional "
"cast to bool",
memberCallExpr->getExprLoc())
<< memberCallExpr->getSourceRange();
report(DiagnosticsEngine::Note, "surrounding parentheses here",
castExpr->getSubExpr()->getExprLoc())
<< castExpr->getSubExpr()->getSourceRange();
}
else
{
if (rewriter)
{
auto const loc
= compiler.getSourceManager().getSpellingLoc(memberCallExpr->getBeginLoc());
auto const range = getCallSourceRange(memberCallExpr);
if (loc.isValid() && range.isValid() && insertText(loc, "bool(")
&& replaceText(range, ")"))
{
//TODO: atomically only change both or neither
return true;
}
}
report(DiagnosticsEngine::Warning,
"simplify, drop the get() and wrap the expression in a functional cast to bool",
memberCallExpr->getExprLoc())
<< memberCallExpr->getSourceRange();
}
// report(DiagnosticsEngine::Note, "method here", param->getLocation())
// << param->getSourceRange();
return true;
}
bool SimplifyPointerToBool::VisitBinaryOperator(BinaryOperator const* binOp)
{
if (ignoreLocation(binOp))
return true;
auto opCode = binOp->getOpcode();
if (opCode != BO_EQ && opCode != BO_NE)
return true;
const Expr* possibleMemberCall = nullptr;
if (isa<CXXNullPtrLiteralExpr>(binOp->getLHS()->IgnoreParenImpCasts()))
possibleMemberCall = binOp->getRHS();
else if (isa<CXXNullPtrLiteralExpr>(binOp->getRHS()->IgnoreParenImpCasts()))
possibleMemberCall = binOp->getLHS();
else
return true;
auto memberCallExpr = dyn_cast<CXXMemberCallExpr>(possibleMemberCall);
if (!memberCallExpr)
return true;
auto methodDecl = memberCallExpr->getMethodDecl();
if (!methodDecl || !methodDecl->getIdentifier() || methodDecl->getName() != "get")
return true;
if (!loplugin::isSmartPointerType(memberCallExpr->getImplicitObjectArgument()))
return true;
report(DiagnosticsEngine::Warning,
std::string("simplify, convert to ") + (opCode == BO_EQ ? "'!x'" : "'x'"),
binOp->getExprLoc())
<< binOp->getSourceRange();
return true;
}
loplugin::Plugin::Registration<SimplifyPointerToBool> simplifypointertobool("simplifypointertobool",
true);
} // namespace
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */