diff options
Diffstat (limited to 'compilerplugins/clang/elidestringvar.cxx')
-rw-r--r-- | compilerplugins/clang/elidestringvar.cxx | 460 |
1 files changed, 460 insertions, 0 deletions
diff --git a/compilerplugins/clang/elidestringvar.cxx b/compilerplugins/clang/elidestringvar.cxx new file mode 100644 index 000000000..89e740d21 --- /dev/null +++ b/compilerplugins/clang/elidestringvar.cxx @@ -0,0 +1,460 @@ +/* -*- 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 <algorithm> +#include <cassert> +#include <map> + +#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<ElideStringVar> +{ +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<ParmVarDecl>(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<CXXConstructExpr>(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<AbstractConditionalOperator>(e2->IgnoreParenImpCasts())) + { + return true; + } + auto const e3 = e1->getArg(1); + if (!(isa<CXXDefaultArgExpr>(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<VarDecl>(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<DeclRefExpr>(expr->getBase()->IgnoreParenImpCasts()); + if (e == nullptr) + { + return true; + } + auto const var = dyn_cast<VarDecl>(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<DeclRefExpr>(expr->getSubExpr()->IgnoreParenImpCasts()); + if (e == nullptr) + { + return true; + } + auto const var = dyn_cast<VarDecl>(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<DeclRefExpr>(expr->getArg(i)->IgnoreParenImpCasts()); + if (e == nullptr) + { + continue; + } + auto const var = dyn_cast<VarDecl>(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<DeclRefExpr>(expr->getArg(i)->IgnoreParenImpCasts()); + if (e == nullptr) + { + continue; + } + auto const var = dyn_cast<VarDecl>(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<Expr const*> singleUse; + }; + + std::stack<Stmt const*> innermostLoop_; + std::map<VarDecl const*, Data> vars_; +}; + +loplugin::Plugin::Registration<ElideStringVar> elidestringvar("elidestringvar"); +} + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */ |