diff options
Diffstat (limited to '')
-rw-r--r-- | compilerplugins/clang/stringconstant.cxx | 2259 |
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: */ |