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