summaryrefslogtreecommitdiffstats
path: root/compilerplugins/clang/expandablemethods.py
diff options
context:
space:
mode:
Diffstat (limited to '')
-rwxr-xr-xcompilerplugins/clang/expandablemethods.py150
1 files changed, 150 insertions, 0 deletions
diff --git a/compilerplugins/clang/expandablemethods.py b/compilerplugins/clang/expandablemethods.py
new file mode 100755
index 000000000..0fa61747d
--- /dev/null
+++ b/compilerplugins/clang/expandablemethods.py
@@ -0,0 +1,150 @@
+#!/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)]
+# 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")