#!/usr/bin/python import sys import re import io # -------------------------------------------------------------------------------------------- # globals # -------------------------------------------------------------------------------------------- definitionSet = set() # set of tuple(return_type, name_and_params) definitionToSourceLocationMap = dict() # for the "unused methods" analysis callSet = set() # set of tuple(return_type, name_and_params) # for the "method can be private" analysis publicDefinitionSet = set() # set of tuple(return_type, name_and_params) calledFromOutsideSet = set() # set of tuple(return_type, name_and_params) virtualSet = set() # set of tuple(return_type, name_and_params) # for the "unused return types" analysis usedReturnSet = set() # set of tuple(return_type, name_and_params) # things we need to exclude for reasons like : # - it's a weird template thingy that confuses the plugin unusedMethodsExclusionSet = set([ # only used by Windows build "_Bool basegfx::B2ITuple::equalZero() const", "class basegfx::B2DPolyPolygon basegfx::unotools::UnoPolyPolygon::getPolyPolygonUnsafe() const", "void basegfx::B2IRange::expand(const class basegfx::B2IRange &)", "void OpenGLContext::requestSingleBufferedRendering()", "_Bool TabitemValue::isBothAligned() const", "_Bool TabitemValue::isNotAligned() const", "void TabitemValue::isLast() const", "void StyleSettings::SetSpinSize(long)", "void StyleSettings::SetFloatTitleHeight(long)", "void StyleSettings::SetTitleHeight(long)", "void StyleSettings::SetUseFlatBorders(_Bool)", "void StyleSettings::SetUseFlatMenus(_Bool)", "void StyleSettings::SetCursorSize(long)", "_Bool CommandMediaData::GetPassThroughToOS() const", "void Application::AppEvent(const class ApplicationEvent &)", "int PhysicalFontFace::GetWidth() const", "void PhysicalFontFace::SetBitmapSize(int,int)", "class boost::intrusive_ptr FontCharMap::GetDefaultMap(_Bool)", "_Bool SalObject::IsEraseBackgroundEnabled()", "const class rtl::OUString & connectivity::OColumn::getCatalogName() const", "const class rtl::OUString & connectivity::OColumn::getSchemaName() const", "_Bool connectivity::OColumn::isDefinitelyWritable() const", "_Bool connectivity::OColumn::isReadOnly() const", "_Bool connectivity::OColumn::isWritable() const", "_Bool IDocumentLinksAdministration::GetData(const class rtl::OUString &,const class rtl::OUString &,class com::sun::star::uno::Any &) const", "_Bool IDocumentLinksAdministration::SetData(const class rtl::OUString &,const class rtl::OUString &,const class com::sun::star::uno::Any &)", "_Bool ScImportExport::ImportData(const class rtl::OUString &,const class com::sun::star::uno::Any &)", "void* ScannerManager::GetData()", "void ScannerManager::SetData(void *)", # only used by OSX build "void StyleSettings::SetHideDisabledMenuItems(_Bool)", # debugging methods "void oox::drawingml::TextParagraphProperties::dump() const", "void oox::PropertyMap::dumpCode(class com::sun::star::uno::Reference)", "void oox::PropertyMap::dumpData(class com::sun::star::uno::Reference)", "class std::basic_string, class std::allocator > writerfilter::ooxml::OOXMLPropertySet::toString()", # I need to teach the plugin that for loops with range expressions call begin() and end() "class __gnu_debug::_Safe_iterator > >, class std::__debug::vector > > SwSortedObjs::begin() const", "class __gnu_debug::_Safe_iterator > >, class std::__debug::vector > > SwSortedObjs::end() const", # loaded by dlopen() "void * getStandardAccessibleFactory()", "void * getSvtAccessibilityComponentFactory()", "struct _rtl_uString * basicide_choose_macro(void *,void *,unsigned char,struct _rtl_uString *)", "void basicide_macro_organizer(short)", "long basicide_handle_basic_error(void *)", "class com::sun::star::uno::XInterface * org_libreoffice_chart2_Chart2ToolboxController(class com::sun::star::uno::XComponentContext *,const class com::sun::star::uno::Sequence &)", "class com::sun::star::uno::XInterface * org_libreoffice_comp_chart2_sidebar_ChartPanelFactory(class com::sun::star::uno::XComponentContext *,const class com::sun::star::uno::Sequence &)", "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)", "class SalInstance * create_SalInstance()", "class SwAbstractDialogFactory * SwCreateDialogFactory()", "class com::sun::star::uno::Reference WordPerfectImportFilterDialog_createInstance(const class com::sun::star::uno::Reference &)", "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 &)", "void ExportDOC(const class rtl::OUString &,const class rtl::OUString &,class tools::SvRef &)", "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)", # only used when the ODBC driver is enabled "_Bool getImplementation(type-parameter-?-? *&,const class com::sun::star::uno::Reference &)", ]) # clang does not always use exactly the same numbers in the type-parameter vars it generates # so I need to substitute them to ensure we can match correctly. normalizeTypeParamsRegex = re.compile(r"type-parameter-\d+-\d+") def normalizeTypeParams( line ): return normalizeTypeParamsRegex.sub("type-parameter-?-?", line) # -------------------------------------------------------------------------------------------- # primary input loop # -------------------------------------------------------------------------------------------- # 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 io.open("loplugin.unusedmethods.log", "rb", buffering=1024*1024) as txt: for line in txt: tokens = line.strip().split("\t") if tokens[0] == "definition:": access = tokens[1] returnType = tokens[2] nameAndParams = tokens[3] sourceLocation = tokens[4] virtual = "" if len(tokens)>=6: virtual = tokens[5] funcInfo = (normalizeTypeParams(returnType), normalizeTypeParams(nameAndParams)) definitionSet.add(funcInfo) if access == "public": publicDefinitionSet.add(funcInfo) definitionToSourceLocationMap[funcInfo] = sourceLocation if virtual == "virtual": virtualSet.add(funcInfo) elif tokens[0] == "call:": returnType = tokens[1] nameAndParams = tokens[2] callSet.add((normalizeTypeParams(returnType), normalizeTypeParams(nameAndParams))) elif tokens[0] == "usedReturn:": returnType = tokens[1] nameAndParams = tokens[2] usedReturnSet.add((normalizeTypeParams(returnType), normalizeTypeParams(nameAndParams))) elif tokens[0] == "outside:": returnType = tokens[1] nameAndParams = tokens[2] calledFromOutsideSet.add((normalizeTypeParams(returnType), normalizeTypeParams(nameAndParams))) else: print( "unknown line: " + line) # Invert the definitionToSourceLocationMap. # 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 it. sourceLocationToDefinitionMap = {} for k, v in definitionToSourceLocationMap.iteritems(): sourceLocationToDefinitionMap[v] = sourceLocationToDefinitionMap.get(v, []) sourceLocationToDefinitionMap[v].append(k) for k, definitions in sourceLocationToDefinitionMap.iteritems(): if len(definitions) > 1: for d in definitions: definitionSet.remove(d) def isOtherConstness( d, callSet ): method = d[0] + " " + d[1] # if this method is const, and there is a non-const variant of it, and the non-const variant is in use, then leave it alone if d[0].startswith("const ") and d[1].endswith(" const"): if ((d[0][6:],d[1][:-6]) in callSet): return True elif method.endswith(" const"): method2 = method[:len(method)-6] # strip off " const" if ((d[0],method2) in callSet): return True if method.endswith(" const") and ("::iterator" in method): method2 = method[:len(method)-6] # strip off " const" method2 = method2.replace("::const_iterator", "::iterator") if ((d[0],method2) in callSet): return True # if this method is non-const, and there is a const variant of it, and the const variant is in use, then leave it alone if (not method.endswith(" const")) and ((d[0],"const " + method + " const") in callSet): return True if (not method.endswith(" const")) and ("::iterator" in method): method2 = method.replace("::iterator", "::const_iterator") + " const" if ((d[0],method2) in callSet): return True return False # 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)] def sort_set_by_natural_key(s): return sorted(s, key=lambda v: natural_sort_key(v[1])) # -------------------------------------------------------------------------------------------- # "unused methods" analysis # -------------------------------------------------------------------------------------------- tmp1set = set() # set of tuple(method, source_location) unusedSet = set() # set of tuple(return_type, name_and_params) for d in definitionSet: method = d[0] + " " + d[1] if method in unusedMethodsExclusionSet: continue if d in callSet: continue if isOtherConstness(d, callSet): continue # include assigment operators, if we remove them, the compiler creates a default one, which can have odd consequences if "::operator=(" in d[1]: continue # these are only invoked implicitly, so the plugin does not see the calls if "::operator new(" in d[1] or "::operator delete(" in d[1]: 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 # used by Windows build if any(x in d[1] for x in ["DdeTopic::", "DdeData::", "DdeService::", "DdeTransaction::", "DdeConnection::", "DdeLink::", "DdeItem::", "DdeGetPutItem::"]): continue if method == "class tools::SvRef FontCharMap::GetDefaultMap(_Bool)": continue # too much template magic here for my plugin if ( ("cairocanvas::" in d[1]) or ("canvas::" in d[1]) or ("oglcanvas::" in d[1]) or ("vclcanvas::" in d[1])): continue # these are loaded by dlopen() from somewhere if "get_implementation" in d[1]: continue if "component_getFactory" in d[1]: continue if d[0]=="_Bool" and "_supportsService(const class rtl::OUString &)" in d[1]: continue if (d[0]=="class com::sun::star::uno::Reference" and "Instance(const class com::sun::star::uno::Reference &)" in d[1]): continue # ignore the Java symbols, loaded from the JavaVM if d[1].startswith("Java_"): continue # ignore external code if definitionToSourceLocationMap[d].startswith("external/"): continue # ignore the VCL_BUILDER_DECL_FACTORY stuff if d[0]=="void" and d[1].startswith("make") and ("(class VclPtr &" in d[1]): continue # ignore methods used to dump objects to stream - normally used for debugging if d[0] == "class std::basic_ostream &" and d[1].startswith("operator<<(class std::basic_ostream &"): continue if d[0] == "basic_ostream &" and d[1].startswith("operator<<(basic_ostream>" in d[1] or "operator++" in d[1] or "operator--" in d[1]): continue # ignore external code if definitionToSourceLocationMap[d].startswith("external/"): continue # ignore the SfxPoolItem CreateDefault methods for now if d[1].endswith("::CreateDefault()"): continue # ignore UNO constructor functions if (d[0] == "class com::sun::star::uno::Reference" and d[1].endswith("_createInstance(const class com::sun::star::uno::Reference &)")): continue if (d[0] == "class com::sun::star::uno::Reference" and d[1].endswith("_CreateInstance(const class com::sun::star::uno::Reference &)")): continue # debug code if d[1] == "writerfilter::ooxml::OOXMLPropertySet::toString()": continue location = definitionToSourceLocationMap[d]; # windows only if location.startswith("include/svl/svdde.hxx"): continue # fluent API (return ref to self) if location.startswith("include/tools/stream.hxx"): continue tmp2set.add((method, location)) # print output, sorted by name and line number with open("loplugin.unusedmethods.report-unused-returns", "wt") as f: for t in sort_set_by_natural_key(tmp2set): f.write(t[1] + "\n") f.write(" " + t[0] + "\n") # -------------------------------------------------------------------------------------------- # "method can be private" analysis # -------------------------------------------------------------------------------------------- tmp3set = set() for d in publicDefinitionSet: method = d[0] + " " + d[1] if d in calledFromOutsideSet: continue if d in virtualSet: continue # TODO ignore constructors for now, my called-from-outside analysis doesn't work here if d[0] == "": continue if isOtherConstness(d, calledFromOutsideSet): continue # ignore external code if definitionToSourceLocationMap[d].startswith("external/"): continue tmp3set.add((method, definitionToSourceLocationMap[d])) # print output, sorted by name and line number with open("loplugin.unusedmethods.report-can-be-private", "wt") as f: for t in sort_set_by_natural_key(tmp3set): f.write(t[1] + "\n") f.write(" " + t[0] + "\n")