65075a76ff
since it might generate false + Change-Id: Icde883e0a2d60e70dd4ce9bbf823b54847cbb180 Reviewed-on: https://gerrit.libreoffice.org/c/core/+/133203 Tested-by: Noel Grandin <noel.grandin@collabora.co.uk> Reviewed-by: Noel Grandin <noel.grandin@collabora.co.uk>
407 lines
11 KiB
C++
407 lines
11 KiB
C++
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */
|
|
/*
|
|
* 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 <cassert>
|
|
#include <set>
|
|
#include <vector>
|
|
|
|
#include "config_clang.h"
|
|
|
|
#include "check.hxx"
|
|
#include "functionaddress.hxx"
|
|
#include "plugin.hxx"
|
|
|
|
// Find various of type rtl::O[U]String that can be converted to std::[u16]string_view instead.
|
|
|
|
// This can generate false positives e.g.
|
|
// OUString sSave( aToken );
|
|
// aToken.append("foo");
|
|
// aToken = sSave;
|
|
// where the data that is backing the view is modified and then the view is used to assign to
|
|
// the same source data.
|
|
|
|
namespace
|
|
{
|
|
enum class StringType
|
|
{
|
|
None,
|
|
RtlOstring,
|
|
RtlOustring
|
|
};
|
|
|
|
StringType relevantStringType(QualType type)
|
|
{
|
|
loplugin::TypeCheck const c(type);
|
|
if (c.Class("OString").Namespace("rtl"))
|
|
{
|
|
return StringType::RtlOstring;
|
|
}
|
|
else if (c.Class("OUString").Namespace("rtl"))
|
|
{
|
|
return StringType::RtlOustring;
|
|
}
|
|
else
|
|
{
|
|
return StringType::None;
|
|
}
|
|
}
|
|
|
|
bool relevantVarDecl(VarDecl const* decl)
|
|
{
|
|
auto const t1 = decl->getType();
|
|
if (relevantStringType(t1.getNonReferenceType()) == StringType::None)
|
|
{
|
|
return false;
|
|
}
|
|
if (isa<ParmVarDecl>(decl))
|
|
{
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
DeclRefExpr const* relevantDeclRefExpr(Expr const* expr)
|
|
{
|
|
//TODO: Look through BO_Comma and AbstractConditionalOperator
|
|
auto const e = dyn_cast<DeclRefExpr>(expr->IgnoreParenImpCasts());
|
|
if (e == nullptr)
|
|
{
|
|
return nullptr;
|
|
}
|
|
auto const d = dyn_cast<VarDecl>(e->getDecl());
|
|
if (d == nullptr)
|
|
{
|
|
return nullptr;
|
|
}
|
|
if (!relevantVarDecl(d))
|
|
{
|
|
return nullptr;
|
|
}
|
|
return e;
|
|
}
|
|
|
|
bool isStringView(QualType qt)
|
|
{
|
|
return bool(loplugin::TypeCheck(qt).ClassOrStruct("basic_string_view").StdNamespace());
|
|
}
|
|
|
|
DeclRefExpr const* relevantImplicitCastExpr(ImplicitCastExpr const* expr)
|
|
{
|
|
if (!isStringView(expr->getType()))
|
|
{
|
|
return nullptr;
|
|
}
|
|
return relevantDeclRefExpr(expr->getSubExprAsWritten());
|
|
}
|
|
|
|
DeclRefExpr const* relevantCStyleCastExpr(CStyleCastExpr const* expr)
|
|
{
|
|
if (expr->getCastKind() != CK_ToVoid)
|
|
{
|
|
return nullptr;
|
|
}
|
|
return relevantDeclRefExpr(expr->getSubExprAsWritten());
|
|
}
|
|
|
|
DeclRefExpr const* relevantCXXMemberCallExpr(CXXMemberCallExpr const* expr)
|
|
{
|
|
StringType t = relevantStringType(expr->getObjectType());
|
|
if (t == StringType::None)
|
|
{
|
|
return nullptr;
|
|
}
|
|
bool good = false;
|
|
auto const d = expr->getMethodDecl();
|
|
if (d->getOverloadedOperator() == OO_Subscript)
|
|
{
|
|
good = true;
|
|
}
|
|
else if (auto const i = d->getIdentifier())
|
|
{
|
|
auto const n = i->getName();
|
|
if (n == "endsWith" || n == "isEmpty" || n == "startsWith" || n == "subView"
|
|
|| n == "indexOf" || n == "lastIndexOf" || n == "compareTo" || n == "match"
|
|
|| n == "trim" || n == "toInt32" || n == "toInt64" || n == "toDouble"
|
|
|| n == "equalsIgnoreAsciiCase" || n == "compareToIgnoreAsciiCase" || n == "getToken"
|
|
|| n == "copy")
|
|
{
|
|
good = true;
|
|
}
|
|
#if 0
|
|
//TODO: rtl::O[U]String::getLength would be awkward to replace with
|
|
// std::[u16]string_view::length/size due to the sal_Int32 vs. std::size_t return type
|
|
// mismatch (C++20 ssize might make that easier, though); and while rtl::OString::getStr is
|
|
// documented to be NUL-terminated (so not eligible for replacement with
|
|
// std::string_view::data in general), rtl::OUString::getStr is not (so should be eligible
|
|
// for replacement with std::u16string_view::data, but some call sites might nevertheless
|
|
// incorrectly rely on NUL termination, so any replacement would need careful review):
|
|
if (n == "getLength" || (t == StringType::RtlOustring && n == "getStr"))
|
|
{
|
|
good = true;
|
|
}
|
|
#endif
|
|
}
|
|
if (!good)
|
|
{
|
|
return nullptr;
|
|
}
|
|
return relevantDeclRefExpr(expr->getImplicitObjectArgument());
|
|
}
|
|
|
|
SmallVector<DeclRefExpr const*, 2> wrap(DeclRefExpr const* expr)
|
|
{
|
|
if (expr == nullptr)
|
|
{
|
|
return {};
|
|
}
|
|
return { expr };
|
|
}
|
|
|
|
SmallVector<DeclRefExpr const*, 2> relevantCXXOperatorCallExpr(CXXOperatorCallExpr const* expr)
|
|
{
|
|
auto const op = expr->getOperator();
|
|
if (op == OO_Subscript)
|
|
{
|
|
auto const e = expr->getArg(0);
|
|
if (relevantStringType(e->getType()) == StringType::None)
|
|
{
|
|
return {};
|
|
}
|
|
return wrap(relevantDeclRefExpr(e));
|
|
}
|
|
if (expr->isComparisonOp() || (op == OO_Plus && expr->getNumArgs() == 2))
|
|
{
|
|
SmallVector<DeclRefExpr const*, 2> v;
|
|
if (auto const e = relevantDeclRefExpr(expr->getArg(0)))
|
|
{
|
|
v.push_back(e);
|
|
}
|
|
if (auto const e = relevantDeclRefExpr(expr->getArg(1)))
|
|
{
|
|
v.push_back(e);
|
|
}
|
|
return v;
|
|
}
|
|
if (op == OO_PlusEqual)
|
|
{
|
|
if (relevantStringType(expr->getArg(0)->getType()) != StringType::RtlOustring)
|
|
{
|
|
return {};
|
|
}
|
|
return wrap(relevantDeclRefExpr(expr->getArg(1)));
|
|
}
|
|
if (op == OO_Equal)
|
|
{
|
|
if (!isStringView(expr->getArg(1)->getType()))
|
|
{
|
|
return {};
|
|
}
|
|
return wrap(relevantDeclRefExpr(expr->getArg(0)));
|
|
}
|
|
return {};
|
|
}
|
|
|
|
static const Expr* IgnoreImplicitAndConversionOperator(const Expr* expr)
|
|
{
|
|
expr = expr->IgnoreImplicit();
|
|
if (auto memberCall = dyn_cast<CXXMemberCallExpr>(expr))
|
|
{
|
|
if (auto conversionDecl = dyn_cast_or_null<CXXConversionDecl>(memberCall->getMethodDecl()))
|
|
{
|
|
if (!conversionDecl->isExplicit())
|
|
expr = memberCall->getImplicitObjectArgument()->IgnoreImplicit();
|
|
}
|
|
}
|
|
return expr;
|
|
}
|
|
|
|
class StringViewVar final
|
|
: public loplugin::FunctionAddress<loplugin::FilteringPlugin<StringViewVar>>
|
|
{
|
|
public:
|
|
explicit StringViewVar(loplugin::InstantiationData const& data)
|
|
: FunctionAddress(data)
|
|
{
|
|
}
|
|
|
|
bool VisitVarDecl(VarDecl* decl)
|
|
{
|
|
if (ignoreLocation(decl))
|
|
{
|
|
return true;
|
|
}
|
|
if (decl->hasGlobalStorage())
|
|
{
|
|
return true;
|
|
}
|
|
if (!decl->isThisDeclarationADefinition())
|
|
{
|
|
return true;
|
|
}
|
|
if (!relevantVarDecl(decl))
|
|
{
|
|
return true;
|
|
}
|
|
if (decl->getInit())
|
|
{
|
|
auto expr = IgnoreImplicitAndConversionOperator(decl->getInit());
|
|
if (auto castExpr = dyn_cast<CXXFunctionalCastExpr>(expr))
|
|
{
|
|
expr = IgnoreImplicitAndConversionOperator(castExpr->getSubExpr());
|
|
}
|
|
if (auto cxxConstruct = dyn_cast<CXXConstructExpr>(expr))
|
|
{
|
|
if (cxxConstruct->getNumArgs() == 0)
|
|
currentVars_.insert(decl); // default constructor
|
|
else if (cxxConstruct->getNumArgs() == 1
|
|
&& isStringView(cxxConstruct->getArg(0)->getType()))
|
|
currentVars_.insert(decl);
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool TraverseImplicitCastExpr(ImplicitCastExpr* expr)
|
|
{
|
|
if (ignoreLocation(expr))
|
|
{
|
|
return true;
|
|
}
|
|
auto const e = relevantImplicitCastExpr(expr);
|
|
if (e == nullptr)
|
|
{
|
|
return FunctionAddress::TraverseImplicitCastExpr(expr);
|
|
}
|
|
currentGoodUses_.insert(e);
|
|
auto const ret = FunctionAddress::TraverseImplicitCastExpr(expr);
|
|
currentGoodUses_.erase(e);
|
|
return ret;
|
|
}
|
|
|
|
bool TraverseCStyleCastExpr(CStyleCastExpr* expr)
|
|
{
|
|
if (ignoreLocation(expr))
|
|
{
|
|
return true;
|
|
}
|
|
auto const e = relevantCStyleCastExpr(expr);
|
|
if (e == nullptr)
|
|
{
|
|
return FunctionAddress::TraverseCStyleCastExpr(expr);
|
|
}
|
|
currentGoodUses_.insert(e);
|
|
auto const ret = FunctionAddress::TraverseCStyleCastExpr(expr);
|
|
currentGoodUses_.erase(e);
|
|
return ret;
|
|
}
|
|
|
|
bool TraverseCXXMemberCallExpr(CXXMemberCallExpr* expr)
|
|
{
|
|
if (ignoreLocation(expr))
|
|
{
|
|
return true;
|
|
}
|
|
auto const e = relevantCXXMemberCallExpr(expr);
|
|
if (e == nullptr)
|
|
{
|
|
return FunctionAddress::TraverseCXXMemberCallExpr(expr);
|
|
}
|
|
currentGoodUses_.insert(e);
|
|
auto const ret = FunctionAddress::TraverseCXXMemberCallExpr(expr);
|
|
currentGoodUses_.erase(e);
|
|
return ret;
|
|
}
|
|
|
|
bool TraverseCXXOperatorCallExpr(CXXOperatorCallExpr* expr)
|
|
{
|
|
if (ignoreLocation(expr))
|
|
{
|
|
return true;
|
|
}
|
|
auto const es = relevantCXXOperatorCallExpr(expr);
|
|
if (es.empty())
|
|
{
|
|
return FunctionAddress::TraverseCXXOperatorCallExpr(expr);
|
|
}
|
|
currentGoodUses_.insert(es.begin(), es.end());
|
|
auto const ret = FunctionAddress::TraverseCXXOperatorCallExpr(expr);
|
|
for (auto const i : es)
|
|
{
|
|
currentGoodUses_.erase(i);
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
bool VisitDeclRefExpr(DeclRefExpr* expr)
|
|
{
|
|
if (!FunctionAddress::VisitDeclRefExpr(expr))
|
|
{
|
|
return false;
|
|
}
|
|
if (ignoreLocation(expr))
|
|
{
|
|
return true;
|
|
}
|
|
if (currentGoodUses_.find(expr) != currentGoodUses_.end())
|
|
{
|
|
return true;
|
|
}
|
|
if (auto const d = dyn_cast<VarDecl>(expr->getDecl()))
|
|
{
|
|
currentVars_.erase(d);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
private:
|
|
void run() override
|
|
{
|
|
if (!compiler.getLangOpts().CPlusPlus)
|
|
{
|
|
return;
|
|
}
|
|
if (compiler.getPreprocessor().getIdentifierInfo("NDEBUG")->hasMacroDefinition())
|
|
{
|
|
return;
|
|
}
|
|
StringRef fn(handler.getMainFileName());
|
|
// leave the string QA tests alone
|
|
if (loplugin::hasPathnamePrefix(fn, SRCDIR "/sal/qa/"))
|
|
{
|
|
return;
|
|
}
|
|
// false +
|
|
if (loplugin::hasPathnamePrefix(fn, SRCDIR "/svtools/source/svrtf/parrtf.cxx"))
|
|
{
|
|
return;
|
|
}
|
|
if (!TraverseDecl(compiler.getASTContext().getTranslationUnitDecl()))
|
|
{
|
|
return;
|
|
}
|
|
for (auto const i : currentVars_)
|
|
{
|
|
auto const t = relevantStringType(i->getType().getNonReferenceType());
|
|
report(DiagnosticsEngine::Warning,
|
|
"replace var of type %0 with "
|
|
"'%select{std::string_view|std::u16string_view}1'",
|
|
i->getLocation())
|
|
<< i->getType() << (int(t) - 1) << i->getSourceRange();
|
|
}
|
|
}
|
|
|
|
std::set<VarDecl const*> currentVars_;
|
|
std::set<DeclRefExpr const*> currentGoodUses_;
|
|
};
|
|
|
|
static loplugin::Plugin::Registration<StringViewVar> reg("stringviewvar",
|
|
false); // might generate false+
|
|
}
|
|
|
|
/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */
|