diff options
author | Stephan Bergmann <sbergman@redhat.com> | 2014-12-19 10:03:39 +0100 |
---|---|---|
committer | Stephan Bergmann <sbergman@redhat.com> | 2014-12-19 10:03:52 +0100 |
commit | 2a52591bfe10c651c9eba66cb44ed7675f5fba26 (patch) | |
tree | a7cce4fd1f8a0e1d56d6ff56aa53ba1e0b33b8a4 | |
parent | 47debe4f1b1024095651355797150507a1d66a6f (diff) |
Extend loplugin:literalalternative to loplugin:stringconstant
Change-Id: Ie425af19019126b6a15ac03f52e32d186a46db35
-rw-r--r-- | compilerplugins/clang/literalalternative.cxx | 111 | ||||
-rw-r--r-- | compilerplugins/clang/stringconstant.cxx | 1096 |
2 files changed, 1096 insertions, 111 deletions
diff --git a/compilerplugins/clang/literalalternative.cxx b/compilerplugins/clang/literalalternative.cxx deleted file mode 100644 index 0d29e0d1d050..000000000000 --- a/compilerplugins/clang/literalalternative.cxx +++ /dev/null @@ -1,111 +0,0 @@ -/* -*- 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 <string> - -#include "plugin.hxx" - -// Find those calls of rtl::OUString::equalsIgnoreAsciiCaseAscii and -// rtl::OUString::equalsIgnoreAsciiCaseAsciiL that could be simplified to call -// rtl::OUString::equalsIgnoreAsciiCase instead: - -namespace { - -class LiteralAlternative: - public RecursiveASTVisitor<LiteralAlternative>, public loplugin::Plugin -{ -public: - explicit LiteralAlternative(InstantiationData const & data): Plugin(data) {} - - virtual void run() override { TraverseDecl(compiler.getASTContext().getTranslationUnitDecl()); } - - bool VisitCallExpr(const CallExpr * expr); -}; - -bool LiteralAlternative::VisitCallExpr(const CallExpr * expr) { - if (ignoreLocation(expr)) { - return true; - } - FunctionDecl const * fdecl = expr->getDirectCallee(); - if (fdecl == nullptr) { - return true; - } - std::string qname { fdecl->getQualifiedNameAsString() }; - if (qname == "rtl::OUString::equalsIgnoreAsciiCaseAscii" - && fdecl->getNumParams() == 1 && expr->getNumArgs() == 1) - { - // equalsIgnoreAsciiCaseAscii("foo") -> equalsIngoreAsciiCase("foo"): - StringLiteral const * lit - = dyn_cast<StringLiteral>(expr->getArg(0)->IgnoreParenImpCasts()); - if (lit != nullptr) { - report( - DiagnosticsEngine::Warning, - ("rewrite call of rtl::OUString::equalsIgnoreAsciiCaseAscii" - " with string literal argument as call of" - " rtl::OUString::equalsIgnoreAsciiCase"), - expr->getExprLoc()); - //TODO: a better loc (the "equalsIgnoreAsciiCaseAscii" part)? - } - return true; - } - if (qname == "rtl::OUString::equalsIgnoreAsciiCaseAsciiL" - && fdecl->getNumParams() == 2 && expr->getNumArgs() == 2) - { - // equalsIgnoreAsciiCaseAsciiL("foo", 3) -> equalsIngoreAsciiCase("foo") - // especially also for - // equalsIgnoreAsciiCaseAsciiL(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): - APSInt res; - if (expr->getArg(1)->isIntegerConstantExpr(res, compiler.getASTContext())) { - Expr const * arg0 = expr->getArg(0)->IgnoreParenImpCasts(); - StringLiteral const * lit = dyn_cast<StringLiteral>(arg0); - bool match = false; - if (lit != nullptr) { - match = res == lit->getLength(); - } else { - UnaryOperator const * op = dyn_cast<UnaryOperator>(arg0); - if (op != nullptr && op->getOpcode() == UO_AddrOf) { - ArraySubscriptExpr const * subs - = dyn_cast<ArraySubscriptExpr>( - op->getSubExpr()->IgnoreParenImpCasts()); - if (subs != nullptr) { - lit = dyn_cast<StringLiteral>( - subs->getBase()->IgnoreParenImpCasts()); - match = lit != nullptr - && subs->getIdx()->isIntegerConstantExpr( - res, compiler.getASTContext()) - && res == 0; - } - } - } - if (match) { - report( - DiagnosticsEngine::Warning, - ("rewrite call of" - " rtl::OUString::equalsIgnoreAsciiCaseAsciiL with string" - " literal and matching length arguments as call of" - " rtl::OUString::equalsIgnoreAsciiCase"), - expr->getExprLoc()); - //TODO: a better loc (the "equalsIgnoreAsciiCaseAsciiL" - // part)? - } - } - return true; - } - return true; -} - -loplugin::Plugin::Registration< LiteralAlternative > X("literalalternative"); - -} - -/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/compilerplugins/clang/stringconstant.cxx b/compilerplugins/clang/stringconstant.cxx new file mode 100644 index 000000000000..12bf173bc4f1 --- /dev/null +++ b/compilerplugins/clang/stringconstant.cxx @@ -0,0 +1,1096 @@ +/* -*- 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 <cassert> +#include <limits> +#include <stack> +#include <string> + +#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-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 { + +bool isPlainChar(QualType type) { + return type->isSpecificBuiltinType(BuiltinType::Char_S) + || type->isSpecificBuiltinType(BuiltinType::Char_U); +} + +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(); +} + +class StringConstant: + public RecursiveASTVisitor<StringConstant>, public loplugin::Plugin +{ +public: + explicit StringConstant(InstantiationData const & data): Plugin(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); + + 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); + + void handleCharLen( + CallExpr const * expr, unsigned arg1, unsigned arg2, + std::string const & qname, std::string const & replacement, + TreatEmpty treatEmpty); + + 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 res = true; + for (auto * e: expr->children()) { + if (!TraverseStmt(e)) { + res = false; + break; + } + } + calls_.pop(); + return res; +} + +bool StringConstant::TraverseCXXMemberCallExpr(CXXMemberCallExpr * expr) { + if (!WalkUpFromCXXMemberCallExpr(expr)) { + return false; + } + calls_.push(expr); + bool res = true; + for (auto * e: expr->children()) { + if (!TraverseStmt(e)) { + res = false; + break; + } + } + calls_.pop(); + return res; +} + +bool StringConstant::TraverseCXXOperatorCallExpr(CXXOperatorCallExpr * expr) +{ + if (!WalkUpFromCXXOperatorCallExpr(expr)) { + return false; + } + calls_.push(expr); + bool res = true; + for (auto * e: expr->children()) { + if (!TraverseStmt(e)) { + res = false; + break; + } + } + calls_.pop(); + return res; +} + +bool StringConstant::TraverseCXXConstructExpr(CXXConstructExpr * expr) { + if (!WalkUpFromCXXConstructExpr(expr)) { + return false; + } + calls_.push(expr); + bool res = true; + for (auto * e: expr->children()) { + if (!TraverseStmt(e)) { + res = false; + break; + } + } + calls_.pop(); + return res; +} + +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()); + //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::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; +} + +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"; + } +} + +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() + && isPlainChar(t->getAsArrayTypeUnsafe()->getElementType()))) + { + return false; + } + 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) +{ + 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); + } + } + } + } + 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) +{ + 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)); +} + +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); +} + +loplugin::Plugin::Registration< StringConstant > X("stringconstant"); + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ |