summaryrefslogtreecommitdiff
path: root/compilerplugins/clang/expandablemethods.py
blob: 934a5f6580edb5daa3fbab3ceec05f4d9b92e1fe (plain)
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
#!/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("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")