office-gobmx/compilerplugins/clang/stringconstant.cxx
Stephan Bergmann 2db16f4bdc -Werror=return-type
Change-Id: I7691fa50d827b688cab299c85c933adabb29994c
2016-02-26 14:34:30 +01:00

1412 lines
52 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 <cstdlib>
#include <limits>
#include <stack>
#include <string>
#include <iostream>
#include "compat.hxx"
#include "plugin.hxx"
#include "typecheck.hxx"
// Define a "string constant" to be a constant expression either of type "array
// of N char" where each array element is a non-NUL ASCII character---except
// that the last array element may be NUL, or, in some situations, of type char
// with a ASCII value (including NUL). 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 = compat::begin(res); d != compat::end(res); ++d) {
FunctionDecl const * f = dyn_cast<FunctionDecl>(*d);
if (f != nullptr && f->getMinRequiredArguments() <= arguments
&& f->getNumParams() >= arguments)
{
++n;
if (n == 2) {
return true;
}
}
}
return false;
}
class StringConstant:
public RecursiveASTVisitor<StringConstant>, public loplugin::RewritePlugin
{
public:
explicit StringConstant(InstantiationData const & data): RewritePlugin(data)
{}
void run() override;
bool TraverseCallExpr(CallExpr * expr);
bool TraverseCXXMemberCallExpr(CXXMemberCallExpr * expr);
bool TraverseCXXOperatorCallExpr(CXXOperatorCallExpr * expr);
bool TraverseCXXConstructExpr(CXXConstructExpr * expr);
bool VisitCallExpr(CallExpr const * expr);
bool VisitCXXConstructExpr(CXXConstructExpr const * expr);
private:
enum class TreatEmpty { DefaultCtor, CheckEmpty, Error };
enum class ChangeKind { Char, CharLen, SingleChar };
enum class PassThrough { No, EmptyConstantString, NonEmptyConstantString };
std::string describeChangeKind(ChangeKind kind);
bool isStringConstant(
Expr const * expr, unsigned * size, bool * nonAscii,
bool * embeddedNuls, bool * terminatingNul);
bool isZero(Expr const * expr);
void reportChange(
Expr const * expr, ChangeKind kind, std::string const & original,
std::string const & replacement, PassThrough pass,
char const * rewriteFrom, char const * rewriteTo);
void checkEmpty(
CallExpr const * expr, std::string const & qname, TreatEmpty treatEmpty,
unsigned size, std::string * replacement);
void handleChar(
CallExpr const * expr, unsigned arg, std::string const & qname,
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,
std::string const & qname, std::string const & replacement,
TreatEmpty treatEmpty);
void handleOUStringCtor(
CallExpr const * expr, unsigned arg, std::string const & qname,
bool explicitFunctionalCastNotation);
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;
}
std::string qname(fdecl->getQualifiedNameAsString());
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, qname, true);
}
}
}
//TODO: u.compareToAscii("foo") -> u.???("foo")
//TODO: u.compareToIgnoreAsciiCaseAscii("foo") -> u.???("foo")
if (qname == "rtl::OUString::createFromAscii" && fdecl->getNumParams() == 1)
{
// OUString::createFromAscii("foo") -> OUString("foo")
handleChar(
expr, 0, qname, "rtl::OUString constructor",
TreatEmpty::DefaultCtor, true);
return true;
}
if (qname == "rtl::OUString::endsWithAsciiL" && fdecl->getNumParams() == 2)
{
// u.endsWithAsciiL("foo", 3) -> u.endsWith("foo"):
handleCharLen(
expr, 0, 1, qname, "rtl::OUString::endsWith", TreatEmpty::Error);
return true;
}
if (qname == "rtl::OUString::endsWithIgnoreAsciiCaseAsciiL"
&& fdecl->getNumParams() == 2)
{
// u.endsWithIgnoreAsciiCaseAsciiL("foo", 3) ->
// u.endsWithIgnoreAsciiCase("foo"):
handleCharLen(
expr, 0, 1, qname, "rtl::OUString::endsWithIgnoreAsciiCase",
TreatEmpty::Error);
return true;
}
if (qname == "rtl::OUString::equalsAscii" && fdecl->getNumParams() == 1) {
// u.equalsAscii("foo") -> u == "foo":
handleChar(
expr, 0, qname, "operator ==", TreatEmpty::CheckEmpty, false);
return true;
}
if (qname == "rtl::OUString::equalsAsciiL" && fdecl->getNumParams() == 2) {
// u.equalsAsciiL("foo", 3) -> u == "foo":
handleCharLen(expr, 0, 1, qname, "operator ==", TreatEmpty::CheckEmpty);
return true;
}
if (qname == "rtl::OUString::equalsIgnoreAsciiCaseAscii"
&& fdecl->getNumParams() == 1)
{
// u.equalsIgnoreAsciiCaseAscii("foo") ->
// u.equalsIngoreAsciiCase("foo"):
handleChar(
expr, 0, qname, "rtl::OUString::equalsIgnoreAsciiCase",
TreatEmpty::CheckEmpty, false);
return true;
}
if (qname == "rtl::OUString::equalsIgnoreAsciiCaseAsciiL"
&& fdecl->getNumParams() == 2)
{
// u.equalsIgnoreAsciiCaseAsciiL("foo", 3) ->
// u.equalsIngoreAsciiCase("foo"):
handleCharLen(
expr, 0, 1, qname, "rtl::OUString::equalsIgnoreAsciiCase",
TreatEmpty::CheckEmpty);
return true;
}
if (qname == "rtl::OUString::indexOfAsciiL" && fdecl->getNumParams() == 3) {
assert(expr->getNumArgs() == 3);
// u.indexOfAsciiL("foo", 3, i) -> u.indexOf("foo", i):
handleCharLen(
expr, 0, 1, qname, "rtl::OUString::indexOf", TreatEmpty::Error);
return true;
}
if (qname == "rtl::OUString::lastIndexOfAsciiL"
&& fdecl->getNumParams() == 2)
{
// u.lastIndexOfAsciiL("foo", 3) -> u.lastIndexOf("foo"):
handleCharLen(
expr, 0, 1, qname, "rtl::OUString::lastIndexOf", TreatEmpty::Error);
return true;
}
if (qname == "rtl::OUString::matchAsciiL" && fdecl->getNumParams() == 3) {
assert(expr->getNumArgs() == 3);
// u.matchAsciiL("foo", 3, i) -> u.match("foo", i):
handleCharLen(
expr, 0, 1, qname,
(isZero(expr->getArg(2))
? std::string("rtl::OUString::startsWith")
: std::string("rtl::OUString::match")),
TreatEmpty::Error);
return true;
}
if (qname == "rtl::OUString::matchIgnoreAsciiCaseAsciiL"
&& fdecl->getNumParams() == 3)
{
assert(expr->getNumArgs() == 3);
// u.matchIgnoreAsciiCaseAsciiL("foo", 3, i) ->
// u.matchIgnoreAsciiCase("foo", i):
handleCharLen(
expr, 0, 1, qname,
(isZero(expr->getArg(2))
? std::string("rtl::OUString::startsWithIgnoreAsciiCase")
: std::string("rtl::OUString::matchIgnoreAsciiCase")),
TreatEmpty::Error);
return true;
}
if (qname == "rtl::OUString::reverseCompareToAsciiL"
&& fdecl->getNumParams() == 2)
{
// u.reverseCompareToAsciiL("foo", 3) -> u.reverseCompareTo("foo"):
handleCharLen(
expr, 0, 1, qname, "rtl::OUString::reverseCompareTo",
TreatEmpty::Error);
return true;
}
if (qname == "rtl::OUString::reverseCompareTo"
&& fdecl->getNumParams() == 1)
{
handleOUStringCtor(expr, 0, qname, false);
return true;
}
if (qname == "rtl::OUString::equalsIgnoreAsciiCase"
&& fdecl->getNumParams() == 1)
{
handleOUStringCtor(expr, 0, qname, false);
return true;
}
if (qname == "rtl::OUString::match" && fdecl->getNumParams() == 2) {
handleOUStringCtor(expr, 0, qname, false);
return true;
}
if (qname == "rtl::OUString::matchIgnoreAsciiCase"
&& fdecl->getNumParams() == 2)
{
handleOUStringCtor(expr, 0, qname, false);
return true;
}
if (qname == "rtl::OUString::startsWith" && fdecl->getNumParams() == 2) {
handleOUStringCtor(expr, 0, qname, false);
return true;
}
if (qname == "rtl::OUString::startsWithIgnoreAsciiCase"
&& fdecl->getNumParams() == 2)
{
handleOUStringCtor(expr, 0, qname, false);
return true;
}
if (qname == "rtl::OUString::endsWith" && fdecl->getNumParams() == 2) {
handleOUStringCtor(expr, 0, qname, false);
return true;
}
if (qname == "rtl::OUString::endsWithIgnoreAsciiCase"
&& fdecl->getNumParams() == 2)
{
handleOUStringCtor(expr, 0, qname, false);
return true;
}
if (qname == "rtl::OUString::indexOf" && fdecl->getNumParams() == 2) {
handleOUStringCtor(expr, 0, qname, false);
return true;
}
if (qname == "rtl::OUString::lastIndexOf" && fdecl->getNumParams() == 1) {
handleOUStringCtor(expr, 0, qname, false);
return true;
}
if (qname == "rtl::OUString::replaceFirst" && fdecl->getNumParams() == 3) {
handleOUStringCtor(expr, 0, qname, false);
handleOUStringCtor(expr, 1, qname, false);
return true;
}
if (qname == "rtl::OUString::replaceAll"
&& (fdecl->getNumParams() == 2 || fdecl->getNumParams() == 3))
{
handleOUStringCtor(expr, 0, qname, false);
handleOUStringCtor(expr, 1, qname, false);
return true;
}
if (qname == "rtl::OUString::operator+=" && fdecl->getNumParams() == 1) {
handleOUStringCtor(
expr, dyn_cast<CXXOperatorCallExpr>(expr) == nullptr ? 0 : 1,
qname, false);
return true;
}
if (qname == "rtl::OUString::equals" && fdecl->getNumParams() == 1) {
unsigned n;
bool non;
bool emb;
bool trm;
if (!isStringConstant(
expr->getArg(0)->IgnoreParenImpCasts(), &n, &non, &emb, &trm))
{
return true;
}
if (non) {
report(
DiagnosticsEngine::Warning,
("call of " + qname
+ (" with string constant argument containging non-ASCII"
" characters")),
expr->getExprLoc())
<< expr->getSourceRange();
}
if (emb) {
report(
DiagnosticsEngine::Warning,
("call of " + qname
+ " with string constant argument containging embedded NULs"),
expr->getExprLoc())
<< expr->getSourceRange();
}
if (n == 0) {
report(
DiagnosticsEngine::Warning,
("rewrite call of " + qname
+ (" with empty string constant argument as call of"
" rtl::OUString::isEmpty")),
expr->getExprLoc())
<< expr->getSourceRange();
return true;
}
}
if (qname == "rtl::operator==" && fdecl->getNumParams() == 2) {
for (unsigned i = 0; i != 2; ++i) {
unsigned n;
bool non;
bool emb;
bool trm;
if (!isStringConstant(
expr->getArg(i)->IgnoreParenImpCasts(), &n, &non, &emb,
&trm))
{
continue;
}
if (non) {
report(
DiagnosticsEngine::Warning,
("call of " + qname
+ (" with string constant argument containging non-ASCII"
" characters")),
expr->getExprLoc())
<< expr->getSourceRange();
}
if (emb) {
report(
DiagnosticsEngine::Warning,
("call of " + qname
+ (" with string constant argument containging embedded"
" NULs")),
expr->getExprLoc())
<< expr->getSourceRange();
}
if (n == 0) {
report(
DiagnosticsEngine::Warning,
("rewrite call of " + qname
+ (" with empty string constant argument as call of"
" rtl::OUString::isEmpty")),
expr->getExprLoc())
<< expr->getSourceRange();
}
}
return true;
}
if (qname == "rtl::operator!=" && fdecl->getNumParams() == 2) {
for (unsigned i = 0; i != 2; ++i) {
unsigned n;
bool non;
bool emb;
bool trm;
if (!isStringConstant(
expr->getArg(i)->IgnoreParenImpCasts(), &n, &non, &emb,
&trm))
{
continue;
}
if (non) {
report(
DiagnosticsEngine::Warning,
("call of " + qname
+ (" with string constant argument containging non-ASCII"
" characters")),
expr->getExprLoc())
<< expr->getSourceRange();
}
if (emb) {
report(
DiagnosticsEngine::Warning,
("call of " + qname
+ (" with string constant argument containging embedded"
" NULs")),
expr->getExprLoc())
<< expr->getSourceRange();
}
if (n == 0) {
report(
DiagnosticsEngine::Warning,
("rewrite call of " + qname
+ (" with empty string constant argument as call of"
" !rtl::OUString::isEmpty")),
expr->getExprLoc())
<< expr->getSourceRange();
}
}
return true;
}
if (qname == "rtl::OUString::operator=" && fdecl->getNumParams() == 1) {
unsigned n;
bool non;
bool emb;
bool trm;
if (!isStringConstant(
expr->getArg(1)->IgnoreParenImpCasts(), &n, &non, &emb, &trm))
{
return true;
}
if (non) {
report(
DiagnosticsEngine::Warning,
("call of " + qname
+ (" with string constant argument containging non-ASCII"
" characters")),
expr->getExprLoc())
<< expr->getSourceRange();
}
if (emb) {
report(
DiagnosticsEngine::Warning,
("call of " + qname
+ " with string constant argument containging embedded NULs"),
expr->getExprLoc())
<< expr->getSourceRange();
}
if (n == 0) {
report(
DiagnosticsEngine::Warning,
("rewrite call of " + qname
+ (" with empty string constant argument as call of"
" rtl::OUString::clear")),
expr->getExprLoc())
<< expr->getSourceRange();
return true;
}
return true;
}
if (qname == "rtl::OUStringBuffer::appendAscii"
&& fdecl->getNumParams() == 1)
{
// u.appendAscii("foo") -> u.append("foo")
handleChar(
expr, 0, qname, "rtl::OUStringBuffer::append", TreatEmpty::Error,
true, "appendAscii", "append");
return true;
}
if (qname == "rtl::OUStringBuffer::appendAscii"
&& fdecl->getNumParams() == 2)
{
// u.appendAscii("foo", 3) -> u.append("foo"):
handleCharLen(
expr, 0, 1, qname, "rtl::OUStringBuffer::append",
TreatEmpty::Error);
return true;
}
return true;
}
bool StringConstant::VisitCXXConstructExpr(CXXConstructExpr const * expr) {
if (ignoreLocation(expr)) {
return true;
}
std::string qname(
expr->getConstructor()->getParent()->getQualifiedNameAsString());
if (qname == "rtl::OUString") {
ChangeKind kind;
PassThrough pass;
switch (expr->getConstructor()->getNumParams()) {
case 1:
{
APSInt v;
if (!expr->getArg(0)->isIntegerConstantExpr(
v, compiler.getASTContext()))
{
return true;
}
if (v == 0 || v.uge(0x80)) {
return true;
}
kind = ChangeKind::SingleChar;
pass = PassThrough::NonEmptyConstantString;
break;
}
case 2:
{
unsigned n;
bool non;
bool emb;
bool trm;
if (!isStringConstant(
expr->getArg(0)->IgnoreParenImpCasts(), &n, &non, &emb,
&trm))
{
return true;
}
if (non) {
report(
DiagnosticsEngine::Warning,
("construction of " + qname
+ (" with string constant argument containging"
" non-ASCII characters")),
expr->getExprLoc())
<< expr->getSourceRange();
}
if (emb) {
report(
DiagnosticsEngine::Warning,
("construction of " + qname
+ (" with string constant argument containging"
" embedded NULs")),
expr->getExprLoc())
<< expr->getSourceRange();
}
kind = ChangeKind::Char;
pass = n == 0
? PassThrough::EmptyConstantString
: PassThrough::NonEmptyConstantString;
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)->GetTemporaryExpr()
->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;
}
std::string callQname(
fdecl->getQualifiedNameAsString());
if (pass == PassThrough::EmptyConstantString) {
if (callQname == "rtl::OUString::equals"
|| callQname == "rtl::operator==")
{
report(
DiagnosticsEngine::Warning,
("rewrite call of " + callQname
+ " with construction of " + qname
+ (" with empty string constant argument"
" as call of rtl::OUString::isEmpty")),
getMemberLocation(call))
<< call->getSourceRange();
return true;
}
if (callQname == "rtl::operator!=") {
report(
DiagnosticsEngine::Warning,
("rewrite call of " + callQname
+ " with construction of " + qname
+ (" with empty string constant argument"
" as call of !rtl::OUString::isEmpty")),
getMemberLocation(call))
<< call->getSourceRange();
return true;
}
if (callQname == "rtl::operator+"
|| callQname == "rtl::OUString::operator+=")
{
report(
DiagnosticsEngine::Warning,
("call of " + callQname
+ " with suspicous construction of "
+ qname
+ " with empty string constant argument"),
getMemberLocation(call))
<< call->getSourceRange();
return true;
}
if (callQname == "rtl::OUString::operator=") {
report(
DiagnosticsEngine::Warning,
("rewrite call of " + callQname
+ " with construction of " + qname
+ (" with empty string constant argument"
" as call of rtl::OUString::clear")),
getMemberLocation(call))
<< call->getSourceRange();
return true;
}
} else {
assert(pass == PassThrough::NonEmptyConstantString);
if (callQname == "rtl::OUString::equals") {
report(
DiagnosticsEngine::Warning,
("rewrite call of " + callQname
+ " with construction of " + qname
+ " with " + describeChangeKind(kind)
+ " as operator =="),
getMemberLocation(call))
<< call->getSourceRange();
return true;
}
if (callQname == "rtl::operator+"
|| callQname == "rtl::OUString::operator="
|| callQname == "rtl::operator=="
|| callQname == "rtl::operator!=")
{
if (callQname == "rtl::operator+") {
std::string file(
compiler.getSourceManager().getFilename(
compiler.getSourceManager()
.getSpellingLoc(
expr->getLocStart())));
if (file
== (SRCDIR
"/sal/qa/rtl/strings/test_ostring_concat.cxx")
|| (file
== (SRCDIR
"/sal/qa/rtl/strings/test_oustring_concat.cxx")))
{
return true;
}
}
report(
DiagnosticsEngine::Warning,
("elide construction of " + qname + " with "
+ describeChangeKind(kind) + " in call of "
+ callQname),
getMemberLocation(expr))
<< expr->getSourceRange();
return true;
}
}
return true;
} else if (isa<CXXConstructExpr>(call)) {
} else {
assert(false);
}
}
}
}
return true;
}
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 "ASCII sal_Unicode argument";
default:
std::abort();
}
}
bool StringConstant::isStringConstant(
Expr const * expr, unsigned * size, bool * nonAscii, bool * embeddedNuls,
bool * terminatingNul)
{
assert(expr != nullptr);
assert(size != nullptr);
assert(nonAscii != nullptr);
assert(embeddedNuls != nullptr);
assert(terminatingNul != nullptr);
QualType t = expr->getType();
if (!(t->isConstantArrayType() && t.isConstQualified()
&& (loplugin::TypeCheck(t->getAsArrayTypeUnsafe()->getElementType())
.Char())))
{
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();
}
}
}
StringLiteral const * lit = dyn_cast<StringLiteral>(expr);
if (lit != nullptr) {
if (!lit->isAscii()) {
return false;
}
unsigned n = lit->getLength();
bool non = false;
bool emb = false;
StringRef str = lit->getString();
for (unsigned i = 0; i != n; ++i) {
if (str[i] == '\0') {
emb = true;
} else if (static_cast<unsigned char>(str[i]) >= 0x80) {
non = true;
}
}
*size = n;
*nonAscii = non;
*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 *>();
assert(e != nullptr); //TODO???
if (!v.getLValueOffset().isZero()) {
return false; //TODO
}
Expr const * e2 = e->IgnoreParenImpCasts();
if (e2 != e) {
return isStringConstant(
e2, size, nonAscii, 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());
*nonAscii = false; //TODO
*embeddedNuls = false; //TODO
*terminatingNul = true;
return true;
}
case APValue::Array:
{
if (v.hasArrayFiller()) { //TODO: handle final NUL filler?
return false;
}
unsigned n = v.getArraySize();
assert(n != 0);
bool non = false;
bool emb = false;
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)) {
non = true;
}
}
APValue e(v.getArrayInitializedElt(n - 1));
if (!e.isInt()) { //TODO: assert?
return false;
}
bool trm = e.getInt() == 0;
*size = trm ? n - 1 : n;
*nonAscii = non;
*embeddedNuls = emb;
*terminatingNul = trm;
return true;
}
default:
assert(false); //TODO???
return false;
}
}
bool StringConstant::isZero(Expr const * expr) {
APSInt res;
return expr->isIntegerConstantExpr(res, compiler.getASTContext())
&& res == 0;
}
void StringConstant::reportChange(
Expr const * expr, ChangeKind kind, std::string const & original,
std::string const & replacement, PassThrough pass, 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;
}
std::string qname(fdecl->getQualifiedNameAsString());
if (pass == PassThrough::EmptyConstantString) {
if (qname == "rtl::OUString::equals"
|| qname == "rtl::operator==")
{
report(
DiagnosticsEngine::Warning,
("rewrite call of " + qname + " with call of "
+ original
+ (" with empty string constant argument as"
" call of rtl::OUString::isEmpty")),
getMemberLocation(call))
<< call->getSourceRange();
return;
}
if (qname == "rtl::operator!=") {
report(
DiagnosticsEngine::Warning,
("rewrite call of " + qname + " with call of "
+ original
+ (" with empty string constant argument as"
" call of !rtl::OUString::isEmpty")),
getMemberLocation(call))
<< call->getSourceRange();
return;
}
if (qname == "rtl::operator+"
|| qname == "rtl::OUString::operator+=")
{
report(
DiagnosticsEngine::Warning,
("call of " + qname + " with suspicous call of "
+ original
+ " with empty string constant argument"),
getMemberLocation(call))
<< call->getSourceRange();
return;
}
if (qname == "rtl::OUString::operator=") {
report(
DiagnosticsEngine::Warning,
("rewrite call of " + qname + " with call of "
+ original
+ (" with empty string constant argument as"
" call of rtl::OUString::call")),
getMemberLocation(call))
<< call->getSourceRange();
return;
}
} else {
assert(pass == PassThrough::NonEmptyConstantString);
if (qname == "rtl::OUString::equals"
|| qname == "rtl::OUString::operator="
|| qname == "rtl::operator=="
|| qname == "rtl::operator!=")
{
report(
DiagnosticsEngine::Warning,
("elide call of " + original + " with "
+ describeChangeKind(kind) + " in call of "
+ qname),
getMemberLocation(expr))
<< expr->getSourceRange();
return;
}
if (qname == "rtl::operator+"
|| qname == "rtl::OUString::operator+=")
{
report(
DiagnosticsEngine::Warning,
("rewrite call of " + original + " with "
+ describeChangeKind(kind) + " in call of "
+ qname
+ (" as (implicit) construction of"
" rtl::OUString")),
getMemberLocation(expr))
<< expr->getSourceRange();
return;
}
}
report(
DiagnosticsEngine::Warning,
"TODO call inside " + qname, getMemberLocation(expr))
<< expr->getSourceRange();
return;
} else if (isa<CXXConstructExpr>(call)) {
std::string qname(
cast<CXXConstructExpr>(call)->getConstructor()
->getParent()->getQualifiedNameAsString());
if (qname == "rtl::OUString"
|| qname == "rtl::OUStringBuffer")
{
//TODO: propagate further out?
if (pass == PassThrough::EmptyConstantString) {
report(
DiagnosticsEngine::Warning,
("rewrite construction of " + qname
+ " with call of " + original
+ (" with empty string constant argument as"
" default construction of ")
+ qname),
getMemberLocation(call))
<< call->getSourceRange();
} else {
assert(pass == PassThrough::NonEmptyConstantString);
report(
DiagnosticsEngine::Warning,
("elide call of " + original + " with "
+ describeChangeKind(kind)
+ " in construction of " + qname),
getMemberLocation(expr))
<< expr->getSourceRange();
}
return;
}
} else {
assert(false);
}
}
}
}
if (rewriter != nullptr && rewriteFrom != nullptr) {
SourceLocation loc = getMemberLocation(expr);
while (compiler.getSourceManager().isMacroArgExpansion(loc)) {
loc = compiler.getSourceManager().getImmediateMacroCallerLoc(loc);
}
if (compat::isMacroBodyExpansion(compiler, 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 " + original + " with " + describeChangeKind(kind)
+ " as call of " + replacement),
getMemberLocation(expr))
<< expr->getSourceRange();
}
void StringConstant::checkEmpty(
CallExpr const * expr, std::string const & qname, 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 " + qname
+ " with suspicous empty string constant argument"),
getMemberLocation(expr))
<< expr->getSourceRange();
break;
}
}
}
void StringConstant::handleChar(
CallExpr const * expr, unsigned arg, std::string const & qname,
std::string const & replacement, TreatEmpty treatEmpty, bool literal,
char const * rewriteFrom, char const * rewriteTo)
{
unsigned n;
bool non;
bool emb;
bool trm;
if (!isStringConstant(
expr->getArg(arg)->IgnoreParenImpCasts(), &n, &non, &emb, &trm))
{
return;
}
if (non) {
report(
DiagnosticsEngine::Warning,
("call of " + qname
+ (" with string constant argument containging non-ASCII"
" characters")),
getMemberLocation(expr))
<< expr->getSourceRange();
return;
}
if (emb) {
report(
DiagnosticsEngine::Warning,
("call of " + qname
+ " with string constant argument containging embedded NULs"),
getMemberLocation(expr))
<< expr->getSourceRange();
return;
}
if (!trm) {
report(
DiagnosticsEngine::Warning,
("call of " + qname
+ " with string constant argument lacking a terminating NUL"),
getMemberLocation(expr))
<< expr->getSourceRange();
return;
}
std::string repl(replacement);
checkEmpty(expr, qname, treatEmpty, n, &repl);
reportChange(
expr, ChangeKind::Char, qname, repl,
(literal
? (n == 0
? PassThrough::EmptyConstantString
: PassThrough::NonEmptyConstantString)
: PassThrough::No),
rewriteFrom, rewriteTo);
}
void StringConstant::handleCharLen(
CallExpr const * expr, unsigned arg1, unsigned arg2,
std::string const & qname, 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 non;
bool emb;
bool trm;
if (!(isStringConstant(
expr->getArg(arg1)->IgnoreParenImpCasts(), &n, &non, &emb, &trm)
&& trm))
{
return;
}
APSInt res;
if (expr->getArg(arg2)->isIntegerConstantExpr(
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 non2;
bool emb2;
bool trm2;
if (!(isStringConstant(
subs->getBase()->IgnoreParenImpCasts(), &n2, &non2, &emb2,
&trm2)
&& n2 == n && non2 == non && emb2 == emb && trm2 == trm
//TODO: same strings
&& subs->getIdx()->isIntegerConstantExpr(
res, compiler.getASTContext())
&& res == 0))
{
return;
}
}
if (non) {
report(
DiagnosticsEngine::Warning,
("call of " + qname
+ (" with string constant argument containging non-ASCII"
" characters")),
getMemberLocation(expr))
<< expr->getSourceRange();
}
if (emb) {
return;
}
std::string repl(replacement);
checkEmpty(expr, qname, treatEmpty, n, &repl);
reportChange(
expr, ChangeKind::CharLen, qname, repl, PassThrough::No, nullptr,
nullptr);
}
void StringConstant::handleOUStringCtor(
CallExpr const * expr, unsigned arg, std::string const & qname,
bool explicitFunctionalCastNotation)
{
auto e0 = expr->getArg(arg)->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 (e3->getConstructor()->getQualifiedNameAsString()
!= "rtl::OUString::OUString")
{
return;
}
if (e3->getNumArgs() == 0) {
report(
DiagnosticsEngine::Warning,
("in call of %0, replace default-constructed OUString with an empty"
" string literal"),
e3->getExprLoc())
<< qname << expr->getSourceRange();
return;
}
APSInt res;
if (e3->getNumArgs() == 1
&& e3->getArg(0)->IgnoreParenImpCasts()->isIntegerConstantExpr(
res, compiler.getASTContext()))
{
// It may not be easy to rewrite OUString(c), esp. given there is no
// OUString ctor taking an OUStringLiteral1 arg, so don't warn there:
if (!explicitFunctionalCastNotation) {
uint64_t n = res.getZExtValue();
if (n != 0 && n <= 127) {
report(
DiagnosticsEngine::Warning,
("in call of %0, replace OUString constructed from an ASCII"
" char constant with a string literal"),
e3->getExprLoc())
<< qname << expr->getSourceRange();
}
}
return;
}
if (e3->getNumArgs() != 2) {
return;
}
unsigned n;
bool non;
bool emb;
bool trm;
if (!isStringConstant(
e3->getArg(0)->IgnoreParenImpCasts(), &n, &non, &emb, &trm))
{
return;
}
//TODO: non, emb, trm
if (rewriter != nullptr) {
auto loc1 = e3->getLocStart();
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())
<< qname << expr->getSourceRange();
}
loplugin::Plugin::Registration< StringConstant > X("stringconstant", true);
}
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */