office-gobmx/compilerplugins/clang/stringconstant.cxx
Stephan Bergmann 5d80385fd1 loplugin:stringconstant: Catch some O[U]String::getStr anti-patterns
Change-Id: I36bc86fcffc3c10fe44e60d779c9aa48eeed00f2
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/154749
Tested-by: Jenkins
Reviewed-by: Stephan Bergmann <sbergman@redhat.com>
2023-07-31 23:24:52 +02:00

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: */