5d80385fd1
Change-Id: I36bc86fcffc3c10fe44e60d779c9aa48eeed00f2 Reviewed-on: https://gerrit.libreoffice.org/c/core/+/154749 Tested-by: Jenkins Reviewed-by: Stephan Bergmann <sbergman@redhat.com>
2259 lines
87 KiB
C++
2259 lines
87 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 <cstdint>
|
|
#include <cstdlib>
|
|
#include <iomanip>
|
|
#include <limits>
|
|
#include <sstream>
|
|
#include <stack>
|
|
#include <string>
|
|
#include <vector>
|
|
#include <iostream>
|
|
|
|
#include "check.hxx"
|
|
#include "compat.hxx"
|
|
#include "plugin.hxx"
|
|
|
|
// Define a "string constant" to be a constant expression either of type "array
|
|
// of N char" where each array element is a non-NULL ASCII character---except
|
|
// that the last array element may be NULL, or, in some situations, of type char
|
|
// with an ASCII value (including NULL). Note that the former includes
|
|
// expressions denoting narrow string literals like "foo", and, with toolchains
|
|
// that support constexpr, constexpr variables declared like
|
|
//
|
|
// constexpr char str[] = "bar";
|
|
//
|
|
// This plugin flags uses of OUString functions with string constant arguments
|
|
// that can be rewritten more directly, like
|
|
//
|
|
// OUString::createFromAscii("foo") -> "foo"
|
|
//
|
|
// and
|
|
//
|
|
// s.equals(OUString("bar")) -> s == "bar"
|
|
|
|
namespace {
|
|
|
|
SourceLocation getMemberLocation(Expr const * expr) {
|
|
CallExpr const * e1 = dyn_cast<CallExpr>(expr);
|
|
MemberExpr const * e2 = e1 == nullptr
|
|
? nullptr : dyn_cast<MemberExpr>(e1->getCallee());
|
|
return e2 == nullptr ? expr->getExprLoc()/*TODO*/ : e2->getMemberLoc();
|
|
}
|
|
|
|
bool isLhsOfAssignment(FunctionDecl const * decl, unsigned parameter) {
|
|
if (parameter != 0) {
|
|
return false;
|
|
}
|
|
auto oo = decl->getOverloadedOperator();
|
|
return oo == OO_Equal
|
|
|| (oo >= OO_PlusEqual && oo <= OO_GreaterGreaterEqual);
|
|
}
|
|
|
|
bool hasOverloads(FunctionDecl const * decl, unsigned arguments) {
|
|
int n = 0;
|
|
auto ctx = decl->getDeclContext();
|
|
if (ctx->getDeclKind() == Decl::LinkageSpec) {
|
|
ctx = ctx->getParent();
|
|
}
|
|
auto res = ctx->lookup(decl->getDeclName());
|
|
for (auto d = res.begin(); d != res.end(); ++d) {
|
|
FunctionDecl const * f = dyn_cast<FunctionDecl>(*d);
|
|
if (f != nullptr && f->getMinRequiredArguments() <= arguments
|
|
&& f->getNumParams() >= arguments)
|
|
{
|
|
auto consDecl = dyn_cast<CXXConstructorDecl>(f);
|
|
if (consDecl && consDecl->isCopyOrMoveConstructor()) {
|
|
continue;
|
|
}
|
|
++n;
|
|
if (n == 2) {
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
CXXConstructExpr const * lookForCXXConstructExpr(Expr const * expr) {
|
|
if (auto e = dyn_cast<MaterializeTemporaryExpr>(expr)) {
|
|
expr = e->getSubExpr();
|
|
}
|
|
if (auto e = dyn_cast<CXXFunctionalCastExpr>(expr)) {
|
|
expr = e->getSubExpr();
|
|
}
|
|
if (auto e = dyn_cast<CXXBindTemporaryExpr>(expr)) {
|
|
expr = e->getSubExpr();
|
|
}
|
|
if (auto const e = dyn_cast<CXXMemberCallExpr>(expr)) {
|
|
// Look through OString::operator std::string_view:
|
|
if (auto const d = dyn_cast_or_null<CXXConversionDecl>(e->getCalleeDecl())) {
|
|
return lookForCXXConstructExpr(e->getImplicitObjectArgument()->IgnoreParenImpCasts());
|
|
}
|
|
}
|
|
return dyn_cast<CXXConstructExpr>(expr);
|
|
}
|
|
|
|
char const * adviseNonArray(bool nonArray) {
|
|
return nonArray
|
|
? ", and turn the non-array string constant into an array" : "";
|
|
}
|
|
|
|
class StringConstant:
|
|
public loplugin::FilteringRewritePlugin<StringConstant>
|
|
{
|
|
public:
|
|
explicit StringConstant(loplugin::InstantiationData const & data):
|
|
FilteringRewritePlugin(data) {}
|
|
|
|
void run() override;
|
|
|
|
bool TraverseFunctionDecl(FunctionDecl * decl) {
|
|
returnTypes_.push(decl->getDeclaredReturnType());
|
|
auto const ret = RecursiveASTVisitor::TraverseFunctionDecl(decl);
|
|
assert(!returnTypes_.empty());
|
|
assert(returnTypes_.top() == decl->getDeclaredReturnType());
|
|
returnTypes_.pop();
|
|
return ret;
|
|
}
|
|
|
|
bool TraverseCXXDeductionGuideDecl(CXXDeductionGuideDecl * decl) {
|
|
returnTypes_.push(decl->getDeclaredReturnType());
|
|
auto const ret = RecursiveASTVisitor::TraverseCXXDeductionGuideDecl(
|
|
decl);
|
|
assert(!returnTypes_.empty());
|
|
assert(returnTypes_.top() == decl->getDeclaredReturnType());
|
|
returnTypes_.pop();
|
|
return ret;
|
|
}
|
|
|
|
bool TraverseCXXMethodDecl(CXXMethodDecl * decl) {
|
|
returnTypes_.push(decl->getDeclaredReturnType());
|
|
auto const ret = RecursiveASTVisitor::TraverseCXXMethodDecl(decl);
|
|
assert(!returnTypes_.empty());
|
|
assert(returnTypes_.top() == decl->getDeclaredReturnType());
|
|
returnTypes_.pop();
|
|
return ret;
|
|
}
|
|
|
|
bool TraverseCXXConstructorDecl(CXXConstructorDecl * decl) {
|
|
returnTypes_.push(decl->getDeclaredReturnType());
|
|
auto const ret = RecursiveASTVisitor::TraverseCXXConstructorDecl(decl);
|
|
assert(!returnTypes_.empty());
|
|
assert(returnTypes_.top() == decl->getDeclaredReturnType());
|
|
returnTypes_.pop();
|
|
return ret;
|
|
}
|
|
|
|
bool TraverseCXXDestructorDecl(CXXDestructorDecl * decl) {
|
|
returnTypes_.push(decl->getDeclaredReturnType());
|
|
auto const ret = RecursiveASTVisitor::TraverseCXXDestructorDecl(decl);
|
|
assert(!returnTypes_.empty());
|
|
assert(returnTypes_.top() == decl->getDeclaredReturnType());
|
|
returnTypes_.pop();
|
|
return ret;
|
|
}
|
|
|
|
bool TraverseCXXConversionDecl(CXXConversionDecl * decl) {
|
|
returnTypes_.push(decl->getDeclaredReturnType());
|
|
auto const ret = RecursiveASTVisitor::TraverseCXXConversionDecl(decl);
|
|
assert(!returnTypes_.empty());
|
|
assert(returnTypes_.top() == decl->getDeclaredReturnType());
|
|
returnTypes_.pop();
|
|
return ret;
|
|
}
|
|
|
|
bool TraverseObjCMethodDecl(ObjCMethodDecl * decl) {
|
|
returnTypes_.push(decl->getReturnType());
|
|
auto const ret = RecursiveASTVisitor::TraverseObjCMethodDecl(decl);
|
|
assert(!returnTypes_.empty());
|
|
assert(returnTypes_.top() == decl->getReturnType());
|
|
returnTypes_.pop();
|
|
return ret;
|
|
}
|
|
|
|
bool TraverseCallExpr(CallExpr * expr);
|
|
|
|
bool TraverseCXXMemberCallExpr(CXXMemberCallExpr * expr);
|
|
|
|
bool TraverseCXXOperatorCallExpr(CXXOperatorCallExpr * expr);
|
|
|
|
bool TraverseCXXConstructExpr(CXXConstructExpr * expr);
|
|
|
|
bool VisitCallExpr(CallExpr const * expr);
|
|
|
|
bool VisitCXXMemberCallExpr(CXXMemberCallExpr const * expr);
|
|
|
|
bool VisitCXXConstructExpr(CXXConstructExpr const * expr);
|
|
|
|
bool VisitReturnStmt(ReturnStmt const * stmt);
|
|
|
|
private:
|
|
enum class ContentKind { Ascii, Utf8, Arbitrary };
|
|
|
|
enum class TreatEmpty { DefaultCtor, CheckEmpty, Error };
|
|
|
|
enum class ChangeKind { Char, CharLen, SingleChar, OUStringChar };
|
|
|
|
enum class PassThrough { No, EmptyConstantString, NonEmptyConstantString };
|
|
|
|
std::string describeChangeKind(ChangeKind kind);
|
|
|
|
bool isStringConstant(
|
|
Expr const * expr, unsigned * size, bool * nonArray,
|
|
ContentKind * content, bool * embeddedNuls, bool * terminatingNul,
|
|
std::vector<char32_t> * utf8Content = nullptr);
|
|
|
|
bool isZero(Expr const * expr);
|
|
|
|
void reportChange(
|
|
Expr const * expr, ChangeKind kind, std::string const & original,
|
|
std::string const & replacement, PassThrough pass, bool nonArray,
|
|
char const * rewriteFrom, char const * rewriteTo);
|
|
|
|
void checkEmpty(
|
|
CallExpr const * expr, FunctionDecl const * callee,
|
|
TreatEmpty treatEmpty, unsigned size, std::string * replacement);
|
|
|
|
void handleChar(
|
|
CallExpr const * expr, unsigned arg, FunctionDecl const * callee,
|
|
std::string const & replacement, TreatEmpty treatEmpty, bool literal,
|
|
char const * rewriteFrom = nullptr, char const * rewriteTo = nullptr);
|
|
|
|
void handleCharLen(
|
|
CallExpr const * expr, unsigned arg1, unsigned arg2,
|
|
FunctionDecl const * callee, std::string const & replacement,
|
|
TreatEmpty treatEmpty);
|
|
|
|
void handleOUStringCtor(
|
|
CallExpr const * expr, unsigned arg, FunctionDecl const * callee,
|
|
bool explicitFunctionalCastNotation);
|
|
|
|
void handleOStringCtor(
|
|
CallExpr const * expr, unsigned arg, FunctionDecl const * callee,
|
|
bool explicitFunctionalCastNotation);
|
|
|
|
void handleOUStringCtor(
|
|
Expr const * expr, Expr const * argExpr, FunctionDecl const * callee,
|
|
bool explicitFunctionalCastNotation);
|
|
|
|
void handleOStringCtor(
|
|
Expr const * expr, Expr const * argExpr, FunctionDecl const * callee,
|
|
bool explicitFunctionalCastNotation);
|
|
|
|
enum class StringKind { Unicode, Char };
|
|
void handleStringCtor(
|
|
Expr const * expr, Expr const * argExpr, FunctionDecl const * callee,
|
|
bool explicitFunctionalCastNotation, StringKind stringKind);
|
|
|
|
void handleFunArgOstring(
|
|
CallExpr const * expr, unsigned arg, FunctionDecl const * callee);
|
|
|
|
std::stack<QualType> returnTypes_;
|
|
std::stack<Expr const *> calls_;
|
|
};
|
|
|
|
void StringConstant::run() {
|
|
if (compiler.getLangOpts().CPlusPlus
|
|
&& compiler.getPreprocessor().getIdentifierInfo(
|
|
"LIBO_INTERNAL_ONLY")->hasMacroDefinition())
|
|
//TODO: some parts of it are useful for external code, too
|
|
{
|
|
TraverseDecl(compiler.getASTContext().getTranslationUnitDecl());
|
|
}
|
|
}
|
|
|
|
bool StringConstant::TraverseCallExpr(CallExpr * expr) {
|
|
if (!WalkUpFromCallExpr(expr)) {
|
|
return false;
|
|
}
|
|
calls_.push(expr);
|
|
bool bRes = true;
|
|
for (auto * e: expr->children()) {
|
|
if (!TraverseStmt(e)) {
|
|
bRes = false;
|
|
break;
|
|
}
|
|
}
|
|
calls_.pop();
|
|
return bRes;
|
|
}
|
|
|
|
bool StringConstant::TraverseCXXMemberCallExpr(CXXMemberCallExpr * expr) {
|
|
if (!WalkUpFromCXXMemberCallExpr(expr)) {
|
|
return false;
|
|
}
|
|
calls_.push(expr);
|
|
bool bRes = true;
|
|
for (auto * e: expr->children()) {
|
|
if (!TraverseStmt(e)) {
|
|
bRes = false;
|
|
break;
|
|
}
|
|
}
|
|
calls_.pop();
|
|
return bRes;
|
|
}
|
|
|
|
bool StringConstant::TraverseCXXOperatorCallExpr(CXXOperatorCallExpr * expr)
|
|
{
|
|
if (!WalkUpFromCXXOperatorCallExpr(expr)) {
|
|
return false;
|
|
}
|
|
calls_.push(expr);
|
|
bool bRes = true;
|
|
for (auto * e: expr->children()) {
|
|
if (!TraverseStmt(e)) {
|
|
bRes = false;
|
|
break;
|
|
}
|
|
}
|
|
calls_.pop();
|
|
return bRes;
|
|
}
|
|
|
|
bool StringConstant::TraverseCXXConstructExpr(CXXConstructExpr * expr) {
|
|
if (!WalkUpFromCXXConstructExpr(expr)) {
|
|
return false;
|
|
}
|
|
calls_.push(expr);
|
|
bool bRes = true;
|
|
for (auto * e: expr->children()) {
|
|
if (!TraverseStmt(e)) {
|
|
bRes = false;
|
|
break;
|
|
}
|
|
}
|
|
calls_.pop();
|
|
return bRes;
|
|
}
|
|
|
|
bool StringConstant::VisitCallExpr(CallExpr const * expr) {
|
|
if (ignoreLocation(expr)) {
|
|
return true;
|
|
}
|
|
FunctionDecl const * fdecl = expr->getDirectCallee();
|
|
if (fdecl == nullptr) {
|
|
return true;
|
|
}
|
|
for (unsigned i = 0; i != fdecl->getNumParams(); ++i) {
|
|
auto t = fdecl->getParamDecl(i)->getType();
|
|
if (loplugin::TypeCheck(t).NotSubstTemplateTypeParmType()
|
|
.LvalueReference().Const().NotSubstTemplateTypeParmType()
|
|
.Class("OUString").Namespace("rtl").GlobalNamespace())
|
|
{
|
|
if (!(isLhsOfAssignment(fdecl, i)
|
|
|| hasOverloads(fdecl, expr->getNumArgs())))
|
|
{
|
|
handleOUStringCtor(expr, i, fdecl, true);
|
|
}
|
|
}
|
|
if (loplugin::TypeCheck(t).NotSubstTemplateTypeParmType()
|
|
.LvalueReference().Const().NotSubstTemplateTypeParmType()
|
|
.Class("OString").Namespace("rtl").GlobalNamespace())
|
|
{
|
|
if (!(isLhsOfAssignment(fdecl, i)
|
|
|| hasOverloads(fdecl, expr->getNumArgs())))
|
|
{
|
|
handleOStringCtor(expr, i, fdecl, true);
|
|
}
|
|
}
|
|
}
|
|
loplugin::DeclCheck dc(fdecl);
|
|
//TODO: u.compareToAscii("foo") -> u.???("foo")
|
|
//TODO: u.compareToIgnoreAsciiCaseAscii("foo") -> u.???("foo")
|
|
if ((dc.Function("createFromAscii").Class("OUString").Namespace("rtl")
|
|
.GlobalNamespace())
|
|
&& fdecl->getNumParams() == 1)
|
|
{
|
|
// OUString::createFromAscii("foo") -> OUString("foo")
|
|
handleChar(
|
|
expr, 0, fdecl, "rtl::OUString constructor",
|
|
TreatEmpty::DefaultCtor, true);
|
|
return true;
|
|
}
|
|
if ((dc.Function("endsWithAsciiL").Class("OUString").Namespace("rtl")
|
|
.GlobalNamespace())
|
|
&& fdecl->getNumParams() == 2)
|
|
{
|
|
// u.endsWithAsciiL("foo", 3) -> u.endsWith("foo"):
|
|
handleCharLen(
|
|
expr, 0, 1, fdecl, "rtl::OUString::endsWith", TreatEmpty::Error);
|
|
return true;
|
|
}
|
|
if ((dc.Function("endsWithIgnoreAsciiCaseAsciiL").Class("OUString")
|
|
.Namespace("rtl").GlobalNamespace())
|
|
&& fdecl->getNumParams() == 2)
|
|
{
|
|
// u.endsWithIgnoreAsciiCaseAsciiL("foo", 3) ->
|
|
// u.endsWithIgnoreAsciiCase("foo"):
|
|
handleCharLen(
|
|
expr, 0, 1, fdecl, "rtl::OUString::endsWithIgnoreAsciiCase",
|
|
TreatEmpty::Error);
|
|
return true;
|
|
}
|
|
if ((dc.Function("equalsAscii").Class("OUString").Namespace("rtl")
|
|
.GlobalNamespace())
|
|
&& fdecl->getNumParams() == 1)
|
|
{
|
|
// u.equalsAscii("foo") -> u == "foo":
|
|
handleChar(
|
|
expr, 0, fdecl, "operator ==", TreatEmpty::CheckEmpty, false);
|
|
return true;
|
|
}
|
|
if ((dc.Function("equalsAsciiL").Class("OUString").Namespace("rtl")
|
|
.GlobalNamespace())
|
|
&& fdecl->getNumParams() == 2)
|
|
{
|
|
// u.equalsAsciiL("foo", 3) -> u == "foo":
|
|
handleCharLen(expr, 0, 1, fdecl, "operator ==", TreatEmpty::CheckEmpty);
|
|
return true;
|
|
}
|
|
if ((dc.Function("equalsIgnoreAsciiCaseAscii").Class("OUString")
|
|
.Namespace("rtl").GlobalNamespace())
|
|
&& fdecl->getNumParams() == 1)
|
|
{
|
|
// u.equalsIgnoreAsciiCaseAscii("foo") ->
|
|
// u.equalsIngoreAsciiCase("foo"):
|
|
|
|
auto file = getFilenameOfLocation(
|
|
compiler.getSourceManager().getSpellingLoc(expr->getBeginLoc()));
|
|
if (loplugin::isSamePathname(
|
|
file, SRCDIR "/sal/qa/rtl/strings/test_oustring_compare.cxx"))
|
|
{
|
|
return true;
|
|
}
|
|
handleChar(
|
|
expr, 0, fdecl, "rtl::OUString::equalsIgnoreAsciiCase",
|
|
TreatEmpty::CheckEmpty, false);
|
|
return true;
|
|
}
|
|
if ((dc.Function("equalsIgnoreAsciiCaseAsciiL").Class("OUString")
|
|
.Namespace("rtl").GlobalNamespace())
|
|
&& fdecl->getNumParams() == 2)
|
|
{
|
|
// u.equalsIgnoreAsciiCaseAsciiL("foo", 3) ->
|
|
// u.equalsIngoreAsciiCase("foo"):
|
|
auto file = getFilenameOfLocation(
|
|
compiler.getSourceManager().getSpellingLoc(expr->getBeginLoc()));
|
|
if (loplugin::isSamePathname(
|
|
file, SRCDIR "/sal/qa/rtl/strings/test_oustring_compare.cxx"))
|
|
{
|
|
return true;
|
|
}
|
|
handleCharLen(
|
|
expr, 0, 1, fdecl, "rtl::OUString::equalsIgnoreAsciiCase",
|
|
TreatEmpty::CheckEmpty);
|
|
return true;
|
|
}
|
|
if ((dc.Function("indexOfAsciiL").Class("OUString").Namespace("rtl")
|
|
.GlobalNamespace())
|
|
&& fdecl->getNumParams() == 3)
|
|
{
|
|
assert(expr->getNumArgs() == 3);
|
|
// u.indexOfAsciiL("foo", 3, i) -> u.indexOf("foo", i):
|
|
handleCharLen(
|
|
expr, 0, 1, fdecl, "rtl::OUString::indexOf", TreatEmpty::Error);
|
|
return true;
|
|
}
|
|
if ((dc.Function("lastIndexOfAsciiL").Class("OUString").Namespace("rtl")
|
|
.GlobalNamespace())
|
|
&& fdecl->getNumParams() == 2)
|
|
{
|
|
// u.lastIndexOfAsciiL("foo", 3) -> u.lastIndexOf("foo"):
|
|
handleCharLen(
|
|
expr, 0, 1, fdecl, "rtl::OUString::lastIndexOf", TreatEmpty::Error);
|
|
return true;
|
|
}
|
|
if ((dc.Function("matchAsciiL").Class("OUString").Namespace("rtl")
|
|
.GlobalNamespace())
|
|
&& fdecl->getNumParams() == 3)
|
|
{
|
|
assert(expr->getNumArgs() == 3);
|
|
// u.matchAsciiL("foo", 3, i) -> u.match("foo", i):
|
|
handleCharLen(
|
|
expr, 0, 1, fdecl,
|
|
(isZero(expr->getArg(2))
|
|
? std::string("rtl::OUString::startsWith")
|
|
: std::string("rtl::OUString::match")),
|
|
TreatEmpty::Error);
|
|
return true;
|
|
}
|
|
if ((dc.Function("matchIgnoreAsciiCaseAsciiL").Class("OUString")
|
|
.Namespace("rtl").GlobalNamespace())
|
|
&& fdecl->getNumParams() == 3)
|
|
{
|
|
assert(expr->getNumArgs() == 3);
|
|
// u.matchIgnoreAsciiCaseAsciiL("foo", 3, i) ->
|
|
// u.matchIgnoreAsciiCase("foo", i):
|
|
handleCharLen(
|
|
expr, 0, 1, fdecl,
|
|
(isZero(expr->getArg(2))
|
|
? std::string("rtl::OUString::startsWithIgnoreAsciiCase")
|
|
: std::string("rtl::OUString::matchIgnoreAsciiCase")),
|
|
TreatEmpty::Error);
|
|
return true;
|
|
}
|
|
if ((dc.Function("reverseCompareToAsciiL").Class("OUString")
|
|
.Namespace("rtl").GlobalNamespace())
|
|
&& fdecl->getNumParams() == 2)
|
|
{
|
|
// u.reverseCompareToAsciiL("foo", 3) -> u.reverseCompareTo("foo"):
|
|
handleCharLen(
|
|
expr, 0, 1, fdecl, "rtl::OUString::reverseCompareTo",
|
|
TreatEmpty::Error);
|
|
return true;
|
|
}
|
|
if ((dc.Function("reverseCompareTo").Class("OUString").Namespace("rtl")
|
|
.GlobalNamespace())
|
|
&& fdecl->getNumParams() == 1)
|
|
{
|
|
handleOUStringCtor(expr, 0, fdecl, false);
|
|
return true;
|
|
}
|
|
if ((dc.Function("equalsIgnoreAsciiCase").Class("OUString").Namespace("rtl")
|
|
.GlobalNamespace())
|
|
&& fdecl->getNumParams() == 1)
|
|
{
|
|
handleOUStringCtor(expr, 0, fdecl, false);
|
|
return true;
|
|
}
|
|
if ((dc.Function("match").Class("OUString").Namespace("rtl")
|
|
.GlobalNamespace())
|
|
&& fdecl->getNumParams() == 2)
|
|
{
|
|
handleOUStringCtor(expr, 0, fdecl, false);
|
|
return true;
|
|
}
|
|
if ((dc.Function("matchIgnoreAsciiCase").Class("OUString").Namespace("rtl")
|
|
.GlobalNamespace())
|
|
&& fdecl->getNumParams() == 2)
|
|
{
|
|
handleOUStringCtor(expr, 0, fdecl, false);
|
|
return true;
|
|
}
|
|
if ((dc.Function("startsWith").Class("OUString").Namespace("rtl")
|
|
.GlobalNamespace())
|
|
&& fdecl->getNumParams() == 2)
|
|
{
|
|
handleOUStringCtor(expr, 0, fdecl, false);
|
|
return true;
|
|
}
|
|
if ((dc.Function("startsWithIgnoreAsciiCase").Class("OUString")
|
|
.Namespace("rtl").GlobalNamespace())
|
|
&& fdecl->getNumParams() == 2)
|
|
{
|
|
handleOUStringCtor(expr, 0, fdecl, false);
|
|
return true;
|
|
}
|
|
if ((dc.Function("endsWith").Class("OUString").Namespace("rtl")
|
|
.GlobalNamespace())
|
|
&& fdecl->getNumParams() == 2)
|
|
{
|
|
handleOUStringCtor(expr, 0, fdecl, false);
|
|
return true;
|
|
}
|
|
if ((dc.Function("endsWithIgnoreAsciiCase").Class("OUString")
|
|
.Namespace("rtl").GlobalNamespace())
|
|
&& fdecl->getNumParams() == 2)
|
|
{
|
|
handleOUStringCtor(expr, 0, fdecl, false);
|
|
return true;
|
|
}
|
|
if ((dc.Function("indexOf").Class("OUString").Namespace("rtl")
|
|
.GlobalNamespace())
|
|
&& fdecl->getNumParams() == 2)
|
|
{
|
|
handleOUStringCtor(expr, 0, fdecl, false);
|
|
return true;
|
|
}
|
|
if ((dc.Function("lastIndexOf").Class("OUString").Namespace("rtl")
|
|
.GlobalNamespace())
|
|
&& fdecl->getNumParams() == 1)
|
|
{
|
|
handleOUStringCtor(expr, 0, fdecl, false);
|
|
return true;
|
|
}
|
|
if ((dc.Function("replaceFirst").Class("OUString").Namespace("rtl")
|
|
.GlobalNamespace())
|
|
&& fdecl->getNumParams() == 3)
|
|
{
|
|
handleOUStringCtor(expr, 0, fdecl, false);
|
|
handleOUStringCtor(expr, 1, fdecl, false);
|
|
return true;
|
|
}
|
|
if ((dc.Function("replaceAll").Class("OUString").Namespace("rtl")
|
|
.GlobalNamespace())
|
|
&& (fdecl->getNumParams() == 2 || fdecl->getNumParams() == 3))
|
|
{
|
|
handleOUStringCtor(expr, 0, fdecl, false);
|
|
handleOUStringCtor(expr, 1, fdecl, false);
|
|
return true;
|
|
}
|
|
if ((dc.Operator(OO_PlusEqual).Class("OUString").Namespace("rtl")
|
|
.GlobalNamespace())
|
|
&& fdecl->getNumParams() == 1)
|
|
{
|
|
handleOUStringCtor(
|
|
expr, dyn_cast<CXXOperatorCallExpr>(expr) == nullptr ? 0 : 1,
|
|
fdecl, false);
|
|
return true;
|
|
}
|
|
if ((dc.Function("equals").Class("OUString").Namespace("rtl")
|
|
.GlobalNamespace())
|
|
&& fdecl->getNumParams() == 1)
|
|
{
|
|
unsigned n;
|
|
bool nonArray;
|
|
ContentKind cont;
|
|
bool emb;
|
|
bool trm;
|
|
if (!isStringConstant(
|
|
expr->getArg(0)->IgnoreParenImpCasts(), &n, &nonArray, &cont,
|
|
&emb, &trm))
|
|
{
|
|
return true;
|
|
}
|
|
if (cont != ContentKind::Ascii) {
|
|
report(
|
|
DiagnosticsEngine::Warning,
|
|
("call of '%0' with string constant argument containing"
|
|
" non-ASCII characters"),
|
|
expr->getExprLoc())
|
|
<< fdecl->getQualifiedNameAsString() << expr->getSourceRange();
|
|
}
|
|
if (emb) {
|
|
report(
|
|
DiagnosticsEngine::Warning,
|
|
("call of '%0' with string constant argument containing"
|
|
" embedded NULLs"),
|
|
expr->getExprLoc())
|
|
<< fdecl->getQualifiedNameAsString() << expr->getSourceRange();
|
|
}
|
|
if (n == 0) {
|
|
report(
|
|
DiagnosticsEngine::Warning,
|
|
("rewrite call of '%0' with empty string constant argument as"
|
|
" call of 'rtl::OUString::isEmpty'"),
|
|
expr->getExprLoc())
|
|
<< fdecl->getQualifiedNameAsString() << expr->getSourceRange();
|
|
return true;
|
|
}
|
|
}
|
|
if (dc.Operator(OO_EqualEqual).Namespace("rtl").GlobalNamespace()
|
|
&& fdecl->getNumParams() == 2)
|
|
{
|
|
for (unsigned i = 0; i != 2; ++i) {
|
|
unsigned n;
|
|
bool nonArray;
|
|
ContentKind cont;
|
|
bool emb;
|
|
bool trm;
|
|
if (!isStringConstant(
|
|
expr->getArg(i)->IgnoreParenImpCasts(), &n, &nonArray,
|
|
&cont, &emb, &trm))
|
|
{
|
|
continue;
|
|
}
|
|
if (cont != ContentKind::Ascii) {
|
|
report(
|
|
DiagnosticsEngine::Warning,
|
|
("call of '%0' with string constant argument containing"
|
|
" non-ASCII characters"),
|
|
expr->getExprLoc())
|
|
<< fdecl->getQualifiedNameAsString()
|
|
<< expr->getSourceRange();
|
|
}
|
|
if (emb) {
|
|
report(
|
|
DiagnosticsEngine::Warning,
|
|
("call of '%0' with string constant argument containing"
|
|
" embedded NULLs"),
|
|
expr->getExprLoc())
|
|
<< fdecl->getQualifiedNameAsString()
|
|
<< expr->getSourceRange();
|
|
}
|
|
if (n == 0) {
|
|
report(
|
|
DiagnosticsEngine::Warning,
|
|
("rewrite call of '%0' with empty string constant argument"
|
|
" as call of 'rtl::OUString::isEmpty'"),
|
|
expr->getExprLoc())
|
|
<< fdecl->getQualifiedNameAsString()
|
|
<< expr->getSourceRange();
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
if (dc.Operator(OO_ExclaimEqual).Namespace("rtl").GlobalNamespace()
|
|
&& fdecl->getNumParams() == 2)
|
|
{
|
|
for (unsigned i = 0; i != 2; ++i) {
|
|
unsigned n;
|
|
bool nonArray;
|
|
ContentKind cont;
|
|
bool emb;
|
|
bool trm;
|
|
if (!isStringConstant(
|
|
expr->getArg(i)->IgnoreParenImpCasts(), &n, &nonArray,
|
|
&cont, &emb, &trm))
|
|
{
|
|
continue;
|
|
}
|
|
if (cont != ContentKind::Ascii) {
|
|
report(
|
|
DiagnosticsEngine::Warning,
|
|
("call of '%0' with string constant argument containing"
|
|
" non-ASCII characters"),
|
|
expr->getExprLoc())
|
|
<< fdecl->getQualifiedNameAsString()
|
|
<< expr->getSourceRange();
|
|
}
|
|
if (emb) {
|
|
report(
|
|
DiagnosticsEngine::Warning,
|
|
("call of '%0' with string constant argument containing"
|
|
" embedded NULLs"),
|
|
expr->getExprLoc())
|
|
<< fdecl->getQualifiedNameAsString()
|
|
<< expr->getSourceRange();
|
|
}
|
|
if (n == 0) {
|
|
report(
|
|
DiagnosticsEngine::Warning,
|
|
("rewrite call of '%0' with empty string constant argument"
|
|
" as call of '!rtl::OUString::isEmpty'"),
|
|
expr->getExprLoc())
|
|
<< fdecl->getQualifiedNameAsString()
|
|
<< expr->getSourceRange();
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
if (dc.Operator(OO_Equal).Namespace("rtl").GlobalNamespace()
|
|
&& fdecl->getNumParams() == 1)
|
|
{
|
|
unsigned n;
|
|
bool nonArray;
|
|
ContentKind cont;
|
|
bool emb;
|
|
bool trm;
|
|
if (!isStringConstant(
|
|
expr->getArg(1)->IgnoreParenImpCasts(), &n, &nonArray, &cont,
|
|
&emb, &trm))
|
|
{
|
|
return true;
|
|
}
|
|
if (cont != ContentKind::Ascii) {
|
|
report(
|
|
DiagnosticsEngine::Warning,
|
|
("call of '%0' with string constant argument containing"
|
|
" non-ASCII characters"),
|
|
expr->getExprLoc())
|
|
<< fdecl->getQualifiedNameAsString() << expr->getSourceRange();
|
|
}
|
|
if (emb) {
|
|
report(
|
|
DiagnosticsEngine::Warning,
|
|
("call of '%0' with string constant argument containing"
|
|
" embedded NULLs"),
|
|
expr->getExprLoc())
|
|
<< fdecl->getQualifiedNameAsString() << expr->getSourceRange();
|
|
}
|
|
if (n == 0) {
|
|
report(
|
|
DiagnosticsEngine::Warning,
|
|
("rewrite call of '%0' with empty string constant argument as"
|
|
" call of 'rtl::OUString::clear'"),
|
|
expr->getExprLoc())
|
|
<< fdecl->getQualifiedNameAsString() << expr->getSourceRange();
|
|
return true;
|
|
}
|
|
return true;
|
|
}
|
|
if (dc.Function("append").Class("OUStringBuffer").Namespace("rtl").GlobalNamespace()
|
|
&& fdecl->getNumParams() == 1)
|
|
{
|
|
handleChar(expr, 0, fdecl, "", TreatEmpty::Error, false);
|
|
return true;
|
|
}
|
|
if ((dc.Function("appendAscii").Class("OUStringBuffer").Namespace("rtl")
|
|
.GlobalNamespace())
|
|
&& fdecl->getNumParams() == 1)
|
|
{
|
|
// u.appendAscii("foo") -> u.append("foo")
|
|
handleChar(
|
|
expr, 0, fdecl, "rtl::OUStringBuffer::append", TreatEmpty::Error,
|
|
true, "appendAscii", "append");
|
|
return true;
|
|
}
|
|
if ((dc.Function("appendAscii").Class("OUStringBuffer").Namespace("rtl")
|
|
.GlobalNamespace())
|
|
&& fdecl->getNumParams() == 2)
|
|
{
|
|
// u.appendAscii("foo", 3) -> u.append("foo"):
|
|
handleCharLen(
|
|
expr, 0, 1, fdecl, "rtl::OUStringBuffer::append",
|
|
TreatEmpty::Error);
|
|
return true;
|
|
}
|
|
if (dc.Function("append").Class("OStringBuffer").Namespace("rtl")
|
|
.GlobalNamespace())
|
|
{
|
|
switch (fdecl->getNumParams()) {
|
|
case 1:
|
|
handleFunArgOstring(expr, 0, fdecl);
|
|
break;
|
|
case 2:
|
|
{
|
|
// b.append("foo", 3) -> b.append("foo"):
|
|
auto file = getFilenameOfLocation(
|
|
compiler.getSourceManager().getSpellingLoc(
|
|
expr->getBeginLoc()));
|
|
if (loplugin::isSamePathname(
|
|
file,
|
|
SRCDIR "/sal/qa/OStringBuffer/rtl_OStringBuffer.cxx"))
|
|
{
|
|
return true;
|
|
}
|
|
handleCharLen(
|
|
expr, 0, 1, fdecl, "rtl::OStringBuffer::append",
|
|
TreatEmpty::Error);
|
|
}
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
return true;
|
|
}
|
|
if (dc.Function("insert").Class("OStringBuffer").Namespace("rtl")
|
|
.GlobalNamespace())
|
|
{
|
|
switch (fdecl->getNumParams()) {
|
|
case 2:
|
|
handleFunArgOstring(expr, 1, fdecl);
|
|
break;
|
|
case 3:
|
|
{
|
|
// b.insert(i, "foo", 3) -> b.insert(i, "foo"):
|
|
handleCharLen(
|
|
expr, 1, 2, fdecl, "rtl::OStringBuffer::insert",
|
|
TreatEmpty::Error);
|
|
break;
|
|
}
|
|
default:
|
|
break;
|
|
}
|
|
return true;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool StringConstant::VisitCXXMemberCallExpr(CXXMemberCallExpr const * expr) {
|
|
if (ignoreLocation(expr)) {
|
|
return true;
|
|
}
|
|
FunctionDecl const * fdecl = expr->getDirectCallee();
|
|
if (fdecl == nullptr) {
|
|
return true;
|
|
}
|
|
auto const c = loplugin::DeclCheck(fdecl).Function("getStr");
|
|
if ((c.Class("OString").Namespace("rtl").GlobalNamespace()
|
|
|| c.Class("OUString").Namespace("rtl").GlobalNamespace())
|
|
&& fdecl->getNumParams() == 0)
|
|
{
|
|
auto const e1 = expr->getImplicitObjectArgument()->IgnoreImplicit()->IgnoreParens();
|
|
if (auto const e2 = dyn_cast<CXXTemporaryObjectExpr>(e1)) {
|
|
if (e2->getNumArgs() != 0) {
|
|
return true;
|
|
}
|
|
report(
|
|
DiagnosticsEngine::Warning,
|
|
"in call of '%0', replace default-constructed %1 directly with an empty %select{ordinary|UTF-16}2 string literal",
|
|
expr->getExprLoc())
|
|
<< fdecl->getQualifiedNameAsString() << e2->getType() << bool(loplugin::TypeCheck(e2->getType()).Class("OUString")) << expr->getSourceRange();
|
|
return true;
|
|
}
|
|
if (auto const e2 = dyn_cast<CXXFunctionalCastExpr>(e1)) {
|
|
auto const e3 = dyn_cast<clang::StringLiteral>(e2->getSubExprAsWritten()->IgnoreParens());
|
|
if (e3 == nullptr) {
|
|
return true;
|
|
}
|
|
report(
|
|
DiagnosticsEngine::Warning,
|
|
"in call of '%0', replace %1 constructed from a string literal directly with %select{the|a UTF-16}2 string literal",
|
|
expr->getExprLoc())
|
|
<< fdecl->getQualifiedNameAsString() << e2->getType() << (loplugin::TypeCheck(e2->getType()).Class("OUString") && !e3->isUTF16()) << expr->getSourceRange();
|
|
return true;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool StringConstant::VisitCXXConstructExpr(CXXConstructExpr const * expr) {
|
|
if (ignoreLocation(expr)) {
|
|
return true;
|
|
}
|
|
auto classdecl = expr->getConstructor()->getParent();
|
|
if (loplugin::DeclCheck(classdecl)
|
|
.Class("OUString").Namespace("rtl").GlobalNamespace())
|
|
{
|
|
ChangeKind kind;
|
|
PassThrough pass;
|
|
bool simplify;
|
|
switch (expr->getConstructor()->getNumParams()) {
|
|
case 1:
|
|
if (!loplugin::TypeCheck(
|
|
expr->getConstructor()->getParamDecl(0)->getType())
|
|
.Typedef("sal_Unicode").GlobalNamespace())
|
|
{
|
|
return true;
|
|
}
|
|
kind = ChangeKind::SingleChar;
|
|
pass = PassThrough::NonEmptyConstantString;
|
|
simplify = false;
|
|
break;
|
|
case 2:
|
|
{
|
|
auto arg = expr->getArg(0);
|
|
if (loplugin::TypeCheck(arg->getType())
|
|
.Class("OUStringChar_").Namespace("rtl")
|
|
.GlobalNamespace())
|
|
{
|
|
kind = ChangeKind::OUStringChar;
|
|
pass = PassThrough::NonEmptyConstantString;
|
|
simplify = false;
|
|
} else {
|
|
unsigned n;
|
|
bool nonArray;
|
|
ContentKind cont;
|
|
bool emb;
|
|
bool trm;
|
|
if (!isStringConstant(
|
|
arg->IgnoreParenImpCasts(), &n, &nonArray, &cont,
|
|
&emb, &trm))
|
|
{
|
|
return true;
|
|
}
|
|
if (cont != ContentKind::Ascii) {
|
|
report(
|
|
DiagnosticsEngine::Warning,
|
|
("construction of %0 with string constant argument"
|
|
" containing non-ASCII characters"),
|
|
expr->getExprLoc())
|
|
<< classdecl << expr->getSourceRange();
|
|
}
|
|
if (emb) {
|
|
report(
|
|
DiagnosticsEngine::Warning,
|
|
("construction of %0 with string constant argument"
|
|
" containing embedded NULLs"),
|
|
expr->getExprLoc())
|
|
<< classdecl << expr->getSourceRange();
|
|
}
|
|
kind = ChangeKind::Char;
|
|
pass = n == 0
|
|
? PassThrough::EmptyConstantString
|
|
: PassThrough::NonEmptyConstantString;
|
|
simplify = false;
|
|
}
|
|
break;
|
|
}
|
|
case 4:
|
|
{
|
|
unsigned n;
|
|
bool nonArray;
|
|
ContentKind cont;
|
|
bool emb;
|
|
bool trm;
|
|
std::vector<char32_t> utf8Cont;
|
|
if (!isStringConstant(
|
|
expr->getArg(0)->IgnoreParenImpCasts(), &n, &nonArray,
|
|
&cont, &emb, &trm, &utf8Cont))
|
|
{
|
|
return true;
|
|
}
|
|
APSInt res;
|
|
if (!compat::EvaluateAsInt(expr->getArg(1),
|
|
res, compiler.getASTContext()))
|
|
{
|
|
return true;
|
|
}
|
|
if (res != n) {
|
|
report(
|
|
DiagnosticsEngine::Warning,
|
|
("suspicious 'rtl::OUString' constructor with literal"
|
|
" of length %0 and non-matching length argument %1"),
|
|
expr->getExprLoc())
|
|
<< n << compat::toString(res, 10) << expr->getSourceRange();
|
|
return true;
|
|
}
|
|
APSInt enc;
|
|
if (!compat::EvaluateAsInt(expr->getArg(2),
|
|
enc, compiler.getASTContext()))
|
|
{
|
|
return true;
|
|
}
|
|
auto const encIsAscii = enc == 11; // RTL_TEXTENCODING_ASCII_US
|
|
auto const encIsUtf8 = enc == 76; // RTL_TEXTENCODING_UTF8
|
|
if (!compat::EvaluateAsInt(expr->getArg(3),
|
|
res, compiler.getASTContext())
|
|
|| res != 0x333) // OSTRING_TO_OUSTRING_CVTFLAGS
|
|
{
|
|
return true;
|
|
}
|
|
if (!encIsAscii && cont == ContentKind::Ascii) {
|
|
report(
|
|
DiagnosticsEngine::Warning,
|
|
("suspicious 'rtl::OUString' constructor with text"
|
|
" encoding %0 but plain ASCII content; use"
|
|
" 'RTL_TEXTENCODING_ASCII_US' instead"),
|
|
expr->getArg(2)->getExprLoc())
|
|
<< compat::toString(enc, 10) << expr->getSourceRange();
|
|
return true;
|
|
}
|
|
if (encIsUtf8) {
|
|
if (cont == ContentKind::Arbitrary) {
|
|
report(
|
|
DiagnosticsEngine::Warning,
|
|
("suspicious 'rtl::OUString' constructor with text"
|
|
" encoding 'RTL_TEXTENCODING_UTF8' but non-UTF-8"
|
|
" content"),
|
|
expr->getArg(0)->getExprLoc())
|
|
<< expr->getSourceRange();
|
|
} else {
|
|
assert(cont == ContentKind::Utf8);
|
|
//TODO: keep original content as much as possible
|
|
std::ostringstream s;
|
|
for (auto const c: utf8Cont) {
|
|
if (c == '\\') {
|
|
s << "\\\\";
|
|
} else if (c == '"') {
|
|
s << "\\\"";
|
|
} else if (c == '\a') {
|
|
s << "\\a";
|
|
} else if (c == '\b') {
|
|
s << "\\b";
|
|
} else if (c == '\f') {
|
|
s << "\\f";
|
|
} else if (c == '\n') {
|
|
s << "\\n";
|
|
} else if (c == '\r') {
|
|
s << "\\r";
|
|
} else if (c == '\t') {
|
|
s << "\\r";
|
|
} else if (c == '\v') {
|
|
s << "\\v";
|
|
} else if (c <= 0x1F || c == 0x7F) {
|
|
s << "\\x" << std::oct << std::setw(3)
|
|
<< std::setfill('0')
|
|
<< static_cast<std::uint_least32_t>(c);
|
|
} else if (c < 0x7F) {
|
|
s << char(c);
|
|
} else if (c <= 0xFFFF) {
|
|
s << "\\u" << std::hex << std::uppercase
|
|
<< std::setw(4) << std::setfill('0')
|
|
<< static_cast<std::uint_least32_t>(c);
|
|
} else {
|
|
assert(c <= 0x10FFFF);
|
|
s << "\\U" << std::hex << std::uppercase
|
|
<< std::setw(8) << std::setfill('0')
|
|
<< static_cast<std::uint_least32_t>(c);
|
|
}
|
|
}
|
|
report(
|
|
DiagnosticsEngine::Warning,
|
|
("simplify construction of %0 with UTF-8 content as"
|
|
" OUString(u\"%1\")"),
|
|
expr->getExprLoc())
|
|
<< classdecl << s.str() << expr->getSourceRange();
|
|
|
|
}
|
|
return true;
|
|
}
|
|
if (cont != ContentKind::Ascii || emb) {
|
|
// cf. remaining uses of RTL_CONSTASCII_USTRINGPARAM
|
|
return true;
|
|
}
|
|
kind = ChangeKind::Char;
|
|
pass = n == 0
|
|
? PassThrough::EmptyConstantString
|
|
: PassThrough::NonEmptyConstantString;
|
|
simplify = true;
|
|
break;
|
|
}
|
|
default:
|
|
return true;
|
|
}
|
|
if (!calls_.empty()) {
|
|
Expr const * call = calls_.top();
|
|
CallExpr::const_arg_iterator argsBeg;
|
|
CallExpr::const_arg_iterator argsEnd;
|
|
if (isa<CallExpr>(call)) {
|
|
argsBeg = cast<CallExpr>(call)->arg_begin();
|
|
argsEnd = cast<CallExpr>(call)->arg_end();
|
|
} else if (isa<CXXConstructExpr>(call)) {
|
|
argsBeg = cast<CXXConstructExpr>(call)->arg_begin();
|
|
argsEnd = cast<CXXConstructExpr>(call)->arg_end();
|
|
} else {
|
|
assert(false);
|
|
}
|
|
for (auto i(argsBeg); i != argsEnd; ++i) {
|
|
Expr const * e = (*i)->IgnoreParenImpCasts();
|
|
if (isa<MaterializeTemporaryExpr>(e)) {
|
|
e = cast<MaterializeTemporaryExpr>(e)->getSubExpr()
|
|
->IgnoreParenImpCasts();
|
|
}
|
|
if (isa<CXXFunctionalCastExpr>(e)) {
|
|
e = cast<CXXFunctionalCastExpr>(e)->getSubExpr()
|
|
->IgnoreParenImpCasts();
|
|
}
|
|
if (isa<CXXBindTemporaryExpr>(e)) {
|
|
e = cast<CXXBindTemporaryExpr>(e)->getSubExpr()
|
|
->IgnoreParenImpCasts();
|
|
}
|
|
if (e == expr) {
|
|
if (isa<CallExpr>(call)) {
|
|
FunctionDecl const * fdecl
|
|
= cast<CallExpr>(call)->getDirectCallee();
|
|
if (fdecl == nullptr) {
|
|
break;
|
|
}
|
|
loplugin::DeclCheck dc(fdecl);
|
|
if (pass == PassThrough::EmptyConstantString) {
|
|
if ((dc.Function("equals").Class("OUString")
|
|
.Namespace("rtl").GlobalNamespace())
|
|
|| (dc.Operator(OO_EqualEqual).Namespace("rtl")
|
|
.GlobalNamespace()))
|
|
{
|
|
report(
|
|
DiagnosticsEngine::Warning,
|
|
("rewrite call of '%0' with construction of"
|
|
" %1 with empty string constant argument"
|
|
" as call of 'rtl::OUString::isEmpty'"),
|
|
getMemberLocation(call))
|
|
<< fdecl->getQualifiedNameAsString()
|
|
<< classdecl << call->getSourceRange();
|
|
return true;
|
|
}
|
|
if (dc.Operator(OO_ExclaimEqual).Namespace("rtl")
|
|
.GlobalNamespace())
|
|
{
|
|
report(
|
|
DiagnosticsEngine::Warning,
|
|
("rewrite call of '%0' with construction of"
|
|
" %1 with empty string constant argument"
|
|
" as call of '!rtl::OUString::isEmpty'"),
|
|
getMemberLocation(call))
|
|
<< fdecl->getQualifiedNameAsString()
|
|
<< classdecl << call->getSourceRange();
|
|
return true;
|
|
}
|
|
if ((dc.Operator(OO_Plus).Namespace("rtl")
|
|
.GlobalNamespace())
|
|
|| (dc.Operator(OO_Plus).Class("OUString")
|
|
.Namespace("rtl").GlobalNamespace()))
|
|
{
|
|
report(
|
|
DiagnosticsEngine::Warning,
|
|
("call of '%0' with suspicious construction"
|
|
" of %1 with empty string constant"
|
|
" argument"),
|
|
getMemberLocation(call))
|
|
<< fdecl->getQualifiedNameAsString()
|
|
<< classdecl << call->getSourceRange();
|
|
return true;
|
|
}
|
|
if (dc.Operator(OO_Equal).Class("OUString")
|
|
.Namespace("rtl").GlobalNamespace())
|
|
{
|
|
report(
|
|
DiagnosticsEngine::Warning,
|
|
("rewrite call of '%0' with construction of"
|
|
" %1 with empty string constant argument"
|
|
" as call of 'rtl::OUString::clear'"),
|
|
getMemberLocation(call))
|
|
<< fdecl->getQualifiedNameAsString()
|
|
<< classdecl << call->getSourceRange();
|
|
return true;
|
|
}
|
|
} else {
|
|
assert(pass == PassThrough::NonEmptyConstantString);
|
|
if (dc.Function("equals").Class("OUString")
|
|
.Namespace("rtl").GlobalNamespace())
|
|
{
|
|
report(
|
|
DiagnosticsEngine::Warning,
|
|
("rewrite call of '%0' with construction of"
|
|
" %1 with %2 as 'operator =='"),
|
|
getMemberLocation(call))
|
|
<< fdecl->getQualifiedNameAsString()
|
|
<< classdecl << describeChangeKind(kind)
|
|
<< call->getSourceRange();
|
|
return true;
|
|
}
|
|
if ((dc.Operator(OO_Plus).Namespace("rtl")
|
|
.GlobalNamespace())
|
|
|| (dc.Operator(OO_Plus).Class("OUString")
|
|
.Namespace("rtl").GlobalNamespace())
|
|
|| (dc.Operator(OO_EqualEqual).Namespace("rtl")
|
|
.GlobalNamespace())
|
|
|| (dc.Operator(OO_ExclaimEqual)
|
|
.Namespace("rtl").GlobalNamespace()))
|
|
{
|
|
if (dc.Operator(OO_Plus).Namespace("rtl")
|
|
.GlobalNamespace())
|
|
{
|
|
auto file = getFilenameOfLocation(
|
|
compiler.getSourceManager()
|
|
.getSpellingLoc(
|
|
expr->getBeginLoc()));
|
|
if (loplugin::isSamePathname(
|
|
file,
|
|
(SRCDIR
|
|
"/sal/qa/rtl/strings/test_ostring_concat.cxx"))
|
|
|| loplugin::isSamePathname(
|
|
file,
|
|
(SRCDIR
|
|
"/sal/qa/rtl/strings/test_oustring_concat.cxx")))
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
auto loc = expr->getArg(0)->getBeginLoc();
|
|
while (compiler.getSourceManager()
|
|
.isMacroArgExpansion(loc))
|
|
{
|
|
loc = compiler.getSourceManager()
|
|
.getImmediateMacroCallerLoc(loc);
|
|
}
|
|
if (kind == ChangeKind::SingleChar) {
|
|
report(
|
|
DiagnosticsEngine::Warning,
|
|
("rewrite construction of %0 with %1 in"
|
|
" call of '%2' as construction of"
|
|
" 'OUStringChar'"),
|
|
getMemberLocation(expr))
|
|
<< classdecl << describeChangeKind(kind)
|
|
<< fdecl->getQualifiedNameAsString()
|
|
<< expr->getSourceRange();
|
|
} else {
|
|
report(
|
|
DiagnosticsEngine::Warning,
|
|
("elide construction of %0 with %1 in"
|
|
" call of '%2'"),
|
|
getMemberLocation(expr))
|
|
<< classdecl << describeChangeKind(kind)
|
|
<< fdecl->getQualifiedNameAsString()
|
|
<< expr->getSourceRange();
|
|
}
|
|
return true;
|
|
}
|
|
}
|
|
} else if (isa<CXXConstructExpr>(call)) {
|
|
} else {
|
|
assert(false);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if (simplify) {
|
|
report(
|
|
DiagnosticsEngine::Warning,
|
|
"simplify construction of %0 with %1", expr->getExprLoc())
|
|
<< classdecl << describeChangeKind(kind)
|
|
<< expr->getSourceRange();
|
|
}
|
|
return true;
|
|
}
|
|
|
|
auto consDecl = expr->getConstructor();
|
|
for (unsigned i = 0; i != consDecl->getNumParams(); ++i) {
|
|
auto t = consDecl->getParamDecl(i)->getType();
|
|
if (loplugin::TypeCheck(t).NotSubstTemplateTypeParmType()
|
|
.LvalueReference().Const().NotSubstTemplateTypeParmType()
|
|
.Class("OUString").Namespace("rtl").GlobalNamespace())
|
|
{
|
|
auto argExpr = expr->getArg(i);
|
|
if (argExpr && i <= consDecl->getNumParams())
|
|
{
|
|
if (!hasOverloads(consDecl, expr->getNumArgs()))
|
|
{
|
|
handleOUStringCtor(expr, argExpr, consDecl, true);
|
|
}
|
|
}
|
|
}
|
|
if (loplugin::TypeCheck(t).NotSubstTemplateTypeParmType()
|
|
.LvalueReference().Const().NotSubstTemplateTypeParmType()
|
|
.Class("OString").Namespace("rtl").GlobalNamespace())
|
|
{
|
|
auto argExpr = expr->getArg(i);
|
|
if (argExpr && i <= consDecl->getNumParams())
|
|
{
|
|
if (!hasOverloads(consDecl, expr->getNumArgs()))
|
|
{
|
|
handleOStringCtor(expr, argExpr, consDecl, true);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool StringConstant::VisitReturnStmt(ReturnStmt const * stmt) {
|
|
if (ignoreLocation(stmt)) {
|
|
return true;
|
|
}
|
|
auto const e1 = stmt->getRetValue();
|
|
if (e1 == nullptr) {
|
|
return true;
|
|
}
|
|
auto const tc1 = loplugin::TypeCheck(e1->getType().getTypePtr());
|
|
if (!(tc1.Class("OString").Namespace("rtl").GlobalNamespace()
|
|
|| tc1.Class("OUString").Namespace("rtl").GlobalNamespace()))
|
|
{
|
|
return true;
|
|
}
|
|
assert(!returnTypes_.empty());
|
|
auto const tc2 = loplugin::TypeCheck(returnTypes_.top().getTypePtr());
|
|
if (!(tc2.Class("OString").Namespace("rtl").GlobalNamespace()
|
|
|| tc2.Class("OUString").Namespace("rtl").GlobalNamespace()))
|
|
{
|
|
return true;
|
|
}
|
|
auto const e2 = dyn_cast<CXXFunctionalCastExpr>(e1->IgnoreImplicit());
|
|
if (e2 == nullptr) {
|
|
return true;
|
|
}
|
|
auto const e3 = dyn_cast<CXXBindTemporaryExpr>(e2->getSubExpr());
|
|
if (e3 == nullptr) {
|
|
return true;
|
|
}
|
|
auto const e4 = dyn_cast<CXXConstructExpr>(e3->getSubExpr());
|
|
if (e4 == nullptr) {
|
|
return true;
|
|
}
|
|
if (e4->getNumArgs() != 2) {
|
|
return true;
|
|
}
|
|
auto const t = e4->getArg(0)->getType();
|
|
if (!(t.isConstQualified() && t->isConstantArrayType())) {
|
|
return true;
|
|
}
|
|
auto const e5 = e4->getArg(1);
|
|
if (!(isa<CXXDefaultArgExpr>(e5)
|
|
&& (loplugin::TypeCheck(e5->getType()).Struct("Dummy").Namespace("libreoffice_internal")
|
|
.Namespace("rtl").GlobalNamespace())))
|
|
{
|
|
return true;
|
|
}
|
|
report(DiagnosticsEngine::Warning, "elide constructor call", e1->getBeginLoc())
|
|
<< e1->getSourceRange();
|
|
return true;
|
|
}
|
|
|
|
std::string StringConstant::describeChangeKind(ChangeKind kind) {
|
|
switch (kind) {
|
|
case ChangeKind::Char:
|
|
return "string constant argument";
|
|
case ChangeKind::CharLen:
|
|
return "string constant and matching length arguments";
|
|
case ChangeKind::SingleChar:
|
|
return "sal_Unicode argument";
|
|
case ChangeKind::OUStringChar:
|
|
return "OUStringChar argument";
|
|
}
|
|
llvm_unreachable("unknown change kind");
|
|
}
|
|
|
|
bool StringConstant::isStringConstant(
|
|
Expr const * expr, unsigned * size, bool * nonArray, ContentKind * content,
|
|
bool * embeddedNuls, bool * terminatingNul,
|
|
std::vector<char32_t> * utf8Content)
|
|
{
|
|
assert(expr != nullptr);
|
|
assert(size != nullptr);
|
|
assert(nonArray != nullptr);
|
|
assert(content != nullptr);
|
|
assert(embeddedNuls != nullptr);
|
|
assert(terminatingNul != nullptr);
|
|
QualType t = expr->getType();
|
|
// Look inside RTL_CONSTASCII_STRINGPARAM:
|
|
if (loplugin::TypeCheck(t).Pointer().Const().Char()) {
|
|
auto e2 = dyn_cast<UnaryOperator>(expr);
|
|
if (e2 != nullptr && e2->getOpcode() == UO_AddrOf) {
|
|
auto e3 = dyn_cast<ArraySubscriptExpr>(
|
|
e2->getSubExpr()->IgnoreParenImpCasts());
|
|
if (e3 == nullptr || !isZero(e3->getIdx()->IgnoreParenImpCasts())) {
|
|
return false;
|
|
}
|
|
expr = e3->getBase()->IgnoreParenImpCasts();
|
|
t = expr->getType();
|
|
}
|
|
}
|
|
if (!t.isConstQualified()) {
|
|
return false;
|
|
}
|
|
DeclRefExpr const * dre = dyn_cast<DeclRefExpr>(expr);
|
|
if (dre != nullptr) {
|
|
VarDecl const * var = dyn_cast<VarDecl>(dre->getDecl());
|
|
if (var != nullptr) {
|
|
Expr const * init = var->getAnyInitializer();
|
|
if (init != nullptr) {
|
|
expr = init->IgnoreParenImpCasts();
|
|
}
|
|
}
|
|
}
|
|
bool isPtr;
|
|
if (loplugin::TypeCheck(t).Pointer().Const().Char()) {
|
|
isPtr = true;
|
|
} else if (t->isConstantArrayType()
|
|
&& (loplugin::TypeCheck(
|
|
t->getAsArrayTypeUnsafe()->getElementType())
|
|
.Char()))
|
|
{
|
|
isPtr = false;
|
|
} else {
|
|
return false;
|
|
}
|
|
clang::StringLiteral const * lit = dyn_cast<clang::StringLiteral>(expr);
|
|
if (lit != nullptr) {
|
|
if (!(compat::isOrdinary(lit) || lit->isUTF8())) {
|
|
return false;
|
|
}
|
|
unsigned n = lit->getLength();
|
|
ContentKind cont = ContentKind::Ascii;
|
|
bool emb = false;
|
|
char32_t val = 0;
|
|
enum class Utf8State { Start, E0, EB, F0, F4, Trail1, Trail2, Trail3 };
|
|
Utf8State s = Utf8State::Start;
|
|
StringRef str = lit->getString();
|
|
for (unsigned i = 0; i != n; ++i) {
|
|
auto const c = static_cast<unsigned char>(str[i]);
|
|
if (c == '\0') {
|
|
emb = true;
|
|
}
|
|
switch (s) {
|
|
case Utf8State::Start:
|
|
if (c >= 0x80) {
|
|
if (c >= 0xC2 && c <= 0xDF) {
|
|
val = c & 0x1F;
|
|
s = Utf8State::Trail1;
|
|
} else if (c == 0xE0) {
|
|
val = c & 0x0F;
|
|
s = Utf8State::E0;
|
|
} else if ((c >= 0xE1 && c <= 0xEA)
|
|
|| (c >= 0xEE && c <= 0xEF))
|
|
{
|
|
val = c & 0x0F;
|
|
s = Utf8State::Trail2;
|
|
} else if (c == 0xEB) {
|
|
val = c & 0x0F;
|
|
s = Utf8State::EB;
|
|
} else if (c == 0xF0) {
|
|
val = c & 0x03;
|
|
s = Utf8State::F0;
|
|
} else if (c >= 0xF1 && c <= 0xF3) {
|
|
val = c & 0x03;
|
|
s = Utf8State::Trail3;
|
|
} else if (c == 0xF4) {
|
|
val = c & 0x03;
|
|
s = Utf8State::F4;
|
|
} else {
|
|
cont = ContentKind::Arbitrary;
|
|
}
|
|
} else if (utf8Content != nullptr
|
|
&& cont != ContentKind::Arbitrary)
|
|
{
|
|
utf8Content->push_back(c);
|
|
}
|
|
break;
|
|
case Utf8State::E0:
|
|
if (c >= 0xA0 && c <= 0xBF) {
|
|
val = (val << 6) | (c & 0x3F);
|
|
s = Utf8State::Trail1;
|
|
} else {
|
|
cont = ContentKind::Arbitrary;
|
|
s = Utf8State::Start;
|
|
}
|
|
break;
|
|
case Utf8State::EB:
|
|
if (c >= 0x80 && c <= 0x9F) {
|
|
val = (val << 6) | (c & 0x3F);
|
|
s = Utf8State::Trail1;
|
|
} else {
|
|
cont = ContentKind::Arbitrary;
|
|
s = Utf8State::Start;
|
|
}
|
|
break;
|
|
case Utf8State::F0:
|
|
if (c >= 0x90 && c <= 0xBF) {
|
|
val = (val << 6) | (c & 0x3F);
|
|
s = Utf8State::Trail2;
|
|
} else {
|
|
cont = ContentKind::Arbitrary;
|
|
s = Utf8State::Start;
|
|
}
|
|
break;
|
|
case Utf8State::F4:
|
|
if (c >= 0x80 && c <= 0x8F) {
|
|
val = (val << 6) | (c & 0x3F);
|
|
s = Utf8State::Trail2;
|
|
} else {
|
|
cont = ContentKind::Arbitrary;
|
|
s = Utf8State::Start;
|
|
}
|
|
break;
|
|
case Utf8State::Trail1:
|
|
if (c >= 0x80 && c <= 0xBF) {
|
|
cont = ContentKind::Utf8;
|
|
if (utf8Content != nullptr)
|
|
{
|
|
utf8Content->push_back((val << 6) | (c & 0x3F));
|
|
val = 0;
|
|
}
|
|
} else {
|
|
cont = ContentKind::Arbitrary;
|
|
}
|
|
s = Utf8State::Start;
|
|
break;
|
|
case Utf8State::Trail2:
|
|
if (c >= 0x80 && c <= 0xBF) {
|
|
val = (val << 6) | (c & 0x3F);
|
|
s = Utf8State::Trail1;
|
|
} else {
|
|
cont = ContentKind::Arbitrary;
|
|
s = Utf8State::Start;
|
|
}
|
|
break;
|
|
case Utf8State::Trail3:
|
|
if (c >= 0x80 && c <= 0xBF) {
|
|
val = (val << 6) | (c & 0x3F);
|
|
s = Utf8State::Trail2;
|
|
} else {
|
|
cont = ContentKind::Arbitrary;
|
|
s = Utf8State::Start;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
*size = n;
|
|
*nonArray = isPtr;
|
|
*content = cont;
|
|
*embeddedNuls = emb;
|
|
*terminatingNul = true;
|
|
return true;
|
|
}
|
|
APValue v;
|
|
if (!expr->isCXX11ConstantExpr(compiler.getASTContext(), &v)) {
|
|
return false;
|
|
}
|
|
switch (v.getKind()) {
|
|
case APValue::LValue:
|
|
{
|
|
Expr const * e = v.getLValueBase().dyn_cast<Expr const *>();
|
|
if (e == nullptr) {
|
|
return false;
|
|
}
|
|
if (!v.getLValueOffset().isZero()) {
|
|
return false; //TODO
|
|
}
|
|
Expr const * e2 = e->IgnoreParenImpCasts();
|
|
if (e2 != e) {
|
|
return isStringConstant(
|
|
e2, size, nonArray, content, embeddedNuls, terminatingNul);
|
|
}
|
|
//TODO: string literals are represented as recursive LValues???
|
|
llvm::APInt n
|
|
= compiler.getASTContext().getAsConstantArrayType(t)->getSize();
|
|
assert(n != 0);
|
|
--n;
|
|
assert(n.ule(std::numeric_limits<unsigned>::max()));
|
|
*size = static_cast<unsigned>(n.getLimitedValue());
|
|
*nonArray = isPtr || *nonArray;
|
|
*content = ContentKind::Ascii; //TODO
|
|
*embeddedNuls = false; //TODO
|
|
*terminatingNul = true;
|
|
return true;
|
|
}
|
|
case APValue::Array:
|
|
{
|
|
if (v.hasArrayFiller()) { //TODO: handle final NULL filler?
|
|
return false;
|
|
}
|
|
unsigned n = v.getArraySize();
|
|
assert(n != 0);
|
|
ContentKind cont = ContentKind::Ascii;
|
|
bool emb = false;
|
|
//TODO: check for ContentType::Utf8
|
|
for (unsigned i = 0; i != n - 1; ++i) {
|
|
APValue e(v.getArrayInitializedElt(i));
|
|
if (!e.isInt()) { //TODO: assert?
|
|
return false;
|
|
}
|
|
APSInt iv = e.getInt();
|
|
if (iv == 0) {
|
|
emb = true;
|
|
} else if (iv.uge(0x80)) {
|
|
cont = ContentKind::Arbitrary;
|
|
}
|
|
}
|
|
APValue e(v.getArrayInitializedElt(n - 1));
|
|
if (!e.isInt()) { //TODO: assert?
|
|
return false;
|
|
}
|
|
bool trm = e.getInt() == 0;
|
|
*size = trm ? n - 1 : n;
|
|
*nonArray = isPtr;
|
|
*content = cont;
|
|
*embeddedNuls = emb;
|
|
*terminatingNul = trm;
|
|
return true;
|
|
}
|
|
default:
|
|
assert(false); //TODO???
|
|
return false;
|
|
}
|
|
}
|
|
|
|
bool StringConstant::isZero(Expr const * expr) {
|
|
APSInt res;
|
|
return compat::EvaluateAsInt(expr, res, compiler.getASTContext()) && res == 0;
|
|
}
|
|
|
|
void StringConstant::reportChange(
|
|
Expr const * expr, ChangeKind kind, std::string const & original,
|
|
std::string const & replacement, PassThrough pass, bool nonArray,
|
|
char const * rewriteFrom, char const * rewriteTo)
|
|
{
|
|
assert((rewriteFrom == nullptr) == (rewriteTo == nullptr));
|
|
if (pass != PassThrough::No && !calls_.empty()) {
|
|
Expr const * call = calls_.top();
|
|
CallExpr::const_arg_iterator argsBeg;
|
|
CallExpr::const_arg_iterator argsEnd;
|
|
if (isa<CallExpr>(call)) {
|
|
argsBeg = cast<CallExpr>(call)->arg_begin();
|
|
argsEnd = cast<CallExpr>(call)->arg_end();
|
|
} else if (isa<CXXConstructExpr>(call)) {
|
|
argsBeg = cast<CXXConstructExpr>(call)->arg_begin();
|
|
argsEnd = cast<CXXConstructExpr>(call)->arg_end();
|
|
} else {
|
|
assert(false);
|
|
}
|
|
for (auto i(argsBeg); i != argsEnd; ++i) {
|
|
Expr const * e = (*i)->IgnoreParenImpCasts();
|
|
if (isa<CXXBindTemporaryExpr>(e)) {
|
|
e = cast<CXXBindTemporaryExpr>(e)->getSubExpr()
|
|
->IgnoreParenImpCasts();
|
|
}
|
|
if (e == expr) {
|
|
if (isa<CallExpr>(call)) {
|
|
FunctionDecl const * fdecl
|
|
= cast<CallExpr>(call)->getDirectCallee();
|
|
if (fdecl == nullptr) {
|
|
break;
|
|
}
|
|
loplugin::DeclCheck dc(fdecl);
|
|
if (pass == PassThrough::EmptyConstantString) {
|
|
if ((dc.Function("equals").Class("OUString")
|
|
.Namespace("rtl").GlobalNamespace())
|
|
|| (dc.Operator(OO_EqualEqual).Namespace("rtl")
|
|
.GlobalNamespace()))
|
|
{
|
|
report(
|
|
DiagnosticsEngine::Warning,
|
|
("rewrite call of '%0' with call of %1 with"
|
|
" empty string constant argument as call of"
|
|
" 'rtl::OUString::isEmpty'"),
|
|
getMemberLocation(call))
|
|
<< fdecl->getQualifiedNameAsString() << original
|
|
<< call->getSourceRange();
|
|
return;
|
|
}
|
|
if (dc.Operator(OO_ExclaimEqual).Namespace("rtl")
|
|
.GlobalNamespace())
|
|
{
|
|
report(
|
|
DiagnosticsEngine::Warning,
|
|
("rewrite call of '%0' with call of %1 with"
|
|
" empty string constant argument as call of"
|
|
" '!rtl::OUString::isEmpty'"),
|
|
getMemberLocation(call))
|
|
<< fdecl->getQualifiedNameAsString() << original
|
|
<< call->getSourceRange();
|
|
return;
|
|
}
|
|
if ((dc.Operator(OO_Plus).Namespace("rtl")
|
|
.GlobalNamespace())
|
|
|| (dc.Operator(OO_Plus).Class("OUString")
|
|
.Namespace("rtl").GlobalNamespace()))
|
|
{
|
|
report(
|
|
DiagnosticsEngine::Warning,
|
|
("call of '%0' with suspicious call of %1 with"
|
|
" empty string constant argument"),
|
|
getMemberLocation(call))
|
|
<< fdecl->getQualifiedNameAsString() << original
|
|
<< call->getSourceRange();
|
|
return;
|
|
}
|
|
if (dc.Operator(OO_Equal).Class("OUString")
|
|
.Namespace("rtl").GlobalNamespace())
|
|
{
|
|
report(
|
|
DiagnosticsEngine::Warning,
|
|
("rewrite call of '%0' with call of %1 with"
|
|
" empty string constant argument as call of"
|
|
" rtl::OUString::call"),
|
|
getMemberLocation(call))
|
|
<< fdecl->getQualifiedNameAsString() << original
|
|
<< call->getSourceRange();
|
|
return;
|
|
}
|
|
report(
|
|
DiagnosticsEngine::Warning,
|
|
"TODO call inside %0", getMemberLocation(expr))
|
|
<< fdecl->getQualifiedNameAsString()
|
|
<< expr->getSourceRange();
|
|
return;
|
|
} else {
|
|
assert(pass == PassThrough::NonEmptyConstantString);
|
|
if ((dc.Function("equals").Class("OUString")
|
|
.Namespace("rtl").GlobalNamespace())
|
|
|| (dc.Operator(OO_Equal).Class("OUString")
|
|
.Namespace("rtl").GlobalNamespace())
|
|
|| (dc.Operator(OO_EqualEqual).Namespace("rtl")
|
|
.GlobalNamespace())
|
|
|| (dc.Operator(OO_ExclaimEqual).Namespace("rtl")
|
|
.GlobalNamespace()))
|
|
{
|
|
report(
|
|
DiagnosticsEngine::Warning,
|
|
"elide call of %0 with %1 in call of '%2'",
|
|
getMemberLocation(expr))
|
|
<< original << describeChangeKind(kind)
|
|
<< fdecl->getQualifiedNameAsString()
|
|
<< expr->getSourceRange();
|
|
return;
|
|
}
|
|
report(
|
|
DiagnosticsEngine::Warning,
|
|
("rewrite call of %0 with %1 in call of '%2' as"
|
|
" (implicit) construction of 'OUString'"),
|
|
getMemberLocation(expr))
|
|
<< original << describeChangeKind(kind)
|
|
<< fdecl->getQualifiedNameAsString()
|
|
<< expr->getSourceRange();
|
|
return;
|
|
}
|
|
} else if (isa<CXXConstructExpr>(call)) {
|
|
auto classdecl = cast<CXXConstructExpr>(call)
|
|
->getConstructor()->getParent();
|
|
loplugin::DeclCheck dc(classdecl);
|
|
if (dc.Class("OUString").Namespace("rtl").GlobalNamespace()
|
|
|| (dc.Class("OUStringBuffer").Namespace("rtl")
|
|
.GlobalNamespace()))
|
|
{
|
|
//TODO: propagate further out?
|
|
if (pass == PassThrough::EmptyConstantString) {
|
|
report(
|
|
DiagnosticsEngine::Warning,
|
|
("rewrite construction of %0 with call of %1"
|
|
" with empty string constant argument as"
|
|
" default construction of %0"),
|
|
getMemberLocation(call))
|
|
<< classdecl << original
|
|
<< call->getSourceRange();
|
|
} else {
|
|
assert(pass == PassThrough::NonEmptyConstantString);
|
|
report(
|
|
DiagnosticsEngine::Warning,
|
|
("elide call of %0 with %1 in construction of"
|
|
" %2"),
|
|
getMemberLocation(expr))
|
|
<< original << describeChangeKind(kind)
|
|
<< classdecl << expr->getSourceRange();
|
|
}
|
|
return;
|
|
}
|
|
} else {
|
|
assert(false);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if (rewriter != nullptr && !nonArray && rewriteFrom != nullptr) {
|
|
SourceLocation loc = getMemberLocation(expr);
|
|
while (compiler.getSourceManager().isMacroArgExpansion(loc)) {
|
|
loc = compiler.getSourceManager().getImmediateMacroCallerLoc(loc);
|
|
}
|
|
if (compiler.getSourceManager().isMacroBodyExpansion(loc)) {
|
|
loc = compiler.getSourceManager().getSpellingLoc(loc);
|
|
}
|
|
unsigned n = Lexer::MeasureTokenLength(
|
|
loc, compiler.getSourceManager(), compiler.getLangOpts());
|
|
if ((std::string(compiler.getSourceManager().getCharacterData(loc), n)
|
|
== rewriteFrom)
|
|
&& replaceText(loc, n, rewriteTo))
|
|
{
|
|
return;
|
|
}
|
|
}
|
|
report(
|
|
DiagnosticsEngine::Warning,
|
|
"rewrite call of '%0' with %1 as call of '%2'%3",
|
|
getMemberLocation(expr))
|
|
<< original << describeChangeKind(kind) << replacement
|
|
<< adviseNonArray(nonArray) << expr->getSourceRange();
|
|
}
|
|
|
|
void StringConstant::checkEmpty(
|
|
CallExpr const * expr, FunctionDecl const * callee, TreatEmpty treatEmpty,
|
|
unsigned size, std::string * replacement)
|
|
{
|
|
assert(replacement != nullptr);
|
|
if (size == 0) {
|
|
switch (treatEmpty) {
|
|
case TreatEmpty::DefaultCtor:
|
|
*replacement = "rtl::OUString default constructor";
|
|
break;
|
|
case TreatEmpty::CheckEmpty:
|
|
*replacement = "rtl::OUString::isEmpty";
|
|
break;
|
|
case TreatEmpty::Error:
|
|
report(
|
|
DiagnosticsEngine::Warning,
|
|
"call of '%0' with suspicious empty string constant argument",
|
|
getMemberLocation(expr))
|
|
<< callee->getQualifiedNameAsString() << expr->getSourceRange();
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
void StringConstant::handleChar(
|
|
CallExpr const * expr, unsigned arg, FunctionDecl const * callee,
|
|
std::string const & replacement, TreatEmpty treatEmpty, bool literal,
|
|
char const * rewriteFrom, char const * rewriteTo)
|
|
{
|
|
unsigned n;
|
|
bool nonArray;
|
|
ContentKind cont;
|
|
bool emb;
|
|
bool trm;
|
|
if (!isStringConstant(
|
|
expr->getArg(arg)->IgnoreParenImpCasts(), &n, &nonArray, &cont,
|
|
&emb, &trm))
|
|
{
|
|
return;
|
|
}
|
|
if (cont != ContentKind::Ascii) {
|
|
report(
|
|
DiagnosticsEngine::Warning,
|
|
("call of '%0' with string constant argument containing non-ASCII"
|
|
" characters"),
|
|
getMemberLocation(expr))
|
|
<< callee->getQualifiedNameAsString() << expr->getSourceRange();
|
|
return;
|
|
}
|
|
if (emb) {
|
|
report(
|
|
DiagnosticsEngine::Warning,
|
|
("call of '%0' with string constant argument containing embedded"
|
|
" NULLs"),
|
|
getMemberLocation(expr))
|
|
<< callee->getQualifiedNameAsString() << expr->getSourceRange();
|
|
return;
|
|
}
|
|
if (!trm) {
|
|
report(
|
|
DiagnosticsEngine::Warning,
|
|
("call of '%0' with string constant argument lacking a terminating"
|
|
" NULL"),
|
|
getMemberLocation(expr))
|
|
<< callee->getQualifiedNameAsString() << expr->getSourceRange();
|
|
return;
|
|
}
|
|
std::string repl(replacement);
|
|
checkEmpty(expr, callee, treatEmpty, n, &repl);
|
|
if (!repl.empty()) {
|
|
reportChange(
|
|
expr, ChangeKind::Char, callee->getQualifiedNameAsString(), repl,
|
|
(literal
|
|
? (n == 0
|
|
? PassThrough::EmptyConstantString
|
|
: PassThrough::NonEmptyConstantString)
|
|
: PassThrough::No),
|
|
nonArray, rewriteFrom, rewriteTo);
|
|
}
|
|
}
|
|
|
|
void StringConstant::handleCharLen(
|
|
CallExpr const * expr, unsigned arg1, unsigned arg2,
|
|
FunctionDecl const * callee, std::string const & replacement,
|
|
TreatEmpty treatEmpty)
|
|
{
|
|
// Especially for f(RTL_CONSTASCII_STRINGPARAM("foo")), where
|
|
// RTL_CONSTASCII_STRINGPARAM expands to complicated expressions involving
|
|
// (&(X)[0] sub-expressions (and it might or might not be better to handle
|
|
// that at the level of non-expanded macros instead, but I have not found
|
|
// out how to do that yet anyway):
|
|
unsigned n;
|
|
bool nonArray;
|
|
ContentKind cont;
|
|
bool emb;
|
|
bool trm;
|
|
if (!(isStringConstant(
|
|
expr->getArg(arg1)->IgnoreParenImpCasts(), &n, &nonArray, &cont,
|
|
&emb, &trm)
|
|
&& trm))
|
|
{
|
|
return;
|
|
}
|
|
APSInt res;
|
|
if (compat::EvaluateAsInt(expr->getArg(arg2), res, compiler.getASTContext())) {
|
|
if (res != n) {
|
|
return;
|
|
}
|
|
} else {
|
|
UnaryOperator const * op = dyn_cast<UnaryOperator>(
|
|
expr->getArg(arg1)->IgnoreParenImpCasts());
|
|
if (op == nullptr || op->getOpcode() != UO_AddrOf) {
|
|
return;
|
|
}
|
|
ArraySubscriptExpr const * subs = dyn_cast<ArraySubscriptExpr>(
|
|
op->getSubExpr()->IgnoreParenImpCasts());
|
|
if (subs == nullptr) {
|
|
return;
|
|
}
|
|
unsigned n2;
|
|
bool nonArray2;
|
|
ContentKind cont2;
|
|
bool emb2;
|
|
bool trm2;
|
|
if (!(isStringConstant(
|
|
subs->getBase()->IgnoreParenImpCasts(), &n2, &nonArray2,
|
|
&cont2, &emb2, &trm2)
|
|
&& n2 == n && cont2 == cont && emb2 == emb && trm2 == trm
|
|
//TODO: same strings
|
|
&& compat::EvaluateAsInt(subs->getIdx(), res, compiler.getASTContext())
|
|
&& res == 0))
|
|
{
|
|
return;
|
|
}
|
|
}
|
|
if (cont != ContentKind::Ascii) {
|
|
report(
|
|
DiagnosticsEngine::Warning,
|
|
("call of '%0' with string constant argument containing non-ASCII"
|
|
" characters"),
|
|
getMemberLocation(expr))
|
|
<< callee->getQualifiedNameAsString() << expr->getSourceRange();
|
|
}
|
|
if (emb) {
|
|
return;
|
|
}
|
|
std::string repl(replacement);
|
|
checkEmpty(expr, callee, treatEmpty, n, &repl);
|
|
reportChange(
|
|
expr, ChangeKind::CharLen, callee->getQualifiedNameAsString(), repl,
|
|
PassThrough::No, nonArray, nullptr, nullptr);
|
|
}
|
|
|
|
void StringConstant::handleOUStringCtor(
|
|
CallExpr const * expr, unsigned arg, FunctionDecl const * callee,
|
|
bool explicitFunctionalCastNotation)
|
|
{
|
|
handleOUStringCtor(expr, expr->getArg(arg), callee, explicitFunctionalCastNotation);
|
|
}
|
|
|
|
void StringConstant::handleOStringCtor(
|
|
CallExpr const * expr, unsigned arg, FunctionDecl const * callee,
|
|
bool explicitFunctionalCastNotation)
|
|
{
|
|
handleOStringCtor(expr, expr->getArg(arg), callee, explicitFunctionalCastNotation);
|
|
}
|
|
|
|
void StringConstant::handleOUStringCtor(
|
|
Expr const * expr, Expr const * argExpr, FunctionDecl const * callee,
|
|
bool explicitFunctionalCastNotation)
|
|
{
|
|
handleStringCtor(expr, argExpr, callee, explicitFunctionalCastNotation, StringKind::Unicode);
|
|
}
|
|
|
|
void StringConstant::handleOStringCtor(
|
|
Expr const * expr, Expr const * argExpr, FunctionDecl const * callee,
|
|
bool explicitFunctionalCastNotation)
|
|
{
|
|
handleStringCtor(expr, argExpr, callee, explicitFunctionalCastNotation, StringKind::Char);
|
|
}
|
|
|
|
void StringConstant::handleStringCtor(
|
|
Expr const * expr, Expr const * argExpr, FunctionDecl const * callee,
|
|
bool explicitFunctionalCastNotation, StringKind stringKind)
|
|
{
|
|
auto e0 = argExpr->IgnoreParenImpCasts();
|
|
if (auto const e1 = dyn_cast<CXXMemberCallExpr>(e0)) {
|
|
if (auto const e2 = dyn_cast<CXXConversionDecl>(e1->getMethodDecl())) {
|
|
if (loplugin::TypeCheck(e2->getConversionType()).ClassOrStruct("basic_string_view")
|
|
.StdNamespace())
|
|
{
|
|
e0 = e1->getImplicitObjectArgument()->IgnoreParenImpCasts();
|
|
}
|
|
}
|
|
}
|
|
auto e1 = dyn_cast<CXXFunctionalCastExpr>(e0);
|
|
if (e1 == nullptr) {
|
|
if (explicitFunctionalCastNotation) {
|
|
return;
|
|
}
|
|
} else {
|
|
e0 = e1->getSubExpr()->IgnoreParenImpCasts();
|
|
}
|
|
auto e2 = dyn_cast<CXXBindTemporaryExpr>(e0);
|
|
if (e2 == nullptr) {
|
|
return;
|
|
}
|
|
auto e3 = dyn_cast<CXXConstructExpr>(
|
|
e2->getSubExpr()->IgnoreParenImpCasts());
|
|
if (e3 == nullptr) {
|
|
return;
|
|
}
|
|
if (!loplugin::DeclCheck(e3->getConstructor()).MemberFunction()
|
|
.Class(stringKind == StringKind::Unicode ? "OUString" : "OString").Namespace("rtl").GlobalNamespace())
|
|
{
|
|
return;
|
|
}
|
|
if (e3->getNumArgs() == 0) {
|
|
report(
|
|
DiagnosticsEngine::Warning,
|
|
("in call of '%0', replace default-constructed 'OUString' with an"
|
|
" empty string literal"),
|
|
e3->getExprLoc())
|
|
<< callee->getQualifiedNameAsString() << expr->getSourceRange();
|
|
return;
|
|
}
|
|
if (e3->getNumArgs() == 1
|
|
&& e3->getConstructor()->getNumParams() == 1
|
|
&& (loplugin::TypeCheck(
|
|
e3->getConstructor()->getParamDecl(0)->getType())
|
|
.Typedef(stringKind == StringKind::Unicode ? "sal_Unicode" : "char").GlobalNamespace()))
|
|
{
|
|
// It may not be easy to rewrite OUString(c), esp. given there is no
|
|
// OUString ctor taking an OUStringChar arg, so don't warn there:
|
|
if (!explicitFunctionalCastNotation) {
|
|
report(
|
|
DiagnosticsEngine::Warning,
|
|
("in call of '%0', replace 'OUString' constructed from a"
|
|
" 'sal_Unicode' with an 'OUStringChar'"),
|
|
e3->getExprLoc())
|
|
<< callee->getQualifiedNameAsString() << expr->getSourceRange();
|
|
}
|
|
return;
|
|
}
|
|
if (e3->getNumArgs() != 2) {
|
|
return;
|
|
}
|
|
unsigned n;
|
|
bool nonArray;
|
|
ContentKind cont;
|
|
bool emb;
|
|
bool trm;
|
|
if (!isStringConstant(
|
|
e3->getArg(0)->IgnoreParenImpCasts(), &n, &nonArray, &cont, &emb,
|
|
&trm))
|
|
{
|
|
return;
|
|
}
|
|
//TODO: cont, emb, trm
|
|
if (rewriter != nullptr) {
|
|
auto loc1 = e3->getBeginLoc();
|
|
auto range = e3->getParenOrBraceRange();
|
|
if (loc1.isFileID() && range.getBegin().isFileID()
|
|
&& range.getEnd().isFileID())
|
|
{
|
|
auto loc2 = range.getBegin();
|
|
for (bool first = true;; first = false) {
|
|
unsigned n = Lexer::MeasureTokenLength(
|
|
loc2, compiler.getSourceManager(), compiler.getLangOpts());
|
|
if (!first) {
|
|
StringRef s(
|
|
compiler.getSourceManager().getCharacterData(loc2), n);
|
|
while (s.startswith("\\\n")) {
|
|
s = s.drop_front(2);
|
|
while (!s.empty()
|
|
&& (s.front() == ' ' || s.front() == '\t'
|
|
|| s.front() == '\n' || s.front() == '\v'
|
|
|| s.front() == '\f'))
|
|
{
|
|
s = s.drop_front(1);
|
|
}
|
|
}
|
|
if (!(s.empty() || s.startswith("/*") || s.startswith("//")
|
|
|| s == "\\"))
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
loc2 = loc2.getLocWithOffset(std::max<unsigned>(n, 1));
|
|
}
|
|
auto loc3 = range.getEnd();
|
|
for (;;) {
|
|
auto l = Lexer::GetBeginningOfToken(
|
|
loc3.getLocWithOffset(-1), compiler.getSourceManager(),
|
|
compiler.getLangOpts());
|
|
unsigned n = Lexer::MeasureTokenLength(
|
|
l, compiler.getSourceManager(), compiler.getLangOpts());
|
|
StringRef s(compiler.getSourceManager().getCharacterData(l), n);
|
|
while (s.startswith("\\\n")) {
|
|
s = s.drop_front(2);
|
|
while (!s.empty()
|
|
&& (s.front() == ' ' || s.front() == '\t'
|
|
|| s.front() == '\n' || s.front() == '\v'
|
|
|| s.front() == '\f'))
|
|
{
|
|
s = s.drop_front(1);
|
|
}
|
|
}
|
|
if (!(s.empty() || s.startswith("/*") || s.startswith("//")
|
|
|| s == "\\"))
|
|
{
|
|
break;
|
|
}
|
|
loc3 = l;
|
|
}
|
|
if (removeText(CharSourceRange(SourceRange(loc1, loc2), false))) {
|
|
if (removeText(SourceRange(loc3, range.getEnd()))) {
|
|
return;
|
|
}
|
|
report(DiagnosticsEngine::Fatal, "Corrupt rewrite", loc3)
|
|
<< expr->getSourceRange();
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
report(
|
|
DiagnosticsEngine::Warning,
|
|
("in call of '%0', replace 'OUString' constructed from a string literal"
|
|
" directly with the string literal"),
|
|
e3->getExprLoc())
|
|
<< callee->getQualifiedNameAsString() << expr->getSourceRange();
|
|
}
|
|
|
|
void StringConstant::handleFunArgOstring(
|
|
CallExpr const * expr, unsigned arg, FunctionDecl const * callee)
|
|
{
|
|
auto argExpr = expr->getArg(arg)->IgnoreParenImpCasts();
|
|
unsigned n;
|
|
bool nonArray;
|
|
ContentKind cont;
|
|
bool emb;
|
|
bool trm;
|
|
if (isStringConstant(argExpr, &n, &nonArray, &cont, &emb, &trm)) {
|
|
if (cont != ContentKind::Ascii || emb) {
|
|
return;
|
|
}
|
|
if (!trm) {
|
|
report(
|
|
DiagnosticsEngine::Warning,
|
|
("call of '%0' with string constant argument lacking a"
|
|
" terminating NULL"),
|
|
getMemberLocation(expr))
|
|
<< callee->getQualifiedNameAsString() << expr->getSourceRange();
|
|
return;
|
|
}
|
|
std::string repl;
|
|
checkEmpty(expr, callee, TreatEmpty::Error, n, &repl);
|
|
if (nonArray) {
|
|
report(
|
|
DiagnosticsEngine::Warning,
|
|
("in call of '%0' with non-array string constant argument,"
|
|
" turn the non-array string constant into an array"),
|
|
getMemberLocation(expr))
|
|
<< callee->getQualifiedNameAsString() << expr->getSourceRange();
|
|
}
|
|
} else if (auto cexpr = lookForCXXConstructExpr(argExpr)) {
|
|
auto classdecl = cexpr->getConstructor()->getParent();
|
|
if (loplugin::DeclCheck(classdecl).Class("OString").Namespace("rtl")
|
|
.GlobalNamespace())
|
|
{
|
|
switch (cexpr->getConstructor()->getNumParams()) {
|
|
case 0:
|
|
report(
|
|
DiagnosticsEngine::Warning,
|
|
("in call of '%0', replace empty %1 constructor with empty"
|
|
" string literal"),
|
|
cexpr->getLocation())
|
|
<< callee->getQualifiedNameAsString() << classdecl
|
|
<< expr->getSourceRange();
|
|
break;
|
|
case 2:
|
|
if (isStringConstant(
|
|
cexpr->getArg(0)->IgnoreParenImpCasts(), &n, &nonArray,
|
|
&cont, &emb, &trm))
|
|
{
|
|
APSInt res;
|
|
if (compat::EvaluateAsInt(cexpr->getArg(1),
|
|
res, compiler.getASTContext()))
|
|
{
|
|
if (res == n && !emb && trm) {
|
|
report(
|
|
DiagnosticsEngine::Warning,
|
|
("in call of '%0', elide explicit %1"
|
|
" constructor%2"),
|
|
cexpr->getLocation())
|
|
<< callee->getQualifiedNameAsString()
|
|
<< classdecl << adviseNonArray(nonArray)
|
|
<< expr->getSourceRange();
|
|
}
|
|
} else {
|
|
if (emb) {
|
|
report(
|
|
DiagnosticsEngine::Warning,
|
|
("call of %0 constructor with string constant"
|
|
" argument containing embedded NULLs"),
|
|
cexpr->getLocation())
|
|
<< classdecl << cexpr->getSourceRange();
|
|
return;
|
|
}
|
|
if (!trm) {
|
|
report(
|
|
DiagnosticsEngine::Warning,
|
|
("call of %0 constructor with string constant"
|
|
" argument lacking a terminating NULL"),
|
|
cexpr->getLocation())
|
|
<< classdecl << cexpr->getSourceRange();
|
|
return;
|
|
}
|
|
report(
|
|
DiagnosticsEngine::Warning,
|
|
"in call of '%0', elide explicit %1 constructor%2",
|
|
cexpr->getLocation())
|
|
<< callee->getQualifiedNameAsString() << classdecl
|
|
<< adviseNonArray(nonArray)
|
|
<< expr->getSourceRange();
|
|
}
|
|
}
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
loplugin::Plugin::Registration< StringConstant > X("stringconstant", true);
|
|
|
|
}
|
|
|
|
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
|