1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149
|
#!/usr/bin/python
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 funcInfo not 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)]
# sort by both the source-line and the datatype, so the output file ordering is stable
# when we have multiple items on the same source line
def v_sort_key(v):
return natural_sort_key(v[1]) + [v[0]]
def sort_set_by_natural_key(s):
return sorted(s, key=lambda v: v_sort_key(v))
# --------------------------------------------------------------------------------------------
# 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")
|