#!/usr/bin/python import sys import re import io # -------------------------------------------------------------------------------------------- # globals # -------------------------------------------------------------------------------------------- definitionSet = set() # set of tuple(return_type, name_and_params) definitionToSourceLocationMap = dict() calledFromDict = dict() calledFromOutsideSet = set() largeFunctionSet = set() # set of tuple(return_type, name_and_params) addressOfSet = set() # set of tuple(return_type, name_and_params) # 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 # -------------------------------------------------------------------------------------------- with io.open("workdir/loplugin.expandablemethods.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] funcInfo = (normalizeTypeParams(returnType), normalizeTypeParams(nameAndParams)) definitionSet.add(funcInfo) definitionToSourceLocationMap[funcInfo] = sourceLocation elif tokens[0] == "calledFrom:": calleeLocation = tokens[1] returnType = tokens[2] nameAndParams = tokens[3] funcInfo = (normalizeTypeParams(returnType), normalizeTypeParams(nameAndParams)) if not funcInfo in calledFromDict: calledFromDict[funcInfo] = set() calledFromDict[funcInfo].add(calleeLocation) elif tokens[0] == "outside:": returnType = tokens[1] nameAndParams = tokens[2] calledFromOutsideSet.add((normalizeTypeParams(returnType), normalizeTypeParams(nameAndParams))) elif tokens[0] == "large:": returnType = tokens[1] nameAndParams = tokens[2] largeFunctionSet.add((normalizeTypeParams(returnType), normalizeTypeParams(nameAndParams))) elif tokens[0] == "addrof:": returnType = tokens[1] nameAndParams = tokens[2] addressOfSet.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])) # -------------------------------------------------------------------------------------------- # Methods that are only called from inside their own class, and are only called from one spot # -------------------------------------------------------------------------------------------- tmp4set = set() for d in definitionSet: if d in calledFromOutsideSet: continue if isOtherConstness(d, calledFromOutsideSet): continue if d not in definitionToSourceLocationMap: print("warning, method has no location: " + d[0] + " " + d[1]) continue # ignore external code if definitionToSourceLocationMap[d].startswith("external/"): continue # ignore constructors, calledFromOutsideSet does not provide accurate info for them tokens = d[1].split("(")[0].split("::") if len(tokens)>1 and tokens[-2] == tokens[-1]: continue # ignore large methods, which make the code clearer by being out of line if d in largeFunctionSet: continue # ignore methods whose address we take if d in addressOfSet: continue # ignore unused methods, leave them to the dedicated analysis if d not in calledFromDict: continue # ignore methods called from more than one site if len(calledFromDict[d]) > 1: continue method = d[0] + " " + d[1] tmp4set.add((method, definitionToSourceLocationMap[d])) # print output, sorted by name and line number with open("loplugin.expandablemethods.report", "wt") as f: for t in sort_set_by_natural_key(tmp4set): f.write(t[1] + "\n") f.write(" " + t[0] + "\n")