loplugin:unusedmethods
Change-Id: I150baadc442e57ee604563bc52965daa9d2e41af
This commit is contained in:
@@ -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);
|
||||
|
||||
}
|
||||
|
@@ -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)]
|
||||
|
Reference in New Issue
Block a user