/* -*- 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/. */ #ifndef LO_CLANG_SHARED_PLUGINS #include #include #include #include "check.hxx" #include "plugin.hxx" // Find cases where a variable of a OString/OUString type is initialized // with a literal value (incl. as an empty string) and used only once. Conservatively this only // covers local non-static variables that are not defined outside of the loop (if any) in which they // are used, as other cases may deliberately use the variable for performance (or even correctness, // if addresses are taken and compared) reasons. // // For one, the historically heavy syntax for such uses of string literals // (RTL_CONSTASCII_USTRINGPARAM etc.) probably explains many of these redundant variables, which can // now be considered cargo-cult baggage. For another, some of those variables are used as arguments // to functions which also have more efficient overloads directly taking string literals. And for // yet another, some cases with default-initialized variables turned out to be effectively unused // code that could be removed completely (d073cca5f7c04de3e1bcedda334d864e98ac7835 "Clean up dead // code", 91345e7dde6100496a7c9e815b72b2821ae82bc2 "Clean up dead code", // 868b0763ac47f765cb48c277897274a595b831d0 "Upcoming loplugin:elidestringvar: dbaccess" in // dbaccess/source/ui/app/AppController.cxx, bde0aac4ccf7b830b5ef21d5b9e75e62aee6aaf9 "Clean up dead // code", 354aefec42de856b4ab6201ada54a3a3c630b4bd "Upcoming loplugin:elidestringvar: cui" in // cui/source/dialogs/SpellDialog.cxx). namespace { bool isStringType(QualType type) { loplugin::TypeCheck const c(type); return c.Class("OString").Namespace("rtl").GlobalNamespace() || c.Class("OUString").Namespace("rtl").GlobalNamespace(); } class ElideStringVar : public loplugin::FilteringPlugin { public: explicit ElideStringVar(loplugin::InstantiationData const& data) : FilteringPlugin(data) { } bool preRun() override { return compiler.getLangOpts().CPlusPlus; } void postRun() override { for (auto const& var : vars_) { if (!var.second.singleUse || *var.second.singleUse == nullptr) { continue; } if (containsPreprocessingConditionalInclusion( SourceRange(var.first->getBeginLoc(), (*var.second.singleUse)->getEndLoc()))) { // This is not perfect, as additional uses can be hidden in conditional blocks that // only start after the (would-be) single use (as was the case in // 3bc5057f9689e024957cfa898a221ee2c4c4afe7 "Upcoming loplugin:elidestringvar: // testtools" when built with --enable-debug, but where also fixing the hidden // additional use was trivial). If this ever becomes a real problem, we can extend // the above check to cover more of the current function body's remainder. continue; } report(DiagnosticsEngine::Warning, "replace single use of literal %0 variable with a literal", (*var.second.singleUse)->getExprLoc()) << var.first->getType() << (*var.second.singleUse)->getSourceRange(); report(DiagnosticsEngine::Note, "literal %0 variable defined here", var.first->getLocation()) << var.first->getType() << var.first->getSourceRange(); } } bool VisitVarDecl(VarDecl const* decl) { if (ignoreLocation(decl)) { return true; } if (!decl->isThisDeclarationADefinition()) { return true; } if (isa(decl)) { return true; } if (decl->getStorageDuration() != SD_Automatic) { return true; } if (!isStringType(decl->getType())) { return true; } if (!decl->hasInit()) { return true; } auto const e1 = dyn_cast(decl->getInit()->IgnoreParenImpCasts()); if (e1 == nullptr) { return true; } if (!isStringType(e1->getType())) { return true; } switch (e1->getNumArgs()) { case 0: break; case 1: { auto const e2 = e1->getArg(0); loplugin::TypeCheck const c(e2->getType()); if (c.Class("OStringLiteral").Namespace("rtl").GlobalNamespace() || c.Class("OUStringLiteral").Namespace("rtl").GlobalNamespace()) { break; } if (!e2->isValueDependent() && e2->isIntegerConstantExpr(compiler.getASTContext())) { break; } return true; } case 2: { auto const e2 = e1->getArg(0); auto const t = e2->getType(); if (!(t.isConstQualified() && t->isConstantArrayType())) { return true; } if (isa(e2->IgnoreParenImpCasts())) { return true; } auto const e3 = e1->getArg(1); if (!(isa(e3) && loplugin::TypeCheck(e3->getType()) .Struct("Dummy") .Namespace("libreoffice_internal") .Namespace("rtl") .GlobalNamespace())) { return true; } break; } default: return true; } auto const ok = vars_.emplace(decl, Data(getInnermostLoop())); assert(ok.second); (void)ok; return true; } bool VisitDeclRefExpr(DeclRefExpr const* expr) { if (ignoreLocation(expr)) { return true; } auto const var = dyn_cast(expr->getDecl()); if (var == nullptr) { return true; } auto const i = vars_.find(var); if (i == vars_.end()) { return true; } i->second.singleUse = i->second.singleUse || i->second.innermostLoop != getInnermostLoop() ? nullptr : expr; return true; } bool VisitMemberExpr(MemberExpr const* expr) { if (ignoreLocation(expr)) { return true; } auto const e = dyn_cast(expr->getBase()->IgnoreParenImpCasts()); if (e == nullptr) { return true; } auto const var = dyn_cast(e->getDecl()); if (var == nullptr) { return true; } auto const i = vars_.find(var); if (i == vars_.end()) { return true; } i->second.singleUse = nullptr; return true; } bool VisitUnaryOperator(UnaryOperator const* expr) { if (ignoreLocation(expr)) { return true; } if (expr->getOpcode() != UO_AddrOf) { return true; } auto const e = dyn_cast(expr->getSubExpr()->IgnoreParenImpCasts()); if (e == nullptr) { return true; } auto const var = dyn_cast(e->getDecl()); if (var == nullptr) { return true; } auto const i = vars_.find(var); if (i == vars_.end()) { return true; } i->second.singleUse = nullptr; return true; } bool VisitCallExpr(CallExpr const* expr) { if (ignoreLocation(expr)) { return true; } auto const fun = expr->getDirectCallee(); if (fun == nullptr) { return true; } unsigned const n = std::min(fun->getNumParams(), expr->getNumArgs()); for (unsigned i = 0; i != n; ++i) { if (!loplugin::TypeCheck(fun->getParamDecl(i)->getType()) .LvalueReference() .NonConstVolatile()) { continue; } auto const e = dyn_cast(expr->getArg(i)->IgnoreParenImpCasts()); if (e == nullptr) { continue; } auto const var = dyn_cast(e->getDecl()); if (var == nullptr) { continue; } auto const j = vars_.find(var); if (j == vars_.end()) { continue; } j->second.singleUse = nullptr; } return true; } bool VisitCXXConstructExpr(CXXConstructExpr const* expr) { if (ignoreLocation(expr)) { return true; } auto const ctor = expr->getConstructor(); unsigned const n = std::min(ctor->getNumParams(), expr->getNumArgs()); for (unsigned i = 0; i != n; ++i) { if (!loplugin::TypeCheck(ctor->getParamDecl(i)->getType()) .LvalueReference() .NonConstVolatile()) { continue; } auto const e = dyn_cast(expr->getArg(i)->IgnoreParenImpCasts()); if (e == nullptr) { continue; } auto const var = dyn_cast(e->getDecl()); if (var == nullptr) { continue; } auto const j = vars_.find(var); if (j == vars_.end()) { continue; } j->second.singleUse = nullptr; } return true; } bool TraverseWhileStmt(WhileStmt* stmt) { bool ret = true; if (PreTraverseWhileStmt(stmt)) { ret = FilteringPlugin::TraverseWhileStmt(stmt); PostTraverseWhileStmt(stmt, ret); } return ret; } bool PreTraverseWhileStmt(WhileStmt* stmt) { innermostLoop_.push(stmt); return true; } bool PostTraverseWhileStmt(WhileStmt* stmt, bool) { assert(!innermostLoop_.empty()); assert(innermostLoop_.top() == stmt); (void)stmt; innermostLoop_.pop(); return true; } bool TraverseDoStmt(DoStmt* stmt) { bool ret = true; if (PreTraverseDoStmt(stmt)) { ret = FilteringPlugin::TraverseDoStmt(stmt); PostTraverseDoStmt(stmt, ret); } return ret; } bool PreTraverseDoStmt(DoStmt* stmt) { innermostLoop_.push(stmt); return true; } bool PostTraverseDoStmt(DoStmt* stmt, bool) { assert(!innermostLoop_.empty()); assert(innermostLoop_.top() == stmt); (void)stmt; innermostLoop_.pop(); return true; } bool TraverseForStmt(ForStmt* stmt) { bool ret = true; if (PreTraverseForStmt(stmt)) { ret = FilteringPlugin::TraverseForStmt(stmt); PostTraverseForStmt(stmt, ret); } return ret; } bool PreTraverseForStmt(ForStmt* stmt) { innermostLoop_.push(stmt); return true; } bool PostTraverseForStmt(ForStmt* stmt, bool) { assert(!innermostLoop_.empty()); assert(innermostLoop_.top() == stmt); (void)stmt; innermostLoop_.pop(); return true; } bool TraverseCXXForRangeStmt(CXXForRangeStmt* stmt) { bool ret = true; if (PreTraverseCXXForRangeStmt(stmt)) { ret = FilteringPlugin::TraverseCXXForRangeStmt(stmt); PostTraverseCXXForRangeStmt(stmt, ret); } return ret; } bool PreTraverseCXXForRangeStmt(CXXForRangeStmt* stmt) { innermostLoop_.push(stmt); return true; } bool PostTraverseCXXForRangeStmt(CXXForRangeStmt* stmt, bool) { assert(!innermostLoop_.empty()); assert(innermostLoop_.top() == stmt); (void)stmt; innermostLoop_.pop(); return true; } private: void run() override { if (preRun() && TraverseDecl(compiler.getASTContext().getTranslationUnitDecl())) { postRun(); } } Stmt const* getInnermostLoop() const { return innermostLoop_.empty() ? nullptr : innermostLoop_.top(); } struct Data { Data(Stmt const* theInnermostLoop) : innermostLoop(theInnermostLoop) { } Stmt const* innermostLoop; llvm::Optional singleUse; }; std::stack innermostLoop_; std::map vars_; }; loplugin::Plugin::Registration elidestringvar("elidestringvar"); } #endif /* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */