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);
}