office-gobmx/compilerplugins/clang/virtualdead.cxx
Noel Grandin ace70dae44 loplugins ignoreLocation() is unreliable with PCH
Change-Id: Ic12cacb6e058a8725ea6d722399e3afe6ea458c6
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/132115
Tested-by: Jenkins
Reviewed-by: Noel Grandin <noel.grandin@collabora.co.uk>
2022-03-25 14:35:24 +01:00

283 lines
9.3 KiB
C++

/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*
* 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/.
*/
#include "plugin.hxx"
#include "check.hxx"
#include "compat.hxx"
#include <cassert>
#include <string>
#include <iostream>
#include <set>
#include <fstream>
/**
Look for virtual methods where all of the overrides either
(a) do nothing
(b) all return the same value
The process goes something like this:
$ make check
$ make FORCE_COMPILE=all COMPILER_PLUGIN_TOOL='VirtualDead' check
$ ./compilerplugins/clang/VirtualDead.py
$ for dir in *; do make FORCE_COMPILE=all UPDATE_FILES=$dir COMPILER_PLUGIN_TOOL='removevirtuals' $dir; done
Note that the actual process may involve a fair amount of undoing, hand editing, and general messing around
to get it to work :-)
*/
namespace
{
struct MyFuncInfo
{
std::string name;
std::string sourceLocation;
std::string returnValue;
};
bool operator<(const MyFuncInfo& lhs, const MyFuncInfo& rhs)
{
return std::tie(lhs.name, lhs.returnValue) < std::tie(rhs.name, rhs.returnValue);
}
struct MyParamInfo
{
std::string funcName;
std::string paramBitField;
};
bool operator<(const MyParamInfo& lhs, const MyParamInfo& rhs)
{
return std::tie(lhs.funcName, lhs.paramBitField) < std::tie(rhs.funcName, rhs.paramBitField);
}
// try to limit the voluminous output a little
static std::set<MyFuncInfo> definitionSet;
static std::set<MyParamInfo> paramUsedSet;
class VirtualDead : public RecursiveASTVisitor<VirtualDead>, public loplugin::Plugin
{
public:
explicit VirtualDead(loplugin::InstantiationData const& data)
: Plugin(data)
{
}
virtual void run() override
{
TraverseDecl(compiler.getASTContext().getTranslationUnitDecl());
// dump all our output in one write call - this is to try and limit IO "crosstalk" between multiple processes
// writing to the same logfile
std::string output;
for (const MyFuncInfo& s : definitionSet)
output += "virtual:\t" + s.name + "\t" + s.sourceLocation + "\t" + s.returnValue + "\n";
for (const MyParamInfo& s : paramUsedSet)
output += "param:\t" + s.funcName + "\t" + s.paramBitField + "\n";
std::ofstream myfile;
myfile.open(WORKDIR "/loplugin.virtualdead.log", std::ios::app | std::ios::out);
myfile << output;
myfile.close();
}
bool shouldVisitTemplateInstantiations() const { return true; }
bool shouldVisitImplicitCode() const { return true; }
bool VisitCXXMethodDecl(const CXXMethodDecl* decl);
private:
std::string getCallValue(const Expr* arg);
std::string toString(SourceLocation loc);
void markSuperclassMethods(const CXXMethodDecl* methodDecl, const std::string& returnValue,
const std::string& paramBitField);
};
std::string niceName(const CXXMethodDecl* cxxMethodDecl)
{
while (cxxMethodDecl->getTemplateInstantiationPattern())
cxxMethodDecl = dyn_cast<CXXMethodDecl>(cxxMethodDecl->getTemplateInstantiationPattern());
while (cxxMethodDecl->getInstantiatedFromMemberFunction())
cxxMethodDecl = dyn_cast<CXXMethodDecl>(cxxMethodDecl->getInstantiatedFromMemberFunction());
std::string s = cxxMethodDecl->getReturnType().getCanonicalType().getAsString() + " "
+ cxxMethodDecl->getQualifiedNameAsString() + "(";
for (const ParmVarDecl* pParmVarDecl : cxxMethodDecl->parameters())
{
s += pParmVarDecl->getType().getCanonicalType().getAsString();
s += ",";
}
s += ")";
if (cxxMethodDecl->isConst())
{
s += "const";
}
return s;
}
bool VirtualDead::VisitCXXMethodDecl(const CXXMethodDecl* methodDecl)
{
if (!methodDecl->isVirtual() || methodDecl->isDeleted())
return true;
if (isa<CXXDestructorDecl>(methodDecl))
return true;
// ignore stuff that forms part of the stable URE interface
if (isInUnoIncludeFile(methodDecl->getCanonicalDecl()))
return true;
if (!methodDecl->isThisDeclarationADefinition())
return true;
std::string returnValue;
auto body = methodDecl->getBody();
if (body)
{
auto compoundStmt = dyn_cast<CompoundStmt>(body);
if (!compoundStmt)
returnValue = "empty";
else if (compoundStmt->size() == 0)
returnValue = "empty";
else
{
if (auto returnStmt = dyn_cast<ReturnStmt>(*compoundStmt->body_begin()))
{
if (!returnStmt->getRetValue())
returnValue = "empty";
else
returnValue = getCallValue(returnStmt->getRetValue());
}
else
returnValue = "unknown-stmt";
}
}
else
returnValue = "empty";
std::string paramBitfield;
for (auto it = methodDecl->param_begin(); it != methodDecl->param_end(); ++it)
{
auto param = *it;
paramBitfield += param->getName().empty() ? "0" : "1";
}
markSuperclassMethods(methodDecl, returnValue, paramBitfield);
return true;
}
void VirtualDead::markSuperclassMethods(const CXXMethodDecl* methodDecl,
const std::string& returnValue,
std::string const& paramBitField)
{
if (methodDecl->size_overridden_methods() == 0)
{
std::string aNiceName = niceName(methodDecl);
definitionSet.insert(
{ aNiceName, toString(methodDecl->getCanonicalDecl()->getLocation()), returnValue });
paramUsedSet.insert({ aNiceName, paramBitField });
return;
}
for (auto iter = methodDecl->begin_overridden_methods();
iter != methodDecl->end_overridden_methods(); ++iter)
{
const CXXMethodDecl* overriddenMethod = *iter;
markSuperclassMethods(overriddenMethod, returnValue, paramBitField);
}
}
std::string VirtualDead::getCallValue(const Expr* arg)
{
arg = arg->IgnoreParenCasts();
if (isa<CXXDefaultArgExpr>(arg))
{
arg = dyn_cast<CXXDefaultArgExpr>(arg)->getExpr();
}
arg = arg->IgnoreParenCasts();
// ignore this, it seems to trigger an infinite recursion
if (isa<UnaryExprOrTypeTraitExpr>(arg))
return "unknown1";
if (arg->isValueDependent())
return "unknown2";
APSInt x1;
if (compat::EvaluateAsInt(arg, x1, compiler.getASTContext()))
{
return compat::toString(x1, 10);
}
if (isa<CXXNullPtrLiteralExpr>(arg))
{
return "0";
}
if (isa<MaterializeTemporaryExpr>(arg))
{
const CXXBindTemporaryExpr* strippedArg
= dyn_cast_or_null<CXXBindTemporaryExpr>(arg->IgnoreParenCasts());
if (strippedArg)
{
auto temp = dyn_cast<CXXTemporaryObjectExpr>(strippedArg->getSubExpr());
if (temp->getNumArgs() == 0)
{
if (loplugin::TypeCheck(temp->getType())
.Class("OUString")
.Namespace("rtl")
.GlobalNamespace())
{
return "\"\"";
}
if (loplugin::TypeCheck(temp->getType())
.Class("OString")
.Namespace("rtl")
.GlobalNamespace())
{
return "\"\"";
}
return "defaultConstruct";
}
}
}
// Get the expression contents.
// This helps us find params which are always initialised with something like "OUString()".
SourceManager& SM = compiler.getSourceManager();
SourceLocation startLoc = arg->getBeginLoc();
SourceLocation endLoc = arg->getEndLoc();
const char* p1 = SM.getCharacterData(startLoc);
const char* p2 = SM.getCharacterData(endLoc);
if (!p1 || !p2 || (p2 - p1) < 0 || (p2 - p1) > 40)
{
return "unknown3";
}
unsigned n = Lexer::MeasureTokenLength(endLoc, SM, compiler.getLangOpts());
std::string s(p1, p2 - p1 + n);
// strip linefeed and tab characters so they don't interfere with the parsing of the log file
std::replace(s.begin(), s.end(), '\r', ' ');
std::replace(s.begin(), s.end(), '\n', ' ');
std::replace(s.begin(), s.end(), '\t', ' ');
// now normalize the value. For some params, like OUString, we can pass it as OUString() or "" and they are the same thing
if (s == "OUString()")
s = "\"\"";
else if (s == "OString()")
s = "\"\"";
if (s[0] == '"' || s[0] == '\'')
return s;
return "unknown4";
}
std::string VirtualDead::toString(SourceLocation loc)
{
SourceLocation expansionLoc = compiler.getSourceManager().getExpansionLoc(loc);
StringRef name = getFilenameOfLocation(expansionLoc);
std::string sourceLocation
= std::string(name.substr(strlen(SRCDIR) + 1)) + ":"
+ std::to_string(compiler.getSourceManager().getSpellingLineNumber(expansionLoc));
loplugin::normalizeDotDotInFilePath(sourceLocation);
return sourceLocation;
}
loplugin::Plugin::Registration<VirtualDead> X("virtualdead", false);
}
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */