Stephan Bergmann af16aa6256 Improve loplugin:stringview
Issue the "instead of O[U]String, pass [u16]string_view" diagnostic also for
operator call arguments.  (The "rather than copy, pass subView()" diagnostic is
already part of handleSubExprThatCouldBeView, so no need to repeat it explicitly
for operator call arguments.)

(And many call sites don't even require an explicit [u16]string_view, esp. with
the recent ad48b2b02f83eed41fb1eb8d16de7e804156fcf1 "Optimized OString operator
+= overloads".  Just some test code in sal/qa/ that explicitly tests the
O[U]String functionality had to be excluded.)

Change-Id: I8d55ba5a7fa16a563f5ffe43d245125c88c793bc
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/115589
Tested-by: Jenkins
Reviewed-by: Stephan Bergmann <sbergman@redhat.com>
2021-05-14 13:11:50 +02:00

329 lines
11 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/.
*/
#ifndef LO_CLANG_SHARED_PLUGINS
#include <cassert>
#include <string>
#include <iostream>
#include <unordered_map>
#include <unordered_set>
#include "plugin.hxx"
#include "check.hxx"
#include "clang/AST/CXXInheritance.h"
#include "clang/AST/StmtVisitor.h"
/**
Look for places where we are making a substring copy of an OUString, and then passing it to a
function that takes a u16string_view, in which case it is more efficient to pass a view
of the OUString, rather than making a copy.
TODO currently does not check if there is some other visible overload of the callee, that can take
a string_view.
TODO handle OUStringBuffer/OStringBuffer similarly
*/
namespace
{
class StringView : public loplugin::FilteringPlugin<StringView>
{
public:
explicit StringView(loplugin::InstantiationData const& data)
: FilteringPlugin(data)
{
}
bool preRun() override
{
auto const fn = handler.getMainFileName();
return !(
loplugin::isSamePathname(fn, SRCDIR "/sal/qa/OStringBuffer/rtl_OStringBuffer.cxx")
|| loplugin::isSamePathname(fn, SRCDIR "/sal/qa/rtl/strings/test_ostring_concat.cxx")
|| loplugin::isSamePathname(fn, SRCDIR "/sal/qa/rtl/strings/test_oustring_concat.cxx"));
}
virtual void run() override
{
if (!preRun())
return;
TraverseDecl(compiler.getASTContext().getTranslationUnitDecl());
}
bool VisitFunctionDecl(FunctionDecl const*);
bool VisitCXXOperatorCallExpr(CXXOperatorCallExpr const*);
bool VisitImplicitCastExpr(ImplicitCastExpr const*);
bool VisitCXXMemberCallExpr(CXXMemberCallExpr const*);
bool VisitCXXConstructExpr(CXXConstructExpr const*);
private:
void handleSubExprThatCouldBeView(Expr const* expr);
void handleCXXConstructExpr(CXXConstructExpr const* expr);
void handleCXXMemberCallExpr(CXXMemberCallExpr const* expr);
};
bool StringView::VisitCXXOperatorCallExpr(CXXOperatorCallExpr const* cxxOperatorCallExpr)
{
if (ignoreLocation(cxxOperatorCallExpr))
return true;
auto op = cxxOperatorCallExpr->getOperator();
if (op == OO_Plus && cxxOperatorCallExpr->getNumArgs() == 2)
{
handleSubExprThatCouldBeView(compat::IgnoreImplicit(cxxOperatorCallExpr->getArg(0)));
handleSubExprThatCouldBeView(compat::IgnoreImplicit(cxxOperatorCallExpr->getArg(1)));
}
if (compat::isComparisonOp(cxxOperatorCallExpr))
{
handleSubExprThatCouldBeView(compat::IgnoreImplicit(cxxOperatorCallExpr->getArg(0)));
handleSubExprThatCouldBeView(compat::IgnoreImplicit(cxxOperatorCallExpr->getArg(1)));
}
else if (op == OO_PlusEqual)
handleSubExprThatCouldBeView(compat::IgnoreImplicit(cxxOperatorCallExpr->getArg(1)));
else if (op == OO_Subscript)
handleSubExprThatCouldBeView(compat::IgnoreImplicit(cxxOperatorCallExpr->getArg(0)));
else if (op == OO_Equal)
{
if (loplugin::TypeCheck(cxxOperatorCallExpr->getType())
.Class("OUStringBuffer")
.Namespace("rtl")
.GlobalNamespace()
|| loplugin::TypeCheck(cxxOperatorCallExpr->getType())
.Class("OStringBuffer")
.Namespace("rtl")
.GlobalNamespace())
{
handleSubExprThatCouldBeView(compat::IgnoreImplicit(cxxOperatorCallExpr->getArg(1)));
}
}
return true;
}
bool StringView::VisitFunctionDecl(FunctionDecl const* functionDecl)
{
if (ignoreLocation(functionDecl))
return true;
// debugging
// if (functionDecl->getIdentifier() && functionDecl->getName() == "f1")
// functionDecl->dump();
return true;
}
bool StringView::VisitImplicitCastExpr(ImplicitCastExpr const* expr)
{
if (ignoreLocation(expr))
{
return true;
}
if (!loplugin::TypeCheck(expr->getType()).ClassOrStruct("basic_string_view").StdNamespace())
{
return true;
}
handleSubExprThatCouldBeView(expr->getSubExprAsWritten());
return true;
}
void StringView::handleSubExprThatCouldBeView(Expr const* subExpr)
{
auto const e = subExpr->IgnoreParens();
auto const tc = loplugin::TypeCheck(e->getType());
if (!(tc.Class("OString").Namespace("rtl").GlobalNamespace()
|| tc.Class("OUString").Namespace("rtl").GlobalNamespace()))
{
return;
}
if (auto const e1 = dyn_cast<CXXConstructExpr>(e))
{
handleCXXConstructExpr(e1);
}
else if (auto const e2 = dyn_cast<CXXFunctionalCastExpr>(e))
{
auto e3 = e2->getSubExpr();
if (auto const e4 = dyn_cast<CXXBindTemporaryExpr>(e3))
{
e3 = e4->getSubExpr();
}
if (auto const e4 = dyn_cast<CXXConstructExpr>(e3))
{
handleCXXConstructExpr(e4);
}
}
else if (auto const e3 = dyn_cast<CXXMemberCallExpr>(e))
{
handleCXXMemberCallExpr(e3);
}
}
void StringView::handleCXXConstructExpr(CXXConstructExpr const* expr)
{
QualType argType;
bool charArg = false;
auto const d = expr->getConstructor();
switch (d->getNumParams())
{
case 0:
break;
case 1:
{
auto const t = d->getParamDecl(0)->getType();
if (t->isAnyCharacterType())
{
argType = expr->getArg(0)->IgnoreImplicit()->getType();
charArg = true;
break;
}
loplugin::TypeCheck tc(t);
if (tc.LvalueReference()
.Const()
.Class("OStringLiteral")
.Namespace("rtl")
.GlobalNamespace()
|| tc.LvalueReference()
.Const()
.Class("OUStringLiteral")
.Namespace("rtl")
.GlobalNamespace()
|| tc.RvalueReference().Struct("OStringNumber").Namespace("rtl").GlobalNamespace()
|| tc.RvalueReference().Struct("OUStringNumber").Namespace("rtl").GlobalNamespace()
|| tc.ClassOrStruct("basic_string_view").StdNamespace())
{
argType = expr->getArg(0)->IgnoreImplicit()->getType();
break;
}
return;
}
case 2:
{
auto const t0 = d->getParamDecl(0)->getType();
if (t0->isPointerType() && t0->getPointeeType()->isAnyCharacterType())
{
auto const t = d->getParamDecl(1)->getType();
if (t->isIntegralType(compiler.getASTContext())
&& !(t->isBooleanType() || t->isAnyCharacterType()))
{
auto const arg = expr->getArg(1);
if (!arg->isValueDependent())
{
if (auto const val
= compat::getIntegerConstantExpr(arg, compiler.getASTContext()))
{
if (val->getExtValue() == 1)
{
charArg = true;
}
}
}
argType = expr->getArg(0)->IgnoreImplicit()->getType();
break;
}
}
if (loplugin::TypeCheck(d->getParamDecl(1)->getType())
.Struct("Dummy")
.Namespace("libreoffice_internal")
.Namespace("rtl")
.GlobalNamespace())
{
argType = expr->getArg(0)->IgnoreImplicit()->getType();
break;
}
return;
}
default:
return;
}
report(DiagnosticsEngine::Warning,
"instead of an %0%select{| constructed from a %2}1, pass a"
" '%select{std::string_view|std::u16string_view}3'"
"%select{| (or an '%select{rtl::OStringChar|rtl::OUStringChar}3')}4",
expr->getExprLoc())
<< expr->getType() << (argType.isNull() ? 0 : 1) << argType
<< (loplugin::TypeCheck(expr->getType()).Class("OString").Namespace("rtl").GlobalNamespace()
? 0
: 1)
<< charArg << expr->getSourceRange();
}
void StringView::handleCXXMemberCallExpr(CXXMemberCallExpr const* expr)
{
auto const dc = loplugin::DeclCheck(expr->getMethodDecl()).Function("copy");
if (!dc)
{
return;
}
if (!(dc.Class("OString").Namespace("rtl").GlobalNamespace()
|| dc.Class("OUString").Namespace("rtl").GlobalNamespace()))
{
return;
}
report(DiagnosticsEngine::Warning, "rather than copy, pass with a view using subView()",
expr->getExprLoc())
<< expr->getSourceRange();
}
/** check for calls to O[U]StringBuffer::append that could be passed as a
std::u16string_view */
bool StringView::VisitCXXMemberCallExpr(CXXMemberCallExpr const* expr)
{
if (ignoreLocation(expr))
{
return true;
}
if (!loplugin::TypeCheck(expr->getType())
.Class("OUStringBuffer")
.Namespace("rtl")
.GlobalNamespace()
&& !loplugin::TypeCheck(expr->getType())
.Class("OStringBuffer")
.Namespace("rtl")
.GlobalNamespace())
{
return true;
}
auto const dc = loplugin::DeclCheck(expr->getMethodDecl());
if (dc.Function("append") || dc.Function("indexOf") || dc.Function("lastIndexOf"))
{
handleSubExprThatCouldBeView(compat::IgnoreImplicit(expr->getArg(0)));
}
else if (dc.Function("insert"))
{
handleSubExprThatCouldBeView(compat::IgnoreImplicit(expr->getArg(1)));
}
return true;
}
/** check for calls to O[U]StringBuffer constructor that could be passed as a
std::u16string_view */
bool StringView::VisitCXXConstructExpr(CXXConstructExpr const* expr)
{
if (ignoreLocation(expr))
{
return true;
}
if (!loplugin::TypeCheck(expr->getType())
.Class("OUStringBuffer")
.Namespace("rtl")
.GlobalNamespace()
&& !loplugin::TypeCheck(expr->getType())
.Class("OStringBuffer")
.Namespace("rtl")
.GlobalNamespace())
{
return true;
}
if (expr->getNumArgs() > 0)
handleSubExprThatCouldBeView(compat::IgnoreImplicit(expr->getArg(0)));
return true;
}
loplugin::Plugin::Registration<StringView> stringview("stringview");
}
#endif // LO_CLANG_SHARED_PLUGINS
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */