diff options
author | Noel Grandin <noel@peralex.com> | 2015-10-02 08:37:23 +0200 |
---|---|---|
committer | Noel Grandin <noel@peralex.com> | 2015-10-06 10:17:02 +0200 |
commit | 7e776c0027c19f1bb8e64dd68d3fd9ded0b5d896 (patch) | |
tree | 62bae1461c0388af6f7a8bebbf134e9a86c92153 /compilerplugins | |
parent | d7f2db4b9ce445afdcabf370497bc66db76efbbc (diff) |
loplugin:unusedmethods
Change-Id: I150baadc442e57ee604563bc52965daa9d2e41af
Diffstat (limited to 'compilerplugins')
-rw-r--r-- | compilerplugins/clang/unusedmethods.cxx | 145 | ||||
-rwxr-xr-x | compilerplugins/clang/unusedmethods.py | 38 |
2 files changed, 113 insertions, 70 deletions
diff --git a/compilerplugins/clang/unusedmethods.cxx b/compilerplugins/clang/unusedmethods.cxx index 31ff68da6093..e5fab5b9c023 100644 --- a/compilerplugins/clang/unusedmethods.cxx +++ b/compilerplugins/clang/unusedmethods.cxx @@ -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); } diff --git a/compilerplugins/clang/unusedmethods.py b/compilerplugins/clang/unusedmethods.py index f2b9e5c4cf54..1ea23943a27d 100755 --- a/compilerplugins/clang/unusedmethods.py +++ b/compilerplugins/clang/unusedmethods.py @@ -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)] |