summaryrefslogtreecommitdiff
path: root/compilerplugins
diff options
context:
space:
mode:
authorStephan Bergmann <sbergman@redhat.com>2014-12-19 10:03:39 +0100
committerStephan Bergmann <sbergman@redhat.com>2014-12-19 10:03:52 +0100
commit2a52591bfe10c651c9eba66cb44ed7675f5fba26 (patch)
treea7cce4fd1f8a0e1d56d6ff56aa53ba1e0b33b8a4 /compilerplugins
parent47debe4f1b1024095651355797150507a1d66a6f (diff)
Extend loplugin:literalalternative to loplugin:stringconstant
Change-Id: Ie425af19019126b6a15ac03f52e32d186a46db35
Diffstat (limited to 'compilerplugins')
-rw-r--r--compilerplugins/clang/literalalternative.cxx111
-rw-r--r--compilerplugins/clang/stringconstant.cxx1096
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: */