summaryrefslogtreecommitdiffstats
path: root/compilerplugins/clang/unusedvariablemore.cxx
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-27 16:51:28 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-27 16:51:28 +0000
commit940b4d1848e8c70ab7642901a68594e8016caffc (patch)
treeeb72f344ee6c3d9b80a7ecc079ea79e9fba8676d /compilerplugins/clang/unusedvariablemore.cxx
parentInitial commit. (diff)
downloadlibreoffice-940b4d1848e8c70ab7642901a68594e8016caffc.tar.xz
libreoffice-940b4d1848e8c70ab7642901a68594e8016caffc.zip
Adding upstream version 1:7.0.4.upstream/1%7.0.4upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'compilerplugins/clang/unusedvariablemore.cxx')
-rw-r--r--compilerplugins/clang/unusedvariablemore.cxx343
1 files changed, 343 insertions, 0 deletions
diff --git a/compilerplugins/clang/unusedvariablemore.cxx b/compilerplugins/clang/unusedvariablemore.cxx
new file mode 100644
index 000000000..38e3bbe81
--- /dev/null
+++ b/compilerplugins/clang/unusedvariablemore.cxx
@@ -0,0 +1,343 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * Based on LLVM/Clang.
+ *
+ * This file is distributed under the University of Illinois Open Source
+ * License. See LICENSE.TXT for details.
+ *
+ */
+
+#include "config_clang.h"
+#include <config_global.h>
+#include "plugin.hxx"
+#include "compat.hxx"
+#include "check.hxx"
+#include <unordered_set>
+#include <unordered_map>
+
+#if CLANG_VERSION >= 110000
+#include "clang/AST/ParentMapContext.h"
+#endif
+
+namespace loplugin
+{
+/*
+ This is a compile check. The results of this plugin need to be checked by hand, since it is a collection of heuristics.
+
+ Check for unused variable where
+ (*) we never call methods that return information from the variable.
+ (*) we never pass the variable to anything else
+
+ Classes which are safe to be warned about need to be marked using
+ SAL_WARN_UNUSED (see e.g. OUString). For external classes such as std::vector
+ that cannot be edited there is a manual list.
+
+ This is an expensive plugin, since it walks up the parent tree,
+ so it is off by default.
+*/
+
+class UnusedVariableMore : public loplugin::FilteringPlugin<UnusedVariableMore>
+{
+public:
+ explicit UnusedVariableMore(const InstantiationData& data);
+ virtual void run() override;
+ bool VisitVarDecl(VarDecl const*);
+ bool VisitDeclRefExpr(DeclRefExpr const*);
+ bool VisitFunctionDecl(FunctionDecl const*);
+
+private:
+ bool checkifUnused(Stmt const*, VarDecl const*);
+ bool isOkForParameter(const QualType&);
+
+ std::unordered_set<VarDecl const*> interestingSet;
+ // used to dump the last place that said the usage was unused, for debug purposes
+ std::unordered_map<VarDecl const*, Stmt const*> interestingDebugMap;
+};
+
+UnusedVariableMore::UnusedVariableMore(const InstantiationData& data)
+ : FilteringPlugin(data)
+{
+}
+
+void UnusedVariableMore::run()
+{
+ std::string fn(handler.getMainFileName());
+ loplugin::normalizeDotDotInFilePath(fn);
+
+ // ignore QA folders
+ if (loplugin::hasPathnamePrefix(fn, SRCDIR "/sal/qa/"))
+ return;
+ if (loplugin::hasPathnamePrefix(fn, SRCDIR "/i18npool/qa/"))
+ return;
+ if (loplugin::hasPathnamePrefix(fn, SRCDIR "/sc/qa/"))
+ return;
+
+ // vector of shared_ptr used to delay destruction
+ if (fn == SRCDIR "/cppuhelper/source/servicemanager.cxx")
+ return;
+ if (fn == SRCDIR "/i18nlangtag/source/languagetag/languagetag.cxx")
+ return;
+ // unordered_set of Reference to delay destruction
+ if (fn == SRCDIR "/stoc/source/servicemanager/servicemanager.cxx")
+ return;
+ // TODO "operator >>" fooling me here
+ if (fn == SRCDIR "/editeng/source/accessibility/AccessibleEditableTextPara.cxx")
+ return;
+ // some weird stuff
+ if (fn == SRCDIR "/sfx2/source/dialog/dinfdlg.cxx")
+ return;
+ // std::vector< Reference< XInterface > > keep alive
+ if (fn == SRCDIR "/dbaccess/source/core/dataaccess/databasedocument.cxx")
+ return;
+ // template magic
+ if (fn == SRCDIR "/sc/source/core/tool/scmatrix.cxx")
+ return;
+ // storing local copy of Link<>
+ if (fn == SRCDIR "/sc/source/ui/miscdlgs/simpref.cxx")
+ return;
+ // Using an SwPaM to do stuff
+ if (fn == SRCDIR "/sw/source/core/crsr/bookmrk.cxx")
+ return;
+ // index variable in for loop?
+ if (fn == SRCDIR "/sw/source/uibase/docvw/edtwin.cxx")
+ return;
+ // TODO "operator >>" fooling me here
+ if (fn == SRCDIR "/sw/source/filter/ww8/ww8par.cxx")
+ return;
+ // TODO "operator >>" fooling me here
+ if (fn == SRCDIR "/sc/source/filter/excel/xistream.cxx")
+ return;
+
+ TraverseDecl(compiler.getASTContext().getTranslationUnitDecl());
+
+ for (VarDecl const* varDecl : interestingSet)
+ {
+ report(DiagnosticsEngine::Warning, "unused variable %0", varDecl->getLocation())
+ << varDecl->getDeclName();
+ //auto it = interestingDebugMap.find(varDecl);
+ //if (it != interestingDebugMap.end())
+ // it->second->dump();
+ }
+}
+
+bool isWarnUnusedType(QualType type)
+{
+ if (auto const t = type->getAs<TypedefType>())
+ {
+ if (t->getDecl()->hasAttr<WarnUnusedAttr>())
+ {
+ return true;
+ }
+ }
+ if (auto const t = type->getAs<RecordType>())
+ {
+ if (t->getDecl()->hasAttr<WarnUnusedAttr>())
+ {
+ return true;
+ }
+ }
+ return loplugin::isExtraWarnUnusedType(type);
+}
+
+bool UnusedVariableMore::VisitVarDecl(VarDecl const* var)
+{
+ if (ignoreLocation(var))
+ return true;
+ if (var->isDefinedOutsideFunctionOrMethod())
+ return true;
+ if (isa<ParmVarDecl>(var))
+ return true;
+ if (!isWarnUnusedType(var->getType()))
+ return true;
+
+ // some false +
+ auto dc = loplugin::TypeCheck(var->getType());
+ if (dc.Class("ZCodec").GlobalNamespace())
+ return true;
+ if (dc.Class("ScopedVclPtrInstance").GlobalNamespace())
+ return true;
+ if (dc.Class("VclPtrInstance").GlobalNamespace())
+ return true;
+ if (dc.Class("Config").GlobalNamespace())
+ return true;
+ // I think these classes modify global state somehow
+ if (dc.Class("SvtHistoryOptions").GlobalNamespace())
+ return true;
+ if (dc.Class("SvtSecurityOptions").GlobalNamespace())
+ return true;
+ if (dc.Class("SvtViewOptions").GlobalNamespace())
+ return true;
+ if (dc.Class("SvtUserOptions").GlobalNamespace())
+ return true;
+ if (dc.Class("SvtFontOptions").GlobalNamespace())
+ return true;
+ if (dc.Class("SvtMenuOptions").GlobalNamespace())
+ return true;
+ if (dc.Class("SvtPathOptions").GlobalNamespace())
+ return true;
+ if (dc.Class("SvtPrintWarningOptions").GlobalNamespace())
+ return true;
+ if (dc.Class("SvtSysLocaleOptions").GlobalNamespace())
+ return true;
+
+ interestingSet.insert(var);
+ return true;
+}
+
+bool UnusedVariableMore::VisitDeclRefExpr(DeclRefExpr const* declRefExpr)
+{
+ if (ignoreLocation(declRefExpr))
+ return true;
+ auto varDecl = dyn_cast<VarDecl>(declRefExpr->getDecl());
+ if (!varDecl)
+ return true;
+ if (interestingSet.find(varDecl) == interestingSet.end())
+ return true;
+ if (!checkifUnused(declRefExpr, varDecl))
+ interestingSet.erase(varDecl);
+ return true;
+}
+
+// Walk up from a statement that contains a DeclRefExpr, checking if the usage means that the
+// related VarDecl is unused.
+bool UnusedVariableMore::checkifUnused(Stmt const* stmt, VarDecl const* varDecl)
+{
+ const Stmt* parent = getParentStmt(stmt);
+ if (!parent)
+ {
+ // check if we're inside a CXXCtorInitializer
+ auto parentsRange = getParents(*stmt);
+ if (parentsRange.begin() != parentsRange.end())
+ {
+ auto parentDecl = parentsRange.begin()->get<Decl>();
+ if (parentDecl && (isa<CXXConstructorDecl>(parentDecl) || isa<VarDecl>(parentDecl)))
+ return false;
+ }
+ interestingDebugMap[varDecl] = stmt;
+ return true;
+ }
+
+ if (isa<ReturnStmt>(parent))
+ return false;
+ if (isa<IfStmt>(parent))
+ return false;
+ if (isa<SwitchStmt>(parent))
+ return false;
+ if (isa<InitListExpr>(parent))
+ return false;
+ if (isa<CXXConstructExpr>(parent))
+ return false;
+ if (isa<BinaryOperator>(parent))
+ return false;
+ if (isa<UnaryOperator>(parent))
+ return false;
+ if (isa<ConditionalOperator>(parent))
+ return false;
+ if (isa<ArraySubscriptExpr>(parent))
+ return false;
+ if (isa<CXXBindTemporaryExpr>(parent))
+ return checkifUnused(parent, varDecl);
+ if (isa<MaterializeTemporaryExpr>(parent))
+ return checkifUnused(parent, varDecl);
+
+ if (isa<CompoundStmt>(parent))
+ {
+ interestingDebugMap[varDecl] = parent;
+ return true;
+ }
+
+ // check for cast to void
+ if (auto explicitCastExpr = dyn_cast<ExplicitCastExpr>(parent))
+ {
+ if (loplugin::TypeCheck(explicitCastExpr->getTypeAsWritten()).Void())
+ return false;
+ }
+
+ if (isa<MemberExpr>(parent))
+ return checkifUnused(parent, varDecl);
+ if (isa<ExprWithCleanups>(parent))
+ return checkifUnused(parent, varDecl);
+ if (isa<CastExpr>(parent))
+ return checkifUnused(parent, varDecl);
+ if (isa<ParenExpr>(parent))
+ return checkifUnused(parent, varDecl);
+
+ if (auto operatorCallExpr = dyn_cast<CXXOperatorCallExpr>(parent))
+ {
+ const CXXMethodDecl* calleeMethodDecl
+ = dyn_cast_or_null<CXXMethodDecl>(operatorCallExpr->getDirectCallee());
+ if (calleeMethodDecl)
+ {
+ if (calleeMethodDecl->getNumParams() == 0)
+ return checkifUnused(parent, varDecl);
+ if (operatorCallExpr->getArg(0) == stmt)
+ return checkifUnused(parent, varDecl);
+ }
+ }
+ if (auto memberCallExpr = dyn_cast<CXXMemberCallExpr>(parent))
+ {
+ const MemberExpr* memberExpr = dyn_cast<MemberExpr>(stmt);
+ if (memberExpr && memberCallExpr->getImplicitObjectArgument() == memberExpr->getBase())
+ {
+ // if we are calling a method on the varDecl, walk up
+ if (!checkifUnused(parent, varDecl))
+ return false;
+ // check if we are passing something to the var by non-const ref, in which case it is updating something else for us
+ const FunctionDecl* calleeFunctionDecl = memberCallExpr->getDirectCallee();
+ if (calleeFunctionDecl)
+ {
+ if (calleeFunctionDecl->isVariadic())
+ return false;
+ for (unsigned i = 0; i < memberCallExpr->getNumArgs(); ++i)
+ {
+ if (i >= calleeFunctionDecl->getNumParams()) // can happen in template code
+ {
+ interestingDebugMap[varDecl] = parent;
+ return true;
+ }
+ if (!isOkForParameter(calleeFunctionDecl->getParamDecl(i)->getType()))
+ return false;
+ }
+ }
+ interestingDebugMap[varDecl] = parent;
+ return true;
+ }
+ }
+ if (isa<CallExpr>(parent))
+ return false;
+
+ interestingDebugMap[varDecl] = parent;
+ return true;
+}
+
+bool UnusedVariableMore::isOkForParameter(const QualType& qt)
+{
+ if (qt->isIntegralOrEnumerationType())
+ return true;
+ auto const type = loplugin::TypeCheck(qt);
+ if (type.Pointer())
+ {
+ return bool(type.Pointer().Const());
+ }
+ else if (type.LvalueReference())
+ {
+ return bool(type.LvalueReference().Const());
+ }
+ return false;
+}
+
+bool UnusedVariableMore::VisitFunctionDecl(FunctionDecl const* functionDecl)
+{
+ if (ignoreLocation(functionDecl))
+ return true;
+ //functionDecl->dump();
+ return true;
+}
+
+static Plugin::Registration<UnusedVariableMore> X("unusedvariablemore", false);
+
+} // namespace
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */