| /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ |
| /* |
| * This file is part of the Collabora Office 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/. |
| */ |
| #ifndef LO_CLANG_SHARED_PLUGINS |
| |
| #include <cassert> |
| #include <string> |
| #include <iostream> |
| #include <unordered_map> |
| #include <unordered_set> |
| |
| #include "plugin.hxx" |
| #include "check.hxx" |
| #include "config_clang.h" |
| #include "clang/AST/CXXInheritance.h" |
| #include "clang/AST/StmtVisitor.h" |
| |
| /** |
| Look for places where we are making a substring copy of an OUString, and then passing it to a |
| function that takes a u16string_view, in which case it is more efficient to pass a view |
| of the OUString, rather than making a copy. |
| |
| TODO currently does not check if there is some other visible overload of the callee, that can take |
| a string_view. |
| TODO handle OUStringBuffer/OStringBuffer similarly |
| */ |
| |
| namespace |
| { |
| class StringView : public loplugin::FilteringPlugin<StringView> |
| { |
| public: |
| explicit StringView(loplugin::InstantiationData const& data) |
| : FilteringPlugin(data) |
| { |
| } |
| |
| bool preRun() override |
| { |
| auto const fn = handler.getMainFileName(); |
| return !(loplugin::isSamePathname(fn, SRCDIR "/sal/qa/OStringBuffer/rtl_OStringBuffer.cxx") |
| || loplugin::hasPathnamePrefix(fn, SRCDIR "/sal/qa/rtl/strings/") |
| || loplugin::hasPathnamePrefix(fn, SRCDIR "/sal/qa/rtl/oustring/")); |
| } |
| |
| virtual void run() override |
| { |
| if (!preRun()) |
| return; |
| TraverseDecl(compiler.getASTContext().getTranslationUnitDecl()); |
| } |
| |
| bool VisitFunctionDecl(FunctionDecl const*); |
| bool VisitCXXOperatorCallExpr(CXXOperatorCallExpr const*); |
| bool VisitImplicitCastExpr(ImplicitCastExpr const*); |
| bool VisitCXXMemberCallExpr(CXXMemberCallExpr const*); |
| bool VisitCXXConstructExpr(CXXConstructExpr const*); |
| |
| private: |
| void handleSubExprThatCouldBeView(Expr const* expr); |
| void handleCXXConstructExpr(CXXConstructExpr const* expr); |
| void handleCXXMemberCallExpr(CXXMemberCallExpr const* expr); |
| }; |
| |
| bool StringView::VisitCXXOperatorCallExpr(CXXOperatorCallExpr const* cxxOperatorCallExpr) |
| { |
| if (ignoreLocation(cxxOperatorCallExpr)) |
| return true; |
| |
| auto op = cxxOperatorCallExpr->getOperator(); |
| if (op == OO_Plus && cxxOperatorCallExpr->getNumArgs() == 2) |
| { |
| handleSubExprThatCouldBeView(cxxOperatorCallExpr->getArg(0)); |
| handleSubExprThatCouldBeView(cxxOperatorCallExpr->getArg(1)); |
| } |
| if (cxxOperatorCallExpr->isComparisonOp()) |
| { |
| handleSubExprThatCouldBeView(cxxOperatorCallExpr->getArg(0)); |
| handleSubExprThatCouldBeView(cxxOperatorCallExpr->getArg(1)); |
| } |
| else if (op == OO_PlusEqual) |
| handleSubExprThatCouldBeView(cxxOperatorCallExpr->getArg(1)); |
| else if (op == OO_Subscript) |
| handleSubExprThatCouldBeView(cxxOperatorCallExpr->getArg(0)); |
| else if (op == OO_Equal) |
| { |
| if (loplugin::TypeCheck(cxxOperatorCallExpr->getType()) |
| .Class("OUStringBuffer") |
| .Namespace("rtl") |
| .GlobalNamespace() |
| || loplugin::TypeCheck(cxxOperatorCallExpr->getType()) |
| .Class("OStringBuffer") |
| .Namespace("rtl") |
| .GlobalNamespace()) |
| { |
| handleSubExprThatCouldBeView(cxxOperatorCallExpr->getArg(1)); |
| } |
| } |
| return true; |
| } |
| |
| bool StringView::VisitFunctionDecl(FunctionDecl const* functionDecl) |
| { |
| if (ignoreLocation(functionDecl)) |
| return true; |
| // debugging |
| // if (functionDecl->getIdentifier() && functionDecl->getName() == "f1") |
| // functionDecl->dump(); |
| return true; |
| } |
| |
| bool StringView::VisitImplicitCastExpr(ImplicitCastExpr const* expr) |
| { |
| if (ignoreLocation(expr)) |
| { |
| return true; |
| } |
| if (!loplugin::TypeCheck(expr->getType()).ClassOrStruct("basic_string_view").StdNamespace()) |
| { |
| return true; |
| } |
| handleSubExprThatCouldBeView(expr->getSubExprAsWritten()); |
| return true; |
| } |
| |
| void StringView::handleSubExprThatCouldBeView(Expr const* subExpr) |
| { |
| auto const e0 = subExpr->IgnoreImplicit(); |
| auto const e = e0->IgnoreParens(); |
| auto const tc = loplugin::TypeCheck(e->getType()); |
| if (!(tc.Class("OString").Namespace("rtl").GlobalNamespace() |
| || tc.Class("OUString").Namespace("rtl").GlobalNamespace() |
| || tc.Class("OUStringBuffer").Namespace("rtl").GlobalNamespace())) |
| { |
| return; |
| } |
| if (auto const e1 = dyn_cast<CXXConstructExpr>(e)) |
| { |
| if (e0 == subExpr) |
| { |
| handleCXXConstructExpr(e1); |
| } |
| } |
| else if (auto const e2 = dyn_cast<CXXFunctionalCastExpr>(e)) |
| { |
| auto e3 = e2->getSubExpr(); |
| if (auto const e4 = dyn_cast<CXXBindTemporaryExpr>(e3)) |
| { |
| e3 = e4->getSubExpr(); |
| } |
| if (auto const e4 = dyn_cast<CXXConstructExpr>(e3)) |
| { |
| handleCXXConstructExpr(e4); |
| } |
| } |
| else if (auto const e3 = dyn_cast<CXXMemberCallExpr>(e)) |
| { |
| handleCXXMemberCallExpr(e3); |
| } |
| } |
| |
| void StringView::handleCXXConstructExpr(CXXConstructExpr const* expr) |
| { |
| QualType argType; |
| enum |
| { |
| None, |
| OrChar, |
| ViaConcatenation |
| } extra |
| = None; |
| auto const d = expr->getConstructor(); |
| switch (d->getNumParams()) |
| { |
| case 0: |
| break; |
| case 1: |
| { |
| auto const t = d->getParamDecl(0)->getType(); |
| if (t->isAnyCharacterType()) |
| { |
| argType = expr->getArg(0)->IgnoreImplicit()->getType(); |
| extra = OrChar; |
| break; |
| } |
| loplugin::TypeCheck tc(t); |
| if (tc.RvalueReference().Struct("StringNumber").Namespace("rtl").GlobalNamespace() |
| || tc.ClassOrStruct("basic_string_view").StdNamespace()) |
| { |
| argType = expr->getArg(0)->IgnoreImplicit()->getType(); |
| break; |
| } |
| if (tc.RvalueReference().Struct("StringConcat").Namespace("rtl").GlobalNamespace()) |
| { |
| argType = expr->getArg(0)->IgnoreImplicit()->getType(); |
| extra = ViaConcatenation; |
| break; |
| } |
| return; |
| } |
| case 2: |
| { |
| auto const t0 = d->getParamDecl(0)->getType(); |
| if (t0->isPointerType() && t0->getPointeeType()->isAnyCharacterType()) |
| { |
| auto const t = d->getParamDecl(1)->getType(); |
| if (t->isIntegralType(compiler.getASTContext()) |
| && !(t->isBooleanType() || t->isAnyCharacterType())) |
| { |
| auto const arg = expr->getArg(1); |
| if (!arg->isValueDependent()) |
| { |
| if (auto const val = arg->getIntegerConstantExpr(compiler.getASTContext())) |
| { |
| if (val->getExtValue() == 1) |
| { |
| extra = OrChar; |
| } |
| } |
| } |
| argType = expr->getArg(0)->IgnoreImplicit()->getType(); |
| break; |
| } |
| } |
| if (loplugin::TypeCheck(d->getParamDecl(1)->getType()) |
| .Struct("Dummy") |
| .Namespace("libreoffice_internal") |
| .Namespace("rtl") |
| .GlobalNamespace()) |
| { |
| argType = expr->getArg(0)->IgnoreImplicit()->getType(); |
| break; |
| } |
| return; |
| } |
| default: |
| return; |
| } |
| report(DiagnosticsEngine::Warning, |
| "instead of an %0%select{| constructed from a %2}1, pass a" |
| " '%select{std::string_view|std::u16string_view}3'" |
| "%select{| (or an '%select{rtl::OStringChar|rtl::OUStringChar}3')|" |
| " via 'rtl::Concat2View'}4", |
| expr->getExprLoc()) |
| << expr->getType() << (argType.isNull() ? 0 : 1) << argType |
| << (loplugin::TypeCheck(expr->getType()).Class("OString").Namespace("rtl").GlobalNamespace() |
| ? 0 |
| : 1) |
| << extra << expr->getSourceRange(); |
| } |
| |
| void StringView::handleCXXMemberCallExpr(CXXMemberCallExpr const* expr) |
| { |
| auto const dc1 = loplugin::DeclCheck(expr->getMethodDecl()); |
| if (auto const dc2 = dc1.Function("copy")) |
| { |
| if (dc2.Class("OString").Namespace("rtl").GlobalNamespace() |
| || dc2.Class("OUString").Namespace("rtl").GlobalNamespace() |
| || dc2.Class("OUStringBuffer").Namespace("rtl").GlobalNamespace()) |
| { |
| report(DiagnosticsEngine::Warning, "rather than copy, pass with a view using subView()", |
| expr->getExprLoc()) |
| << expr->getSourceRange(); |
| } |
| return; |
| } |
| if (auto const dc2 = dc1.Function("getToken")) |
| { |
| if (dc2.Class("OString").Namespace("rtl").GlobalNamespace() |
| || dc2.Class("OUString").Namespace("rtl").GlobalNamespace() |
| || dc2.Class("OUStringBuffer").Namespace("rtl").GlobalNamespace()) |
| { |
| report(DiagnosticsEngine::Warning, |
| "rather than getToken, pass with a view using o3tl::getToken()", |
| expr->getExprLoc()) |
| << expr->getSourceRange(); |
| } |
| return; |
| } |
| if (auto const dc2 = dc1.Function("trim")) |
| { |
| if (dc2.Class("OString").Namespace("rtl").GlobalNamespace() |
| || dc2.Class("OUString").Namespace("rtl").GlobalNamespace() |
| || dc2.Class("OUStringBuffer").Namespace("rtl").GlobalNamespace()) |
| { |
| report(DiagnosticsEngine::Warning, |
| "rather than trim, pass with a view using o3tl::trim()", expr->getExprLoc()) |
| << expr->getSourceRange(); |
| } |
| return; |
| } |
| if (auto const dc2 = dc1.Function("makeStringAndClear")) |
| { |
| if (dc2.Class("OStringBuffer").Namespace("rtl").GlobalNamespace() |
| || dc2.Class("OUStringBuffer").Namespace("rtl").GlobalNamespace()) |
| { |
| auto const obj = expr->getImplicitObjectArgument(); |
| if (!(obj->isLValue() || obj->getType()->isPointerType())) |
| { |
| report(DiagnosticsEngine::Warning, |
| "rather than call makeStringAndClear on an rvalue, pass with a view", |
| expr->getExprLoc()) |
| << expr->getSourceRange(); |
| } |
| } |
| return; |
| } |
| if (auto const dc2 = dc1.Function("toString")) |
| { |
| if (dc2.Class("OStringBuffer").Namespace("rtl").GlobalNamespace() |
| || dc2.Class("OUStringBuffer").Namespace("rtl").GlobalNamespace()) |
| { |
| report(DiagnosticsEngine::Warning, "rather than call toString, pass with a view", |
| expr->getExprLoc()) |
| << expr->getSourceRange(); |
| } |
| return; |
| } |
| } |
| |
| bool StringView::VisitCXXMemberCallExpr(CXXMemberCallExpr const* expr) |
| { |
| if (ignoreLocation(expr)) |
| { |
| return true; |
| } |
| /** check for calls to O[U]StringBuffer::append that could be passed as a |
| std::u16string_view */ |
| if (loplugin::TypeCheck(expr->getType()) |
| .Class("OUStringBuffer") |
| .Namespace("rtl") |
| .GlobalNamespace() |
| || loplugin::TypeCheck(expr->getType()) |
| .Class("OStringBuffer") |
| .Namespace("rtl") |
| .GlobalNamespace()) |
| { |
| auto const dc = loplugin::DeclCheck(expr->getMethodDecl()); |
| if (dc.Function("append") || dc.Function("indexOf") || dc.Function("lastIndexOf")) |
| { |
| handleSubExprThatCouldBeView(expr->getArg(0)); |
| } |
| else if (dc.Function("insert")) |
| { |
| handleSubExprThatCouldBeView(expr->getArg(1)); |
| } |
| } |
| |
| // rather than getToken...toInt32, use o3tl::toInt(o3tl::getToken(...) |
| auto tc = loplugin::TypeCheck(expr->getImplicitObjectArgument()->getType()); |
| if (tc.Class("OUString").Namespace("rtl").GlobalNamespace() |
| || tc.Class("OString").Namespace("rtl").GlobalNamespace()) |
| { |
| auto const dc = loplugin::DeclCheck(expr->getMethodDecl()); |
| if (dc.Function("toInt32") || dc.Function("toUInt32") || dc.Function("toInt64") |
| || dc.Function("toDouble") || dc.Function("equalsAscii") || dc.Function("equalsAsciiL") |
| || dc.Function("equalsIgnoreAsciiCase") || dc.Function("compareToIgnoreAsciiCase") |
| || dc.Function("matchIgnoreAsciiCase") || dc.Function("trim") |
| || dc.Function("startsWith") || dc.Function("endsWith") || dc.Function("match") |
| || dc.Function("isEmpty") || dc.Function("getLength") |
| || dc.Function("iterateCodePoints")) |
| { |
| handleSubExprThatCouldBeView(expr->getImplicitObjectArgument()); |
| } |
| } |
| return true; |
| } |
| |
| /** check for calls to O[U]StringBuffer constructor that could be passed as a |
| std::u16string_view */ |
| bool StringView::VisitCXXConstructExpr(CXXConstructExpr const* expr) |
| { |
| if (ignoreLocation(expr)) |
| { |
| return true; |
| } |
| if (!loplugin::TypeCheck(expr->getType()) |
| .Class("OUStringBuffer") |
| .Namespace("rtl") |
| .GlobalNamespace() |
| && !loplugin::TypeCheck(expr->getType()) |
| .Class("OStringBuffer") |
| .Namespace("rtl") |
| .GlobalNamespace()) |
| { |
| return true; |
| } |
| if (!compiler.getLangOpts().CPlusPlus17 && expr->isElidable()) // external C++03 code |
| { |
| return true; |
| } |
| if (expr->getNumArgs() > 0) |
| handleSubExprThatCouldBeView(expr->getArg(0)); |
| return true; |
| } |
| |
| loplugin::Plugin::Registration<StringView> stringview("stringview"); |
| } |
| |
| #endif // LO_CLANG_SHARED_PLUGINS |
| |
| /* vim:set shiftwidth=4 softtabstop=4 expandtab: */ |