unusedmethods.py 12.8 KB
Newer Older
1 2 3
#!/usr/bin/python

import sys
Noel Grandin's avatar
Noel Grandin committed
4
import re
Noel Grandin's avatar
Noel Grandin committed
5
import io
6

7 8 9 10 11
# --------------------------------------------------------------------------------------------
# globals
# --------------------------------------------------------------------------------------------

definitionSet = set() # set of tuple(return_type, name_and_params)
Noel Grandin's avatar
Noel Grandin committed
12
definitionToSourceLocationMap = dict()
13 14 15 16

# for the "unused methods" analysis
callSet = set() # set of tuple(return_type, name_and_params)

17
# for the "method can be private" analysis
18
publicDefinitionSet = set() # set of tuple(return_type, name_and_params)
19
protectedDefinitionSet = set() # set of tuple(return_type, name_and_params)
20
calledFromOutsideSet = set() # set of tuple(return_type, name_and_params)
21
virtualSet = set() # set of tuple(return_type, name_and_params)
22 23 24 25

# for the "unused return types" analysis
usedReturnSet = set() # set of tuple(return_type, name_and_params)

Noel Grandin's avatar
Noel Grandin committed
26 27 28 29 30
# 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)
31

32 33 34 35
# --------------------------------------------------------------------------------------------
# primary input loop
# --------------------------------------------------------------------------------------------

36
with io.open("workdir/loplugin.unusedmethods.log", "rb", buffering=1024*1024) as txt:
37
    for line in txt:
38 39 40 41 42 43
        tokens = line.strip().split("\t")
        if tokens[0] == "definition:":
            access = tokens[1]
            returnType = tokens[2]
            nameAndParams = tokens[3]
            sourceLocation = tokens[4]
44 45
            virtual = ""
            if len(tokens)>=6: virtual = tokens[5]
46
            funcInfo = (normalizeTypeParams(returnType), normalizeTypeParams(nameAndParams))
Noel Grandin's avatar
Noel Grandin committed
47
            definitionSet.add(funcInfo)
48 49
            if access == "public":
                publicDefinitionSet.add(funcInfo)
50 51
            elif access == "protected":
                protectedDefinitionSet.add(funcInfo)
52
            definitionToSourceLocationMap[funcInfo] = sourceLocation
53 54
            if virtual == "virtual":
                virtualSet.add(funcInfo)
55 56 57
        elif tokens[0] == "call:":
            returnType = tokens[1]
            nameAndParams = tokens[2]
58
            callSet.add((normalizeTypeParams(returnType), normalizeTypeParams(nameAndParams)))
59 60 61
        elif tokens[0] == "usedReturn:":
            returnType = tokens[1]
            nameAndParams = tokens[2]
62
            usedReturnSet.add((normalizeTypeParams(returnType), normalizeTypeParams(nameAndParams)))
63
        elif tokens[0] == "outside:":
64 65
            returnType = tokens[1]
            nameAndParams = tokens[2]
66
            calledFromOutsideSet.add((normalizeTypeParams(returnType), normalizeTypeParams(nameAndParams)))
67 68
        else:
            print( "unknown line: " + line)
69

70
# Invert the definitionToSourceLocationMap.
Noel Grandin's avatar
Noel Grandin committed
71
# If we see more than one method at the same sourceLocation, it's being autogenerated as part of a template
72
# and we should just ignore it.
Noel Grandin's avatar
Noel Grandin committed
73 74 75 76 77 78 79 80
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)
81 82

def isOtherConstness( d, callSet ):
83
    method = d[0] + " " + d[1]
84 85 86 87
    # 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
88 89 90
    elif method.endswith(" const"):
        method2 = method[:len(method)-6] # strip off " const"
        if ((d[0],method2) in callSet):
91
           return True
92 93 94 95
    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):
96 97
           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
98
    if (not method.endswith(" const")) and ((d[0],"const " + method + " const") in callSet):
99
           return True
100 101 102
    if (not method.endswith(" const")) and ("::iterator" in method):
        method2 = method.replace("::iterator", "::const_iterator") + " const"
        if ((d[0],method2) in callSet):
103 104 105
           return True
    return False

106 107 108 109 110 111 112
# 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]))

Noel Grandin's avatar
Noel Grandin committed
113
    
114 115 116
# --------------------------------------------------------------------------------------------
#  "unused methods" analysis
# --------------------------------------------------------------------------------------------
117

