/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */ /* * 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 "config_clang.h" #include "check.hxx" #include "functionaddress.hxx" #include "plugin.hxx" // Find various of type rtl::O[U]String that can be converted to std::[u16]string_view instead. // This can generate false positives e.g. // OUString sSave( aToken ); // aToken.append("foo"); // aToken = sSave; // where the data that is backing the view is modified and then the view is used to assign to // the same source data. namespace { enum class StringType { None, RtlOstring, RtlOustring }; StringType relevantStringType(QualType type) { loplugin::TypeCheck const c(type); if (c.Class("OString").Namespace("rtl")) { return StringType::RtlOstring; } else if (c.Class("OUString").Namespace("rtl")) { return StringType::RtlOustring; } else { return StringType::None; } } bool relevantVarDecl(VarDecl const* decl) { auto const t1 = decl->getType(); if (relevantStringType(t1.getNonReferenceType()) == StringType::None) { return false; } if (isa(decl)) { return false; } return true; } DeclRefExpr const* relevantDeclRefExpr(Expr const* expr) { //TODO: Look through BO_Comma and AbstractConditionalOperator auto const e = dyn_cast(expr->IgnoreParenImpCasts()); if (e == nullptr) { return nullptr; } auto const d = dyn_cast(e->getDecl()); if (d == nullptr) { return nullptr; } if (!relevantVarDecl(d)) { return nullptr; } return e; } bool isStringView(QualType qt) { return bool(loplugin::TypeCheck(qt).ClassOrStruct("basic_string_view").StdNamespace()); } DeclRefExpr const* relevantImplicitCastExpr(ImplicitCastExpr const* expr) { if (!isStringView(expr->getType())) { return nullptr; } return relevantDeclRefExpr(expr->getSubExprAsWritten()); } DeclRefExpr const* relevantCStyleCastExpr(CStyleCastExpr const* expr) { if (expr->getCastKind() != CK_ToVoid) { return nullptr; } return relevantDeclRefExpr(expr->getSubExprAsWritten()); } DeclRefExpr const* relevantCXXMemberCallExpr(CXXMemberCallExpr const* expr) { StringType t = relevantStringType(expr->getObjectType()); if (t == StringType::None) { return nullptr; } bool good = false; auto const d = expr->getMethodDecl(); if (d->getOverloadedOperator() == OO_Subscript) { good = true; } else if (auto const i = d->getIdentifier()) { auto const n = i->getName(); if (n == "endsWith" || n == "isEmpty" || n == "startsWith" || n == "subView" || n == "indexOf" || n == "lastIndexOf" || n == "compareTo" || n == "match" || n == "trim" || n == "toInt32" || n == "toInt64" || n == "toDouble" || n == "equalsIgnoreAsciiCase" || n == "compareToIgnoreAsciiCase" || n == "getToken" || n == "copy") { good = true; } #if 0 //TODO: rtl::O[U]String::getLength would be awkward to replace with // std::[u16]string_view::length/size due to the sal_Int32 vs. std::size_t return type // mismatch (C++20 ssize might make that easier, though); and while rtl::OString::getStr is // documented to be NUL-terminated (so not eligible for replacement with // std::string_view::data in general), rtl::OUString::getStr is not (so should be eligible // for replacement with std::u16string_view::data, but some call sites might nevertheless // incorrectly rely on NUL termination, so any replacement would need careful review): if (n == "getLength" || (t == StringType::RtlOustring && n == "getStr")) { good = true; } #endif } if (!good) { return nullptr; } return relevantDeclRefExpr(expr->getImplicitObjectArgument()); } SmallVector wrap(DeclRefExpr const* expr) { if (expr == nullptr) { return {}; } return { expr }; } SmallVector relevantCXXOperatorCallExpr(CXXOperatorCallExpr const* expr) { auto const op = expr->getOperator(); if (op == OO_Subscript) { auto const e = expr->getArg(0); if (relevantStringType(e->getType()) == StringType::None) { return {}; } return wrap(relevantDeclRefExpr(e)); } if (expr->isComparisonOp() || (op == OO_Plus && expr->getNumArgs() == 2)) { SmallVector v; if (auto const e = relevantDeclRefExpr(expr->getArg(0))) { v.push_back(e); } if (auto const e = relevantDeclRefExpr(expr->getArg(1))) { v.push_back(e); } return v; } if (op == OO_PlusEqual) { if (relevantStringType(expr->getArg(0)->getType()) != StringType::RtlOustring) { return {}; } return wrap(relevantDeclRefExpr(expr->getArg(1))); } if (op == OO_Equal) { if (!isStringView(expr->getArg(1)->getType())) { return {}; } return wrap(relevantDeclRefExpr(expr->getArg(0))); } return {}; } static const Expr* IgnoreImplicitAndConversionOperator(const Expr* expr) { expr = expr->IgnoreImplicit(); if (auto memberCall = dyn_cast(expr)) { if (auto conversionDecl = dyn_cast_or_null(memberCall->getMethodDecl())) { if (!conversionDecl->isExplicit()) expr = memberCall->getImplicitObjectArgument()->IgnoreImplicit(); } } return expr; } class StringViewVar final : public loplugin::FunctionAddress> { public: explicit StringViewVar(loplugin::InstantiationData const& data) : FunctionAddress(data) { } bool VisitVarDecl(VarDecl* decl) { if (ignoreLocation(decl)) { return true; } if (decl->hasGlobalStorage()) { return true; } if (!decl->isThisDeclarationADefinition()) { return true; } if (!relevantVarDecl(decl)) { return true; } if (decl->getInit()) { auto expr = IgnoreImplicitAndConversionOperator(decl->getInit()); if (auto castExpr = dyn_cast(expr)) { expr = IgnoreImplicitAndConversionOperator(castExpr->getSubExpr()); } if (auto cxxConstruct = dyn_cast(expr)) { if (cxxConstruct->getNumArgs() == 0) currentVars_.insert(decl); // default constructor else if (cxxConstruct->getNumArgs() == 1 && isStringView(cxxConstruct->getArg(0)->getType())) currentVars_.insert(decl); } } return true; } bool TraverseImplicitCastExpr(ImplicitCastExpr* expr) { if (ignoreLocation(expr)) { return true; } auto const e = relevantImplicitCastExpr(expr); if (e == nullptr) { return FunctionAddress::TraverseImplicitCastExpr(expr); } currentGoodUses_.insert(e); auto const ret = FunctionAddress::TraverseImplicitCastExpr(expr); currentGoodUses_.erase(e); return ret; } bool TraverseCStyleCastExpr(CStyleCastExpr* expr) { if (ignoreLocation(expr)) { return true; } auto const e = relevantCStyleCastExpr(expr); if (e == nullptr) { return FunctionAddress::TraverseCStyleCastExpr(expr); } currentGoodUses_.insert(e); auto const ret = FunctionAddress::TraverseCStyleCastExpr(expr); currentGoodUses_.erase(e); return ret; } bool TraverseCXXMemberCallExpr(CXXMemberCallExpr* expr) { if (ignoreLocation(expr)) { return true; } auto const e = relevantCXXMemberCallExpr(expr); if (e == nullptr) { return FunctionAddress::TraverseCXXMemberCallExpr(expr); } currentGoodUses_.insert(e); auto const ret = FunctionAddress::TraverseCXXMemberCallExpr(expr); currentGoodUses_.erase(e); return ret; } bool TraverseCXXOperatorCallExpr(CXXOperatorCallExpr* expr) { if (ignoreLocation(expr)) { return true; } auto const es = relevantCXXOperatorCallExpr(expr); if (es.empty()) { return FunctionAddress::TraverseCXXOperatorCallExpr(expr); } currentGoodUses_.insert(es.begin(), es.end()); auto const ret = FunctionAddress::TraverseCXXOperatorCallExpr(expr); for (auto const i : es) { currentGoodUses_.erase(i); } return ret; } bool VisitDeclRefExpr(DeclRefExpr* expr) { if (!FunctionAddress::VisitDeclRefExpr(expr)) { return false; } if (ignoreLocation(expr)) { return true; } if (currentGoodUses_.find(expr) != currentGoodUses_.end()) { return true; } if (auto const d = dyn_cast(expr->getDecl())) { currentVars_.erase(d); } return true; } private: void run() override { if (!compiler.getLangOpts().CPlusPlus) { return; } if (compiler.getPreprocessor().getIdentifierInfo("NDEBUG")->hasMacroDefinition()) { return; } StringRef fn(handler.getMainFileName()); // leave the string QA tests alone if (loplugin::hasPathnamePrefix(fn, SRCDIR "/sal/qa/")) { return; } // false + if (loplugin::hasPathnamePrefix(fn, SRCDIR "/svtools/source/svrtf/parrtf.cxx")) { return; } if (!TraverseDecl(compiler.getASTContext().getTranslationUnitDecl())) { return; } for (auto const i : currentVars_) { auto const t = relevantStringType(i->getType().getNonReferenceType()); report(DiagnosticsEngine::Warning, "replace var of type %0 with " "'%select{std::string_view|std::u16string_view}1'", i->getLocation()) << i->getType() << (int(t) - 1) << i->getSourceRange(); } } std::set currentVars_; std::set currentGoodUses_; }; static loplugin::Plugin::Registration reg("stringviewvar", false); // might generate false+ } /* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */