d1a2b80b9d
...as discussed in the mail thread starting at <https://lists.freedesktop.org/archives/libreoffice/2020-November/086234.html> "Bump --enable-compiler-plugins Clang baseline?" (and now picked up again at <https://lists.freedesktop.org/archives/libreoffice/2022-February/088459.html> "Re: Bump --enable-compiler-plugins Clang baseline?"), and clean up compilerplugins/clang/ accordingly Change-Id: I5e81c6fdcc363aeefd6227606225b526fdf7ac16 Reviewed-on: https://gerrit.libreoffice.org/c/core/+/129989 Tested-by: Jenkins Reviewed-by: Stephan Bergmann <sbergman@redhat.com>
337 lines
11 KiB
C++
337 lines
11 KiB
C++
/* -*- 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>
|
|
|
|
#include "clang/AST/ParentMapContext.h"
|
|
|
|
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/bookmark.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("SvtMenuOptions").GlobalNamespace())
|
|
return true;
|
|
if (dc.Class("SvtPathOptions").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 = compiler.getASTContext().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: */
|