118 119
tmp1set = set() # set of tuple(method, source_location)
unusedSet = set() # set of tuple(return_type, name_and_params)
Noel Grandin's avatar
Noel Grandin committed
120
for d in definitionSet:
121
    method = d[0] + " " + d[1]
Noel Grandin's avatar
Noel Grandin committed
122 123
    if d in callSet:
        continue
124
    if isOtherConstness(d, callSet):
125
        continue
126
    # exclude assignment operators, if we remove them, the compiler creates a default one, which can have odd consequences
127
    if "::operator=(" in d[1]:
128
        continue
129 130
    # these are only invoked implicitly, so the plugin does not see the calls
    if "::operator new(" in d[1] or "::operator delete(" in d[1]:
131
        continue
Noel Grandin's avatar
Noel Grandin committed
132 133 134 135
    # just ignore iterators, they normally occur in pairs, and we typically want to leave one constness version alone
    # alone if the other one is in use.
    if d[1] == "begin() const" or d[1] == "begin()" or d[1] == "end()" or d[1] == "end() const":
        continue
136
    # used by Windows build
Noel Grandin's avatar
Noel Grandin committed
137
    if any(x in d[1] for x in ["DdeTopic::", "DdeData::", "DdeService::", "DdeTransaction::", "DdeConnection::", "DdeLink::", "DdeItem::", "DdeGetPutItem::"]):
138
       continue
139 140
    if method == "class tools::SvRef<class FontCharMap> FontCharMap::GetDefaultMap(_Bool)":
       continue
Noel Grandin's avatar
Noel Grandin committed
141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159
    # these are loaded by dlopen() from somewhere
    if "get_implementation" in d[1]:
       continue
    if "component_getFactory" in d[1]:
       continue
    if d[0]=="_Bool" and "_supportsService(const class rtl::OUString &)" in d[1]:
       continue
    if (d[0]=="class com::sun::star::uno::Reference<class com::sun::star::uno::XInterface>"
        and "Instance(const class com::sun::star::uno::Reference<class com::sun::star::lang::XMultiServiceFactory> &)" in d[1]):
       continue
    # ignore the Java symbols, loaded from the JavaVM
    if d[1].startswith("Java_"):
       continue
    # ignore external code
    if definitionToSourceLocationMap[d].startswith("external/"):
       continue
    # ignore the VCL_BUILDER_DECL_FACTORY stuff
    if d[0]=="void" and d[1].startswith("make") and ("(class VclPtr<class vcl::Window> &" in d[1]):
       continue
Noel Grandin's avatar
Noel Grandin committed
160 161 162
    # ignore methods used to dump objects to stream - normally used for debugging
    if d[0] == "class std::basic_ostream<char> &" and d[1].startswith("operator<<(class std::basic_ostream<char> &"):
       continue
Noel Grandin's avatar
Noel Grandin committed
163 164
    if d[0] == "basic_ostream<type-parameter-?-?, type-parameter-?-?> &" and d[1].startswith("operator<<(basic_ostream<type-parameter-?-?"):
       continue
165

166 167 168
    location = definitionToSourceLocationMap[d];
    # whacky template stuff
    if location.startswith("sc/source/ui/vba/vbaformat.hxx"): continue
169 170 171 172
    # not sure how this stuff is called
    if location.startswith("include/test"): continue
    # leave the debug/dump alone
    if location.startswith("include/oox/dump"): continue
Noel Grandin's avatar
Noel Grandin committed
173 174 175 176
    # plugin testing stuff
    if location.startswith("compilerplugins/clang/test"): continue
    # leave this alone for now
    if location.startswith("include/LibreOfficeKit"): continue
Noel Grandin's avatar
Noel Grandin committed
177

178
    unusedSet.add(d) # used by the "unused return types" analysis
179
    tmp1set.add((method, location))
Noel Grandin's avatar
Noel Grandin committed
180

181
# print out the results, sorted by name and line number
182
with open("compilerplugins/clang/unusedmethods.results", "wt") as f:
183
    for t in sort_set_by_natural_key(tmp1set):
Noel Grandin's avatar
Noel Grandin committed
184 185
        f.write(t[1] + "\n")
        f.write("    " + t[0] + "\n")
186

187 188 189
# --------------------------------------------------------------------------------------------
# "unused return types" analysis
# --------------------------------------------------------------------------------------------
190 191 192

tmp2set = set()
for d in definitionSet:
193
    method = d[0] + " " + d[1]
194
    if d in usedReturnSet:
195
        continue
196 197
    if d in unusedSet:
        continue
