52b08068c9
Change-Id: Ifd5fdee23e5adba2b5a667f74c38ba6fb28f54b4 Reviewed-on: https://gerrit.libreoffice.org/c/core/+/165446 Tested-by: Ilmari Lauhakangas <ilmari.lauhakangas@libreoffice.org> Tested-by: Jenkins Reviewed-by: Ilmari Lauhakangas <ilmari.lauhakangas@libreoffice.org>
149 lines
6.5 KiB
Python
Executable file
149 lines
6.5 KiB
Python
Executable file
#!/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")
|