loplugin:unusedmethods

Change-Id: I150baadc442e57ee604563bc52965daa9d2e41af
This commit is contained in:
Noel Grandin
2015-10-02 08:37:23 +02:00
parent d7f2db4b9c
commit 7e776c0027
83 changed files with 134 additions and 948 deletions

View File

@@ -19,7 +19,7 @@
Dump a list of calls to methods, and a list of method definitions.
Then we will post-process the 2 lists and find the set of unused methods.
Be warned that it produces around 2.4G of log file.
Be warned that it produces around 4G of log file.
The process goes something like this:
$ make check
@@ -62,6 +62,11 @@ static std::set<MyFuncInfo> callSet;
static std::set<MyFuncInfo> definitionSet;
static bool startswith(const std::string& s, const std::string& prefix)
{
return s.rfind(prefix,0) == 0;
}
class UnusedMethods:
public RecursiveASTVisitor<UnusedMethods>, public loplugin::Plugin
{
@@ -78,21 +83,29 @@ public:
for (const MyFuncInfo & s : callSet)
output += "call:\t" + s.returnType + "\t" + s.nameAndParams + "\n";
for (const MyFuncInfo & s : definitionSet)
output += "definition:\t" + s.returnType + "\t" + s.nameAndParams + "\t" + s.sourceLocation + "\n";
{
//treat all UNO interfaces as having been called, since they are part of our external ABI
if (!startswith(s.nameAndParams, "com::sun::star::"))
output += "definition:\t" + s.returnType + "\t" + s.nameAndParams + "\t" + s.sourceLocation + "\n";
}
ofstream myfile;
myfile.open( SRCDIR "/unusedmethods.log", ios::app | ios::out);
myfile << output;
myfile.close();
}
bool shouldVisitTemplateInstantiations () const { return true; }
bool VisitCallExpr(CallExpr* );
bool VisitFunctionDecl( const FunctionDecl* decl );
bool VisitDeclRefExpr( const DeclRefExpr* );
bool VisitCXXConstructExpr( const CXXConstructExpr* );
bool VisitVarDecl( const VarDecl* );
bool VisitCXXRecordDecl( CXXRecordDecl* );
private:
void logCallToRootMethods(const FunctionDecl* functionDecl);
MyFuncInfo niceName(const FunctionDecl* functionDecl);
std::string fullyQualifiedName(const FunctionDecl* functionDecl);
};
MyFuncInfo UnusedMethods::niceName(const FunctionDecl* functionDecl)
@@ -136,10 +149,36 @@ MyFuncInfo UnusedMethods::niceName(const FunctionDecl* functionDecl)
return aInfo;
}
std::string UnusedMethods::fullyQualifiedName(const FunctionDecl* functionDecl)
{
std::string ret = compat::getReturnType(*functionDecl).getCanonicalType().getAsString();
ret += " ";
if (isa<CXXMethodDecl>(functionDecl)) {
const CXXRecordDecl* recordDecl = dyn_cast<CXXMethodDecl>(functionDecl)->getParent();
ret += recordDecl->getQualifiedNameAsString();
ret += "::";
}
ret += functionDecl->getNameAsString() + "(";
bool bFirst = true;
for (const ParmVarDecl *pParmVarDecl : functionDecl->params()) {
if (bFirst)
bFirst = false;
else
ret += ",";
ret += pParmVarDecl->getType().getCanonicalType().getAsString();
}
ret += ")";
if (isa<CXXMethodDecl>(functionDecl) && dyn_cast<CXXMethodDecl>(functionDecl)->isConst()) {
ret += " const";
}
return ret;
}
void UnusedMethods::logCallToRootMethods(const FunctionDecl* functionDecl)
{
functionDecl = functionDecl->getCanonicalDecl();
bool bPrinted = false;
bool bCalledSuperMethod = false;
if (isa<CXXMethodDecl>(functionDecl)) {
// For virtual/overriding methods, we need to pretend we called the root method(s),
// so that they get marked as used.
@@ -148,50 +187,24 @@ void UnusedMethods::logCallToRootMethods(const FunctionDecl* functionDecl)
it != methodDecl->end_overridden_methods(); ++it)
{
logCallToRootMethods(*it);
bPrinted = true;
bCalledSuperMethod = true;
}
}
if (!bPrinted)
if (!bCalledSuperMethod)
{
while (functionDecl->getTemplateInstantiationPattern())
functionDecl = functionDecl->getTemplateInstantiationPattern();
callSet.insert(niceName(functionDecl));
}
}
static bool startsWith(const std::string& s, const char* other)
{
return s.compare(0, strlen(other), other) == 0;
}
static bool isStandardStuff(const std::string& input)
{
std::string s = input;
if (startsWith(s,"class "))
s = s.substr(6);
else if (startsWith(s,"struct "))
s = s.substr(7);
// ignore UNO interface definitions, cannot change those
return startsWith(s, "com::sun::star::")
// ignore stuff in the C++ stdlib and boost
|| startsWith(s, "std::") || startsWith(s, "boost::") || startsWith(s, "class boost::") || startsWith(s, "__gnu_debug::")
// external library
|| startsWith(s, "mdds::")
// can't change our rtl layer
|| startsWith(s, "rtl::")
// ignore anonymous namespace stuff, it is compilation-unit-local and the compiler will detect any
// unused code there
|| startsWith(s, "(anonymous namespace)::");
}
// prevent recursive templates from blowing up the stack
static std::set<const FunctionDecl*> traversedFunctionSet;
static std::set<std::string> traversedFunctionSet;
bool UnusedMethods::VisitCallExpr(CallExpr* expr)
{
// I don't use the normal ignoreLocation() here, because I __want__ to include files that are
// compiled in the $WORKDIR since they may refer to normal code
SourceLocation expansionLoc = compiler.getSourceManager().getExpansionLoc( expr->getLocStart() );
if( compiler.getSourceManager().isInSystemHeader( expansionLoc ))
return true;
// Note that I don't ignore ANYTHING here, because I want to get calls to my code that result
// from template instantiation deep inside the STL and other external code
FunctionDecl* calleeFunctionDecl = expr->getDirectCallee();
if (calleeFunctionDecl == nullptr) {
@@ -229,7 +242,7 @@ gotfunc:
// if the function is templated. However, if we are inside a template function,
// calling another function on the same template, the same problem occurs.
// Rather than tracking all of that, just traverse anything we have not already traversed.
if (traversedFunctionSet.insert(calleeFunctionDecl).second)
if (traversedFunctionSet.insert(fullyQualifiedName(calleeFunctionDecl)).second)
TraverseFunctionDecl(calleeFunctionDecl);
logCallToRootMethods(calleeFunctionDecl);
@@ -238,12 +251,6 @@ gotfunc:
bool UnusedMethods::VisitCXXConstructExpr(const CXXConstructExpr* expr)
{
// I don't use the normal ignoreLocation() here, because I __want__ to include files that are
// compiled in the $WORKDIR since they may refer to normal code
SourceLocation expansionLoc = compiler.getSourceManager().getExpansionLoc( expr->getLocStart() );
if( compiler.getSourceManager().isInSystemHeader( expansionLoc ))
return true;
const CXXConstructorDecl *consDecl = expr->getConstructor();
consDecl = consDecl->getCanonicalDecl();
if (consDecl->getTemplatedKind() == FunctionDecl::TemplatedKind::TK_NonTemplate
@@ -252,7 +259,7 @@ bool UnusedMethods::VisitCXXConstructExpr(const CXXConstructExpr* expr)
}
// if we see a call to a constructor, it may effectively create a whole new class,
// if the constructor's class is templated.
if (!traversedFunctionSet.insert(consDecl).second)
if (!traversedFunctionSet.insert(fullyQualifiedName(consDecl)).second)
return true;
const CXXRecordDecl* parent = consDecl->getParent();
@@ -266,10 +273,6 @@ bool UnusedMethods::VisitCXXConstructExpr(const CXXConstructExpr* expr)
bool UnusedMethods::VisitFunctionDecl( const FunctionDecl* functionDecl )
{
if (ignoreLocation(functionDecl)) {
return true;
}
functionDecl = functionDecl->getCanonicalDecl();
const CXXMethodDecl* methodDecl = dyn_cast_or_null<CXXMethodDecl>(functionDecl);
@@ -282,9 +285,6 @@ bool UnusedMethods::VisitFunctionDecl( const FunctionDecl* functionDecl )
functionDecl->getCanonicalDecl()->getNameInfo().getLoc()))) {
return true;
}
if (methodDecl && isStandardStuff(methodDecl->getParent()->getQualifiedNameAsString())) {
return true;
}
if (isa<CXXDestructorDecl>(functionDecl)) {
return true;
}
@@ -295,19 +295,14 @@ bool UnusedMethods::VisitFunctionDecl( const FunctionDecl* functionDecl )
return true;
}
definitionSet.insert(niceName(functionDecl));
if( !ignoreLocation( functionDecl ))
definitionSet.insert(niceName(functionDecl));
return true;
}
// this catches places that take the address of a method
bool UnusedMethods::VisitDeclRefExpr( const DeclRefExpr* declRefExpr )
{
// I don't use the normal ignoreLocation() here, because I __want__ to include files that are
// compiled in the $WORKDIR since they may refer to normal code
SourceLocation expansionLoc = compiler.getSourceManager().getExpansionLoc( declRefExpr->getLocStart() );
if( compiler.getSourceManager().isInSystemHeader( expansionLoc ))
return true;
const Decl* functionDecl = declRefExpr->getDecl();
if (!isa<FunctionDecl>(functionDecl)) {
return true;
@@ -320,11 +315,6 @@ bool UnusedMethods::VisitDeclRefExpr( const DeclRefExpr* declRefExpr )
bool UnusedMethods::VisitVarDecl( const VarDecl* varDecl )
{
varDecl = varDecl->getCanonicalDecl();
// I don't use the normal ignoreLocation() here, because I __want__ to include files that are
// compiled in the $WORKDIR since they may refer to normal code
SourceLocation expansionLoc = compiler.getSourceManager().getExpansionLoc( varDecl->getLocStart() );
if( compiler.getSourceManager().isInSystemHeader( expansionLoc ))
return true;
if (varDecl->getStorageClass() != SC_Static)
return true;
@@ -343,6 +333,35 @@ bool UnusedMethods::VisitVarDecl( const VarDecl* varDecl )
return true;
}
// Sometimes a class will inherit from something, and in the process invoke a template,
// which can create new methods.
//
bool UnusedMethods::VisitCXXRecordDecl( CXXRecordDecl* recordDecl )
{
recordDecl = recordDecl->getCanonicalDecl();
if (!recordDecl->hasDefinition())
return true;
// workaround clang-3.5 issue
#if __clang_major__ > 3 || ( __clang_major__ == 3 && __clang_minor__ >= 6 )
for(CXXBaseSpecifier* baseSpecifier = recordDecl->bases_begin();
baseSpecifier != recordDecl->bases_end(); ++baseSpecifier)
{
const Type *baseType = baseSpecifier->getType().getTypePtr();
if (isa<TypedefType>(baseSpecifier->getType())) {
baseType = dyn_cast<TypedefType>(baseType)->desugar().getTypePtr();
}
if (isa<RecordType>(baseType)) {
const RecordType *baseRecord = dyn_cast<RecordType>(baseType);
CXXRecordDecl* baseRecordDecl = dyn_cast<CXXRecordDecl>(baseRecord->getDecl());
if (baseRecordDecl && baseRecordDecl->getTemplateInstantiationPattern()) {
TraverseCXXRecordDecl(baseRecordDecl);
}
}
}
#endif
return true;
}
loplugin::Plugin::Registration< UnusedMethods > X("unusedmethods", false);
}

View File

@@ -2,6 +2,7 @@
import sys
import re
import io
definitionSet = set()
definitionToSourceLocationMap = dict()
@@ -71,6 +72,7 @@ exclusionSet = set([
"Ring<value_type> * sw::Ring::Ring_node_traits::get_previous(const Ring<value_type> *)",
"void sw::Ring::Ring_node_traits::set_next(Ring<value_type> *,Ring<value_type> *)",
"void sw::Ring::Ring_node_traits::set_previous(Ring<value_type> *,Ring<value_type> *)",
"type-parameter-0-0 checking_cast(type-parameter-0-0,type-parameter-0-0)",
# I need to teach the plugin that for loops with range expressions call begin() and end()
"class __gnu_debug::_Safe_iterator<class __gnu_cxx::__normal_iterator<class SwAnchoredObject *const *, class std::__cxx1998::vector<class SwAnchoredObject *, class std::allocator<class SwAnchoredObject *> > >, class std::__debug::vector<class SwAnchoredObject *, class std::allocator<class SwAnchoredObject *> > > SwSortedObjs::begin() const",
"class __gnu_debug::_Safe_iterator<class __gnu_cxx::__normal_iterator<class SwAnchoredObject *const *, class std::__cxx1998::vector<class SwAnchoredObject *, class std::allocator<class SwAnchoredObject *> > >, class std::__debug::vector<class SwAnchoredObject *, class std::allocator<class SwAnchoredObject *> > > SwSortedObjs::end() const",
@@ -85,22 +87,36 @@ exclusionSet = set([
"class chart::opengl::OpenglShapeFactory * getOpenglShapeFactory()",
"class VclAbstractDialogFactory * CreateDialogFactory()",
"_Bool GetSpecialCharsForEdit(class vcl::Window *,const class vcl::Font &,class rtl::OUString &)",
"const struct ImplTextEncodingData * sal_getFullTextEncodingData(unsigned short)"
"const struct ImplTextEncodingData * sal_getFullTextEncodingData(unsigned short)",
"class SalInstance * create_SalInstance()",
"class SwAbstractDialogFactory * SwCreateDialogFactory()",
"class com::sun::star::uno::Reference<class com::sun::star::uno::XInterface> WordPerfectImportFilterDialog_createInstance(const class com::sun::star::uno::Reference<class com::sun::star::uno::XComponentContext> &)",
"class UnoWrapperBase * CreateUnoWrapper()",
"class SwAbstractDialogFactory * SwCreateDialogFactory()",
"unsigned long GetSaveWarningOfMSVBAStorage_ww8(class SfxObjectShell &)",
"unsigned long SaveOrDelMSVBAStorage_ww8(class SfxObjectShell &,class SotStorage &,unsigned char,const class rtl::OUString &)",
"void ExportRTF(const class rtl::OUString &,const class rtl::OUString &,class tools::SvRef<class Writer> &)",
"void ExportDOC(const class rtl::OUString &,const class rtl::OUString &,class tools::SvRef<class Writer> &)",
"class Reader * ImportRTF()",
"void ImportXE(class SwDoc &,class SwPaM &,const class rtl::OUString &)",
"_Bool TestImportDOC(const class rtl::OUString &,const class rtl::OUString &)",
"class vcl::Window * CreateWindow(class VCLXWindow **,const struct com::sun::star::awt::WindowDescriptor *,class vcl::Window *,long)",
])
# The parsing here is designed to avoid grabbing stuff which is mixed in from gbuild.
# I have not yet found a way of suppressing the gbuild output.
with open(sys.argv[1]) as txt:
with io.open(sys.argv[1], "rb", buffering=1024*1024) as txt:
for line in txt:
if line.startswith("definition:\t"):
tokens = line.split("\t")
funcInfo = (tokens[1], tokens[2])
idx1 = line.find("\t",12)
idx2 = line.find("\t",idx1+1)
funcInfo = (line[12:idx1], line[idx1+1:idx2])
definitionSet.add(funcInfo)
definitionToSourceLocationMap[funcInfo] = tokens[3].strip()
definitionToSourceLocationMap[funcInfo] = line[idx2+1:].strip()
elif line.startswith("call:\t"):
tokens = line.split("\t")
callSet.add((tokens[1], tokens[2].strip()))
idx1 = line.find("\t",6)
callSet.add((line[6:idx1], line[idx1+1:].strip()))
tmp1set = set()
for d in definitionSet:
@@ -137,6 +153,10 @@ for d in definitionSet:
clazz2 = clazz.replace("::iterator", "::const_iterator") + " const"
if ((d[0],clazz2) in callSet):
continue
# just ignore iterators, they normally occur in pairs, and we typically want to leave one constness version alone
# alone if the other one is in use.
if d[1] == "begin() const" or d[1] == "begin()" or d[1] == "end()" or d[1] == "end() const":
continue
# There is lots of macro magic going on in SRCDIR/include/sax/fshelper.hxx that should be using C++11 varag templates
if d[1].startswith("sax_fastparser::FastSerializerHelper::"):
continue
@@ -171,9 +191,13 @@ for d in definitionSet:
# ignore the VCL_BUILDER_DECL_FACTORY stuff
if d[0]=="void" and d[1].startswith("make") and ("(class VclPtr<class vcl::Window> &" in d[1]):
continue
# ignore methods used to dump objects to stream - normally used for debugging
if d[0] == "class std::basic_ostream<char> &" and d[1].startswith("operator<<(class std::basic_ostream<char> &"):
continue
tmp1set.add((clazz, definitionToSourceLocationMap[d]))
# sort the results using a "natural order" so sequences like [item1,item2,item10] sort nicely
def natural_sort_key(s, _nsre=re.compile('([0-9]+)')):
return [int(text) if text.isdigit() else text.lower()
for text in re.split(_nsre, s)]