summaryrefslogtreecommitdiffstats
path: root/compilerplugins/clang/elidestringvar.cxx
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--compilerplugins/clang/elidestringvar.cxx461
1 files changed, 461 insertions, 0 deletions
diff --git a/compilerplugins/clang/elidestringvar.cxx b/compilerplugins/clang/elidestringvar.cxx
new file mode 100644
index 0000000000..c14eaf027b
--- /dev/null
+++ b/compilerplugins/clang/elidestringvar.cxx
@@ -0,0 +1,461 @@
+/* -*- 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 "compat.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;
+ compat::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: */