198
    if isOtherConstness(d, usedReturnSet):
199
        continue
200 201
    # ignore methods with no return type, and constructors
    if d[0] == "void" or d[0] == "":
202 203
        continue
    # ignore UNO constructor method entrypoints
204
    if "_get_implementation" in d[1] or "_getFactory" in d[1]:
205 206
        continue
    # the plugin can't see calls to these
Noel Grandin's avatar
Noel Grandin committed
207
    if "::operator new" in d[1]:
208 209
        continue
    # unused return type is not a problem here
210 211 212 213
    if ("operator=(" in d[1] or "operator&=" in d[1] or "operator|=" in d[1] or "operator^=" in d[1]
        or "operator+=" in d[1] or "operator-=" in d[1]
        or "operator<<" in d[1] or "operator>>" in d[1]
        or "operator++" in d[1] or "operator--" in d[1]):
214 215 216 217
        continue
    # ignore external code
    if definitionToSourceLocationMap[d].startswith("external/"):
       continue
218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233
    # ignore UNO constructor functions
    if (d[0] == "class com::sun::star::uno::Reference<class com::sun::star::uno::XInterface>" and
        d[1].endswith("_createInstance(const class com::sun::star::uno::Reference<class com::sun::star::lang::XMultiServiceFactory> &)")):
        continue
    if (d[0] == "class com::sun::star::uno::Reference<class com::sun::star::uno::XInterface>" and
        d[1].endswith("_CreateInstance(const class com::sun::star::uno::Reference<class com::sun::star::lang::XMultiServiceFactory> &)")):
        continue
    # debug code
    if d[1] == "writerfilter::ooxml::OOXMLPropertySet::toString()":
        continue
    location = definitionToSourceLocationMap[d];
    # windows only
    if location.startswith("include/svl/svdde.hxx"): continue
    # fluent API (return ref to self)
    if location.startswith("include/tools/stream.hxx"): continue
    tmp2set.add((method, location))
Noel Grandin's avatar
Noel Grandin committed
234

235
# print output, sorted by name and line number
236
with open("compilerplugins/clang/unusedmethods.unused-returns.results", "wt") as f:
237 238
    for t in sort_set_by_natural_key(tmp2set):
        f.write(t[1] + "\n")
Noel Grandin's avatar
Noel Grandin committed
239
        f.write("    " +  t[0] + "\n")
240 241


242
# --------------------------------------------------------------------------------------------
243
# "method can be private" analysis
244
# --------------------------------------------------------------------------------------------
245 246 247

tmp3set = set()
for d in publicDefinitionSet:
248
    method = d[0] + " " + d[1]
249 250
    if d in calledFromOutsideSet:
        continue
251 252
    if d in virtualSet:
        continue
253 254 255
    # TODO ignore constructors for now, my called-from-outside analysis doesn't work here
    if d[0] == "":
        continue
256 257 258 259 260
    if isOtherConstness(d, calledFromOutsideSet):
        continue
    # ignore external code
    if definitionToSourceLocationMap[d].startswith("external/"):
       continue
261
    tmp3set.add((method, definitionToSourceLocationMap[d]))
262

263
# print output, sorted by name and line number
264
with open("loplugin.unusedmethods.report-can-be-private", "wt") as f:
265
    for t in sort_set_by_natural_key(tmp3set):
Noel Grandin's avatar
Noel Grandin committed
266 267
        f.write(t[1] + "\n")
        f.write("    " + t[0] + "\n")
268

269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298


# --------------------------------------------------------------------------------------------
# "all protected methods in class can be made private" analysis
# --------------------------------------------------------------------------------------------

potentialClasses = set()
excludedClasses = set()
potentialClassesSourceLocationMap = dict()
matchClassName = re.compile(r"(\w+)::")
for d in protectedDefinitionSet:
    m = matchClassName.match(d[1])
    if not m: continue
    clazz = m.group(1)
    if d in calledFromOutsideSet:
        excludedClasses.add(clazz)
    else:
        potentialClasses.add(clazz)
        potentialClassesSourceLocationMap[clazz] = definitionToSourceLocationMap[d]

tmp4set = set()
for d in (potentialClasses - excludedClasses):
    tmp4set.add((d, potentialClassesSourceLocationMap[d]))

# print output, sorted by name and line number
with open("loplugin.unusedmethods.report-all-protected-can-be-private", "wt") as f:
    for t in sort_set_by_natural_key(tmp4set):
        f.write(t[1] + "\n")
        f.write("    " + t[0] + "\n")