Kaydet (Commit) d34d7922 authored tarafından Noel Grandin's avatar Noel Grandin

new loplugin to find public methods that can be private

based on the unusedmethods plugin, which I should probably rename at
some point

Change-Id: If197423c59d4350ea1fdc69e99d24b631d9751b9
üst 27b623d9
...@@ -16,8 +16,14 @@ ...@@ -16,8 +16,14 @@
#include "compat.hxx" #include "compat.hxx"
/** /**
Dump a list of calls to methods, and a list of method definitions. This plugin performs 3 different analyses:
Then we will post-process the 2 lists and find the set of unused methods.
(1) Find unused methods
(2) Find methods whose return types are never evaluated
(3) Find methods which are public, but are never called from outside the class i.e. they can be private
It does so, by dumping various call/definition/use info to a log file.
Then we will post-process the various lists and find the set of unused methods.
Be warned that it produces around 5G of log file. Be warned that it produces around 5G of log file.
...@@ -41,6 +47,7 @@ namespace { ...@@ -41,6 +47,7 @@ namespace {
struct MyFuncInfo struct MyFuncInfo
{ {
std::string access;
std::string returnType; std::string returnType;
std::string nameAndParams; std::string nameAndParams;
std::string sourceLocation; std::string sourceLocation;
...@@ -58,16 +65,17 @@ struct MyFuncInfo ...@@ -58,16 +65,17 @@ struct MyFuncInfo
// try to limit the voluminous output a little // try to limit the voluminous output a little
// for the "unused method" analysis
static std::set<MyFuncInfo> callSet; static std::set<MyFuncInfo> callSet;
static std::set<MyFuncInfo> usedReturnSet;
static std::set<MyFuncInfo> definitionSet; static std::set<MyFuncInfo> definitionSet;
// for the "unused return type" analysis
static std::set<MyFuncInfo> usedReturnSet;
// for the "unnecessary public" analysis
static std::set<MyFuncInfo> publicDefinitionSet;
static std::set<MyFuncInfo> calledFromOutsideSet;
static bool startswith(const std::string& s, const std::string& prefix)
{
return s.rfind(prefix,0) == 0;
}
class UnusedMethods: class UnusedMethods:
public RecursiveASTVisitor<UnusedMethods>, public loplugin::Plugin public RecursiveASTVisitor<UnusedMethods>, public loplugin::Plugin
{ {
...@@ -80,17 +88,19 @@ public: ...@@ -80,17 +88,19 @@ public:
// dump all our output in one write call - this is to try and limit IO "crosstalk" between multiple processes // dump all our output in one write call - this is to try and limit IO "crosstalk" between multiple processes
// writing to the same logfile // writing to the same logfile
std::string output; std::string output;
for (const MyFuncInfo & s : definitionSet)
output += "definition:\t" + s.access + "\t" + s.returnType + "\t" + s.nameAndParams + "\t" + s.sourceLocation + "\n";
// for the "unused method" analysis
for (const MyFuncInfo & s : callSet) for (const MyFuncInfo & s : callSet)
output += "call:\t" + s.returnType + "\t" + s.nameAndParams + "\n"; output += "call:\t" + s.returnType + "\t" + s.nameAndParams + "\n";
// for the "unused return type" analysis
for (const MyFuncInfo & s : usedReturnSet) for (const MyFuncInfo & s : usedReturnSet)
output += "usedReturn:\t" + s.returnType + "\t" + s.nameAndParams + "\n"; output += "usedReturn:\t" + s.returnType + "\t" + s.nameAndParams + "\n";
for (const MyFuncInfo & s : definitionSet) // for the "unnecessary public" analysis
{ for (const MyFuncInfo & s : calledFromOutsideSet)
//treat all UNO interfaces as having been called, since they are part of our external ABI output += "outside:\t" + s.returnType + "\t" + s.nameAndParams + "\n";
if (!startswith(s.nameAndParams, "com::sun::star::"))
output += "definition:\t" + s.returnType + "\t" + s.nameAndParams + "\t" + s.sourceLocation + "\n";
}
ofstream myfile; ofstream myfile;
myfile.open( SRCDIR "/unusedmethods.log", ios::app | ios::out); myfile.open( SRCDIR "/unusedmethods.log", ios::app | ios::out);
myfile << output; myfile << output;
...@@ -121,6 +131,13 @@ MyFuncInfo UnusedMethods::niceName(const FunctionDecl* functionDecl) ...@@ -121,6 +131,13 @@ MyFuncInfo UnusedMethods::niceName(const FunctionDecl* functionDecl)
#endif #endif
MyFuncInfo aInfo; MyFuncInfo aInfo;
switch (functionDecl->getAccess())
{
case AS_public: aInfo.access = "public"; break;
case AS_private: aInfo.access = "private"; break;
case AS_protected: aInfo.access = "protected"; break;
default: aInfo.access = "unknown"; break;
}
aInfo.returnType = compat::getReturnType(*functionDecl).getCanonicalType().getAsString(); aInfo.returnType = compat::getReturnType(*functionDecl).getCanonicalType().getAsString();
if (isa<CXXMethodDecl>(functionDecl)) { if (isa<CXXMethodDecl>(functionDecl)) {
...@@ -201,6 +218,33 @@ void UnusedMethods::logCallToRootMethods(const FunctionDecl* functionDecl, std:: ...@@ -201,6 +218,33 @@ void UnusedMethods::logCallToRootMethods(const FunctionDecl* functionDecl, std::
// prevent recursive templates from blowing up the stack // prevent recursive templates from blowing up the stack
static std::set<std::string> traversedFunctionSet; static std::set<std::string> traversedFunctionSet;
const Decl* get_DeclContext_from_Stmt(ASTContext& context, const Stmt& stmt)
{
auto it = context.getParents(stmt).begin();
if (it == context.getParents(stmt).end())
return nullptr;
const Decl *aDecl = it->get<Decl>();
if (aDecl)
return aDecl;
const Stmt *aStmt = it->get<Stmt>();
if (aStmt)
return get_DeclContext_from_Stmt(context, *aStmt);
return nullptr;
}
static const FunctionDecl* get_top_FunctionDecl_from_Stmt(ASTContext& context, const Stmt& stmt)
{
const Decl *decl = get_DeclContext_from_Stmt(context, stmt);
if (decl)
return static_cast<const FunctionDecl*>(decl->getNonClosureContext());
return nullptr;
}
bool UnusedMethods::VisitCallExpr(CallExpr* expr) bool UnusedMethods::VisitCallExpr(CallExpr* expr)
{ {
// Note that I don't ignore ANYTHING here, because I want to get calls to my code that result // Note that I don't ignore ANYTHING here, because I want to get calls to my code that result
...@@ -232,10 +276,22 @@ gotfunc: ...@@ -232,10 +276,22 @@ gotfunc:
logCallToRootMethods(calleeFunctionDecl, callSet); logCallToRootMethods(calleeFunctionDecl, callSet);
const Stmt* parent = parentStmt(expr);
// Now do the checks necessary for the "unnecessary public" analysis
CXXMethodDecl* calleeMethodDecl = dyn_cast<CXXMethodDecl>(calleeFunctionDecl);
if (calleeMethodDecl && calleeMethodDecl->getAccess() == AS_public)
{
const FunctionDecl* parentFunctionDecl = get_top_FunctionDecl_from_Stmt(compiler.getASTContext(), *expr);
if (parentFunctionDecl && parentFunctionDecl != calleeFunctionDecl) {
calledFromOutsideSet.insert(niceName(parentFunctionDecl));
}
}
// Now do the checks necessary for the "unused return value" analysis
if (calleeFunctionDecl->getReturnType()->isVoidType()) { if (calleeFunctionDecl->getReturnType()->isVoidType()) {
return true; return true;
} }
const Stmt* parent = parentStmt(expr);
if (!parent) { if (!parent) {
// we will get null parent if it's under a CXXConstructExpr node // we will get null parent if it's under a CXXConstructExpr node
logCallToRootMethods(calleeFunctionDecl, usedReturnSet); logCallToRootMethods(calleeFunctionDecl, usedReturnSet);
...@@ -283,7 +339,13 @@ bool UnusedMethods::VisitFunctionDecl( const FunctionDecl* functionDecl ) ...@@ -283,7 +339,13 @@ bool UnusedMethods::VisitFunctionDecl( const FunctionDecl* functionDecl )
} }
if( functionDecl->getLocation().isValid() && !ignoreLocation( functionDecl )) if( functionDecl->getLocation().isValid() && !ignoreLocation( functionDecl ))
definitionSet.insert(niceName(functionDecl)); {
MyFuncInfo funcInfo = niceName(functionDecl);
definitionSet.insert(funcInfo);
if (functionDecl->getAccess() == AS_public) {
publicDefinitionSet.insert(funcInfo);
}
}
return true; return true;
} }
......
...@@ -5,10 +5,13 @@ import re ...@@ -5,10 +5,13 @@ import re
import io import io
definitionSet = set() definitionSet = set()
publicDefinitionSet = set()
definitionToSourceLocationMap = dict() definitionToSourceLocationMap = dict()
callSet = set() callSet = set()
returnSet = set() usedReturnSet = set()
sourceLocationSet = set() sourceLocationSet = set()
calledFromOutsideSet = set()
# things we need to exclude for reasons like : # things we need to exclude for reasons like :
# - it's a weird template thingy that confuses the plugin # - it's a weird template thingy that confuses the plugin
exclusionSet = set([ exclusionSet = set([
...@@ -120,19 +123,35 @@ with io.open(sys.argv[1], "rb", buffering=1024*1024) as txt: ...@@ -120,19 +123,35 @@ with io.open(sys.argv[1], "rb", buffering=1024*1024) as txt:
if line.startswith("definition:\t"): if line.startswith("definition:\t"):
idx1 = line.find("\t",12) idx1 = line.find("\t",12)
idx2 = line.find("\t",idx1+1) idx2 = line.find("\t",idx1+1)
funcInfo = (normalizeTypeParams(line[12:idx1]), normalizeTypeParams(line[idx1+1:idx2])) idx3 = line.find("\t",idx2+1)
access = line[12:idx1]
returnType = line[idx1+1:idx2]
nameAndParams = line[idx2+1:idx3]
sourceLocation = line[idx3+1:].strip()
funcInfo = (normalizeTypeParams(returnType), normalizeTypeParams(nameAndParams))
definitionSet.add(funcInfo) definitionSet.add(funcInfo)
definitionToSourceLocationMap[funcInfo] = line[idx2+1:].strip() if access == "public":
publicDefinitionSet.add(funcInfo)
definitionToSourceLocationMap[funcInfo] = sourceLocation
elif line.startswith("call:\t"): elif line.startswith("call:\t"):
idx1 = line.find("\t",6) idx1 = line.find("\t",6)
callSet.add((normalizeTypeParams(line[6:idx1]), normalizeTypeParams(line[idx1+1:].strip()))) returnType = line[6:idx1]
nameAndParams = line[idx1+1:].strip()
callSet.add((normalizeTypeParams(returnType), normalizeTypeParams(nameAndParams)))
elif line.startswith("usedReturn:\t"): elif line.startswith("usedReturn:\t"):
idx1 = line.find("\t",12) idx1 = line.find("\t",12)
returnSet.add((normalizeTypeParams(line[12:idx1]), normalizeTypeParams(line[idx1+1:].strip()))) returnType = line[12:idx1]
nameAndParams = line[idx1+1:].strip()
usedReturnSet.add((normalizeTypeParams(returnType), normalizeTypeParams(nameAndParams)))
elif line.startswith("calledFromOutsideSet:\t"):
idx1 = line.find("\t",22)
returnType = line[22:idx1]
nameAndParams = line[idx1+1:].strip()
calledFromOutsideSet.add((normalizeTypeParams(returnType), normalizeTypeParams(nameAndParams)))
# Invert the definitionToSourceLocationMap # Invert the definitionToSourceLocationMap.
# If we see more than one method at the same sourceLocation, it's being autogenerated as part of a template # If we see more than one method at the same sourceLocation, it's being autogenerated as part of a template
# and we should just ignore # and we should just ignore it.
sourceLocationToDefinitionMap = {} sourceLocationToDefinitionMap = {}
for k, v in definitionToSourceLocationMap.iteritems(): for k, v in definitionToSourceLocationMap.iteritems():
sourceLocationToDefinitionMap[v] = sourceLocationToDefinitionMap.get(v, []) sourceLocationToDefinitionMap[v] = sourceLocationToDefinitionMap.get(v, [])
...@@ -253,11 +272,9 @@ tmp1list = sorted(tmp1set, key=lambda v: natural_sort_key(v[1])) ...@@ -253,11 +272,9 @@ tmp1list = sorted(tmp1set, key=lambda v: natural_sort_key(v[1]))
tmp2set = set() tmp2set = set()
for d in definitionSet: for d in definitionSet:
clazz = d[0] + " " + d[1] clazz = d[0] + " " + d[1]
if clazz in exclusionSet: if d in usedReturnSet:
continue
if d in returnSet:
continue continue
if isOtherConstness(d, returnSet): if isOtherConstness(d, usedReturnSet):
continue continue
if d[0] == "void": if d[0] == "void":
continue continue
...@@ -287,7 +304,31 @@ for d in definitionSet: ...@@ -287,7 +304,31 @@ for d in definitionSet:
# sort results by name and line number # sort results by name and line number
tmp2list = sorted(tmp2set, key=lambda v: natural_sort_key(v[1])) tmp2list = sorted(tmp2set, key=lambda v: natural_sort_key(v[1]))
for t in tmp2list: #for t in tmp2list:
# print t[1]
# print " ", t[0]
# -------------------------------------------
# Do the "unnecessary public" part
# -------------------------------------------
tmp3set = set()
for d in publicDefinitionSet:
clazz = d[0] + " " + d[1]
if d in calledFromOutsideSet:
continue
if isOtherConstness(d, calledFromOutsideSet):
continue
# ignore external code
if definitionToSourceLocationMap[d].startswith("external/"):
continue
tmp3set.add((clazz, definitionToSourceLocationMap[d]))
# sort results by name and line number
tmp3list = sorted(tmp3set, key=lambda v: natural_sort_key(v[1]))
for t in tmp3list:
print t[1] print t[1]
print " ", t[0] print " ", t[0]
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment