/* -*- 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 #include #include #include #include #include #include #include "check.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-NUL ASCII character---except // that the last array element may be NUL, or, in some situations, of type char // with a ASCII value (including NUL). Note that the former includes // expressions denoting narrow string literals like "foo", and, with toolchains // that support constexpr, constexpr variables declared like // // constexpr char str[] = "bar"; // // This plugin flags uses of OUString functions with string constant arguments // that can be rewritten more directly, like // // OUString::createFromAscii("foo") -> "foo" // // and // // s.equals(OUString("bar")) -> s == "bar" namespace { SourceLocation getMemberLocation(Expr const * expr) { CallExpr const * e1 = dyn_cast(expr); MemberExpr const * e2 = e1 == nullptr ? nullptr : dyn_cast(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(*d); if (f != nullptr && f->getMinRequiredArguments() <= arguments && f->getNumParams() >= arguments) { ++n; if (n == 2) { return true; } } } return false; } class StringConstant: public RecursiveASTVisitor, public loplugin::RewritePlugin { public: explicit StringConstant(InstantiationData const & data): RewritePlugin(data) {} void run() override; bool TraverseCallExpr(CallExpr * expr); bool TraverseCXXMemberCallExpr(CXXMemberCallExpr * expr); bool TraverseCXXOperatorCallExpr(CXXOperatorCallExpr * expr); bool TraverseCXXConstructExpr(CXXConstructExpr * expr); bool VisitCallExpr(CallExpr const * expr); bool VisitCXXConstructExpr(CXXConstructExpr const * expr); private: enum class TreatEmpty { DefaultCtor, CheckEmpty, Error }; enum class ChangeKind { Char, CharLen, SingleChar, OUStringLiteral1 }; enum class PassThrough { No, EmptyConstantString, NonEmptyConstantString }; std::string describeChangeKind(ChangeKind kind); bool isStringConstant( Expr const * expr, unsigned * size, bool * nonAscii, bool * embeddedNuls, bool * terminatingNul); bool isZero(Expr const * expr); void reportChange( Expr const * expr, ChangeKind kind, std::string const & original, std::string const & replacement, PassThrough pass, char const * rewriteFrom, char const * rewriteTo); void checkEmpty( CallExpr const * expr, 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); std::stack 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); } } } 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"): 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"): 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(expr) == nullptr ? 0 : 1, fdecl, false); return true; } if ((dc.Function("equals").Class("OUString").Namespace("rtl") .GlobalNamespace()) && 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 %0 with string constant argument containging" " non-ASCII characters"), expr->getExprLoc()) << fdecl->getQualifiedNameAsString() << expr->getSourceRange(); } if (emb) { report( DiagnosticsEngine::Warning, ("call of %0 with string constant argument containging embedded" " NULs"), 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 non; bool emb; bool trm; if (!isStringConstant( expr->getArg(i)->IgnoreParenImpCasts(), &n, &non, &emb, &trm)) { continue; } if (non) { report( DiagnosticsEngine::Warning, ("call of %0 with string constant argument containging" " non-ASCII characters"), expr->getExprLoc()) << fdecl->getQualifiedNameAsString() << expr->getSourceRange(); } if (emb) { report( DiagnosticsEngine::Warning, ("call of %0 with string constant argument containging" " embedded NULs"), 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 non; bool emb; bool trm; if (!isStringConstant( expr->getArg(i)->IgnoreParenImpCasts(), &n, &non, &emb, &trm)) { continue; } if (non) { report( DiagnosticsEngine::Warning, ("call of %0 with string constant argument containging" " non-ASCII characters"), expr->getExprLoc()) << fdecl->getQualifiedNameAsString() << expr->getSourceRange(); } if (emb) { report( DiagnosticsEngine::Warning, ("call of %0 with string constant argument containging" " embedded NULs"), 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 non; bool emb; bool trm; if (!isStringConstant( expr->getArg(1)->IgnoreParenImpCasts(), &n, &non, &emb, &trm)) { return true; } if (non) { report( DiagnosticsEngine::Warning, ("call of %0 with string constant argument containging" " non-ASCII characters"), expr->getExprLoc()) << fdecl->getQualifiedNameAsString() << expr->getSourceRange(); } if (emb) { report( DiagnosticsEngine::Warning, ("call of %0 with string constant argument containging embedded" " NULs"), 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("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; } return true; } bool StringConstant::VisitCXXConstructExpr(CXXConstructExpr const * expr) { if (ignoreLocation(expr)) { return true; } auto cdecl = expr->getConstructor()->getParent(); if (loplugin::DeclCheck(cdecl) .Class("OUString").Namespace("rtl").GlobalNamespace()) { ChangeKind kind; PassThrough pass; 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; break; case 2: { auto arg = expr->getArg(0); if (loplugin::TypeCheck(arg->getType()) .Class("OUStringLiteral1_").Namespace("rtl") .GlobalNamespace()) { kind = ChangeKind::OUStringLiteral1; pass = PassThrough::NonEmptyConstantString; } else { unsigned n; bool non; bool emb; bool trm; if (!isStringConstant( arg->IgnoreParenImpCasts(), &n, &non, &emb, &trm)) { return true; } if (non) { report( DiagnosticsEngine::Warning, ("construction of %0 with string constant argument" " containging non-ASCII characters"), expr->getExprLoc()) << cdecl << expr->getSourceRange(); } if (emb) { report( DiagnosticsEngine::Warning, ("construction of %0 with string constant argument" " containging embedded NULs"), expr->getExprLoc()) << cdecl << 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(call)) { argsBeg = cast(call)->arg_begin(); argsEnd = cast(call)->arg_end(); } else if (isa(call)) { argsBeg = cast(call)->arg_begin(); argsEnd = cast(call)->arg_end(); } else { assert(false); } for (auto i(argsBeg); i != argsEnd; ++i) { Expr const * e = (*i)->IgnoreParenImpCasts(); if (isa(e)) { e = cast(e)->GetTemporaryExpr() ->IgnoreParenImpCasts(); } if (isa(e)) { e = cast(e)->getSubExpr() ->IgnoreParenImpCasts(); } if (isa(e)) { e = cast(e)->getSubExpr() ->IgnoreParenImpCasts(); } if (e == expr) { if (isa(call)) { FunctionDecl const * fdecl = cast(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() << cdecl << 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() << cdecl << 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 suspicous construction of" " %1 with empty string constant argument"), getMemberLocation(call)) << fdecl->getQualifiedNameAsString() << cdecl << 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() << cdecl << 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 ") + describeChangeKind(kind) + " as operator =="), getMemberLocation(call)) << fdecl->getQualifiedNameAsString() << cdecl << 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()) { 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; } } auto loc = expr->getArg(0)->getLocStart(); while (compiler.getSourceManager() .isMacroArgExpansion(loc)) { loc = compiler.getSourceManager() .getImmediateMacroCallerLoc(loc); } if ((compiler.getSourceManager() .isMacroBodyExpansion(loc)) && (Lexer::getImmediateMacroName( loc, compiler.getSourceManager(), compiler.getLangOpts()) == "OSL_THIS_FUNC")) { return true; } if (kind == ChangeKind::SingleChar) { report( DiagnosticsEngine::Warning, ("rewrite construction of %0 with " + describeChangeKind(kind) + (" in call of %1 as construction of" " OUStringLiteral1")), getMemberLocation(expr)) << cdecl << fdecl->getQualifiedNameAsString() << expr->getSourceRange(); } else { report( DiagnosticsEngine::Warning, ("elide construction of %0 with " + describeChangeKind(kind) + " in call of %1"), getMemberLocation(expr)) << cdecl << fdecl->getQualifiedNameAsString() << expr->getSourceRange(); } return true; } } return true; } else if (isa(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 "sal_Unicode argument"; case ChangeKind::OUStringLiteral1: return "OUStringLiteral1 argument"; default: std::abort(); } } bool StringConstant::isStringConstant( Expr const * expr, unsigned * size, bool * nonAscii, bool * embeddedNuls, bool * terminatingNul) { assert(expr != nullptr); assert(size != nullptr); assert(nonAscii != nullptr); assert(embeddedNuls != nullptr); assert(terminatingNul != nullptr); QualType t = expr->getType(); // Look inside RTL_CONSTASCII_STRINGPARAM: if (loplugin::TypeCheck(t).Pointer().Const().Char()) { auto e2 = dyn_cast(expr); if (e2 == nullptr || e2->getOpcode() != UO_AddrOf) { return false; } auto e3 = dyn_cast( e2->getSubExpr()->IgnoreParenImpCasts()); if (e3 == nullptr || !isZero(e3->getIdx()->IgnoreParenImpCasts())) { return false; } expr = e3->getBase()->IgnoreParenImpCasts(); t = expr->getType(); } if (!(t->isConstantArrayType() && t.isConstQualified() && (loplugin::TypeCheck(t->getAsArrayTypeUnsafe()->getElementType()) .Char()))) { return false; } DeclRefExpr const * dre = dyn_cast(expr); if (dre != nullptr) { VarDecl const * var = dyn_cast(dre->getDecl()); if (var != nullptr) { Expr const * init = var->getAnyInitializer(); if (init != nullptr) { expr = init->IgnoreParenImpCasts(); } } } StringLiteral const * lit = dyn_cast(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(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(); 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::max())); *size = static_cast(n.getLimitedValue()); *nonAscii = false; //TODO *embeddedNuls = false; //TODO *terminatingNul = true; return true; } case APValue::Array: { if (v.hasArrayFiller()) { //TODO: handle final NUL filler? return false; } unsigned n = v.getArraySize(); assert(n != 0); bool non = false; bool emb = false; for (unsigned i = 0; i != n - 1; ++i) { APValue e(v.getArrayInitializedElt(i)); if (!e.isInt()) { //TODO: assert? return false; } APSInt iv = e.getInt(); if (iv == 0) { emb = true; } else if (iv.uge(0x80)) { non = true; } } APValue e(v.getArrayInitializedElt(n - 1)); if (!e.isInt()) { //TODO: assert? return false; } bool trm = e.getInt() == 0; *size = trm ? n - 1 : n; *nonAscii = non; *embeddedNuls = emb; *terminatingNul = trm; return true; } default: assert(false); //TODO??? return false; } } bool StringConstant::isZero(Expr const * expr) { APSInt res; return expr->isIntegerConstantExpr(res, compiler.getASTContext()) && res == 0; } void StringConstant::reportChange( Expr const * expr, ChangeKind kind, std::string const & original, std::string const & replacement, PassThrough pass, char const * rewriteFrom, char const * rewriteTo) { assert((rewriteFrom == nullptr) == (rewriteTo == nullptr)); if (pass != PassThrough::No && !calls_.empty()) { Expr const * call = calls_.top(); CallExpr::const_arg_iterator argsBeg; CallExpr::const_arg_iterator argsEnd; if (isa(call)) { argsBeg = cast(call)->arg_begin(); argsEnd = cast(call)->arg_end(); } else if (isa(call)) { argsBeg = cast(call)->arg_begin(); argsEnd = cast(call)->arg_end(); } else { assert(false); } for (auto i(argsBeg); i != argsEnd; ++i) { Expr const * e = (*i)->IgnoreParenImpCasts(); if (isa(e)) { e = cast(e)->getSubExpr() ->IgnoreParenImpCasts(); } if (e == expr) { if (isa(call)) { FunctionDecl const * fdecl = cast(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 " + original + (" with empty string constant argument as" " call of rtl::OUString::isEmpty")), getMemberLocation(call)) << fdecl->getQualifiedNameAsString() << call->getSourceRange(); return; } if (dc.Operator(OO_ExclaimEqual).Namespace("rtl") .GlobalNamespace()) { report( DiagnosticsEngine::Warning, ("rewrite call of %0 with call of " + original + (" with empty string constant argument as" " call of !rtl::OUString::isEmpty")), getMemberLocation(call)) << fdecl->getQualifiedNameAsString() << 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 suspicous call of " + original + " with empty string constant argument"), getMemberLocation(call)) << fdecl->getQualifiedNameAsString() << call->getSourceRange(); return; } if (dc.Operator(OO_Equal).Class("OUString") .Namespace("rtl").GlobalNamespace()) { report( DiagnosticsEngine::Warning, ("rewrite call of %0 with call of " + original + (" with empty string constant argument as" " call of rtl::OUString::call")), getMemberLocation(call)) << fdecl->getQualifiedNameAsString() << call->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 " + original + " with " + describeChangeKind(kind) + " in call of %0"), getMemberLocation(expr)) << fdecl->getQualifiedNameAsString() << expr->getSourceRange(); return; } if ((dc.Operator(OO_Plus).Namespace("rtl") .GlobalNamespace()) || (dc.Operator(OO_Plus).Class("OUString") .Namespace("rtl").GlobalNamespace())) { report( DiagnosticsEngine::Warning, ("rewrite call of " + original + " with " + describeChangeKind(kind) + (" in call of %0 as (implicit) construction" " of rtl::OUString")), getMemberLocation(expr)) << fdecl->getQualifiedNameAsString() << expr->getSourceRange(); return; } } report( DiagnosticsEngine::Warning, "TODO call inside %0", getMemberLocation(expr)) << fdecl->getQualifiedNameAsString() << expr->getSourceRange(); return; } else if (isa(call)) { auto cdecl = cast(call)->getConstructor() ->getParent(); loplugin::DeclCheck dc(cdecl); 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 " + original + (" with empty string constant argument as" " default construction of %0")), getMemberLocation(call)) << cdecl->getQualifiedNameAsString() << call->getSourceRange(); } else { assert(pass == PassThrough::NonEmptyConstantString); report( DiagnosticsEngine::Warning, ("elide call of " + original + " with " + describeChangeKind(kind) + " in construction of %0"), getMemberLocation(expr)) << cdecl->getQualifiedNameAsString() << expr->getSourceRange(); } return; } } else { assert(false); } } } } if (rewriter != nullptr && rewriteFrom != nullptr) { SourceLocation loc = getMemberLocation(expr); while (compiler.getSourceManager().isMacroArgExpansion(loc)) { loc = compiler.getSourceManager().getImmediateMacroCallerLoc(loc); } if (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 " + original + " with " + describeChangeKind(kind) + " as call of " + replacement), getMemberLocation(expr)) << 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 suspicous 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 non; bool emb; bool trm; if (!isStringConstant( expr->getArg(arg)->IgnoreParenImpCasts(), &n, &non, &emb, &trm)) { return; } if (non) { report( DiagnosticsEngine::Warning, ("call of %0 with string constant argument containging non-ASCII" " characters"), getMemberLocation(expr)) << callee->getQualifiedNameAsString() << expr->getSourceRange(); return; } if (emb) { report( DiagnosticsEngine::Warning, ("call of %0 with string constant argument containging embedded" " NULs"), getMemberLocation(expr)) << callee->getQualifiedNameAsString() << expr->getSourceRange(); return; } if (!trm) { report( DiagnosticsEngine::Warning, ("call of %0 with string constant argument lacking a terminating" " NUL"), getMemberLocation(expr)) << callee->getQualifiedNameAsString() << expr->getSourceRange(); return; } std::string repl(replacement); checkEmpty(expr, callee, treatEmpty, n, &repl); reportChange( expr, ChangeKind::Char, callee->getQualifiedNameAsString(), repl, (literal ? (n == 0 ? PassThrough::EmptyConstantString : PassThrough::NonEmptyConstantString) : PassThrough::No), 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 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( expr->getArg(arg1)->IgnoreParenImpCasts()); if (op == nullptr || op->getOpcode() != UO_AddrOf) { return; } ArraySubscriptExpr const * subs = dyn_cast( 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 %0 with string constant argument containging 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, nullptr, nullptr); } void StringConstant::handleOUStringCtor( CallExpr const * expr, unsigned arg, FunctionDecl const * callee, bool explicitFunctionalCastNotation) { auto e0 = expr->getArg(arg)->IgnoreParenImpCasts(); auto e1 = dyn_cast(e0); if (e1 == nullptr) { if (explicitFunctionalCastNotation) { return; } } else { e0 = e1->getSubExpr()->IgnoreParenImpCasts(); } auto e2 = dyn_cast(e0); if (e2 == nullptr) { return; } auto e3 = dyn_cast( e2->getSubExpr()->IgnoreParenImpCasts()); if (e3 == nullptr) { return; } if (e3->getConstructor()->getQualifiedNameAsString() != "rtl::OUString::OUString") { return; } if (e3->getNumArgs() == 0) { report( DiagnosticsEngine::Warning, ("in call of %0, replace default-constructed OUString with an empty" " string literal"), e3->getExprLoc()) << callee->getQualifiedNameAsString() << expr->getSourceRange(); return; } if (e3->getNumArgs() == 1 && e3->getConstructor()->getNumParams() == 1 && (loplugin::TypeCheck( e3->getConstructor()->getParamDecl(0)->getType()) .Typedef("sal_Unicode").GlobalNamespace())) { // It may not be easy to rewrite OUString(c), esp. given there is no // OUString ctor taking an OUStringLiteral1 arg, so don't warn there: if (!explicitFunctionalCastNotation) { report( DiagnosticsEngine::Warning, ("in call of %0, replace OUString constructed from a" " sal_Unicode with an OUStringLiteral1"), e3->getExprLoc()) << callee->getQualifiedNameAsString() << expr->getSourceRange(); } return; } if (e3->getNumArgs() != 2) { return; } unsigned n; bool non; bool emb; bool trm; if (!isStringConstant( e3->getArg(0)->IgnoreParenImpCasts(), &n, &non, &emb, &trm)) { return; } //TODO: non, emb, trm if (rewriter != nullptr) { auto loc1 = e3->getLocStart(); auto range = e3->getParenOrBraceRange(); if (loc1.isFileID() && range.getBegin().isFileID() && range.getEnd().isFileID()) { auto loc2 = range.getBegin(); for (bool first = true;; first = false) { unsigned n = Lexer::MeasureTokenLength( loc2, compiler.getSourceManager(), compiler.getLangOpts()); if (!first) { StringRef s( compiler.getSourceManager().getCharacterData(loc2), n); while (s.startswith("\\\n")) { s = s.drop_front(2); while (!s.empty() && (s.front() == ' ' || s.front() == '\t' || s.front() == '\n' || s.front() == '\v' || s.front() == '\f')) { s = s.drop_front(1); } } if (!(s.empty() || s.startswith("/*") || s.startswith("//") || s == "\\")) { break; } } loc2 = loc2.getLocWithOffset(std::max(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(); } loplugin::Plugin::Registration< StringConstant > X("stringconstant", true); } /* vim:set shiftwidth=4 softtabstop=4 expandtab: */