bump product version to 6.3.0.0.beta1
[LibreOffice.git] / compilerplugins / clang / unusedmethods.py
blobd4af2f712bf7ba86a3ae59a3d090f258864bcb31
1 #!/usr/bin/python
3 import sys
4 import re
5 import io
7 # --------------------------------------------------------------------------------------------
8 # globals
9 # --------------------------------------------------------------------------------------------
11 definitionSet = set() # set of tuple(return_type, name_and_params)
12 definitionToSourceLocationMap = dict()
14 # for the "unused methods" analysis
15 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)
23 # for the "unused return types" analysis
24 usedReturnSet = set() # set of tuple(return_type, name_and_params)
26 # clang does not always use exactly the same numbers in the type-parameter vars it generates
27 # so I need to substitute them to ensure we can match correctly.
28 normalizeTypeParamsRegex = re.compile(r"type-parameter-\d+-\d+")
29 def normalizeTypeParams( line ):
30 return normalizeTypeParamsRegex.sub("type-parameter-?-?", line)
32 # --------------------------------------------------------------------------------------------
33 # primary input loop
34 # --------------------------------------------------------------------------------------------
36 with io.open("workdir/loplugin.unusedmethods.log", "rb", buffering=1024*1024) as txt:
37 for line in txt:
38 tokens = line.strip().split("\t")
39 if tokens[0] == "definition:":
40 access = tokens[1]
41 returnType = tokens[2]
42 nameAndParams = tokens[3]
43 sourceLocation = tokens[4]
44 virtual = ""
45 if len(tokens)>=6: virtual = tokens[5]
46 funcInfo = (normalizeTypeParams(returnType), normalizeTypeParams(nameAndParams))
47 definitionSet.add(funcInfo)
48 if access == "public":
49 publicDefinitionSet.add(funcInfo)
50 elif access == "protected":
51 protectedDefinitionSet.add(funcInfo)
52 definitionToSourceLocationMap[funcInfo] = sourceLocation
53 if virtual == "virtual":
54 virtualSet.add(funcInfo)
55 elif tokens[0] == "call:":
56 returnType = tokens[1]
57 nameAndParams = tokens[2]
58 callSet.add((normalizeTypeParams(returnType), normalizeTypeParams(nameAndParams)))
59 elif tokens[0] == "usedReturn:":
60 returnType = tokens[1]
61 nameAndParams = tokens[2]
62 usedReturnSet.add((normalizeTypeParams(returnType), normalizeTypeParams(nameAndParams)))
63 elif tokens[0] == "outside:":
64 returnType = tokens[1]
65 nameAndParams = tokens[2]
66 calledFromOutsideSet.add((normalizeTypeParams(returnType), normalizeTypeParams(nameAndParams)))
67 else:
68 print( "unknown line: " + line)
70 # Invert the definitionToSourceLocationMap.
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.
73 sourceLocationToDefinitionMap = {}
74 for k, v in definitionToSourceLocationMap.iteritems():
75 sourceLocationToDefinitionMap[v] = sourceLocationToDefinitionMap.get(v, [])
76 sourceLocationToDefinitionMap[v].append(k)
77 for k, definitions in sourceLocationToDefinitionMap.iteritems():
78 if len(definitions) > 1:
79 for d in definitions:
80 definitionSet.remove(d)
82 def isOtherConstness( d, callSet ):
83 method = d[0] + " " + d[1]
84 # 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
85 if d[0].startswith("const ") and d[1].endswith(" const"):
86 if ((d[0][6:],d[1][:-6]) in callSet):
87 return True
88 elif method.endswith(" const"):
89 method2 = method[:len(method)-6] # strip off " const"
90 if ((d[0],method2) in callSet):
91 return True
92 if method.endswith(" const") and ("::iterator" in method):
93 method2 = method[:len(method)-6] # strip off " const"
94 method2 = method2.replace("::const_iterator", "::iterator")
95 if ((d[0],method2) in callSet):
96 return True
97 # 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 if (not method.endswith(" const")) and ("::iterator" in method):
101 method2 = method.replace("::iterator", "::const_iterator") + " const"
102 if ((d[0],method2) in callSet):
103 return True
104 return False
106 # sort the results using a "natural order" so sequences like [item1,item2,item10] sort nicely
107 def natural_sort_key(s, _nsre=re.compile('([0-9]+)')):
108 return [int(text) if text.isdigit() else text.lower()
109 for text in re.split(_nsre, s)]
110 def sort_set_by_natural_key(s):
111 return sorted(s, key=lambda v: natural_sort_key(v[1]))
114 # --------------------------------------------------------------------------------------------
115 # "unused methods" analysis
116 # --------------------------------------------------------------------------------------------
118 tmp1set = set() # set of tuple(method, source_location)
119 unusedSet = set() # set of tuple(return_type, name_and_params)
120 for d in definitionSet:
121 method = d[0] + " " + d[1]
122 if d in callSet:
123 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 # these are only invoked implicitly, so the plugin does not see the calls
130 if "::operator new(" in d[1] or "::operator delete(" in d[1]:
131 continue
132 # just ignore iterators, they normally occur in pairs, and we typically want to leave one constness version alone
133 # alone if the other one is in use.
134 if d[1] == "begin() const" or d[1] == "begin()" or d[1] == "end()" or d[1] == "end() const":
135 continue
136 # used by Windows build
137 if any(x in d[1] for x in ["DdeTopic::", "DdeData::", "DdeService::", "DdeTransaction::", "DdeConnection::", "DdeLink::", "DdeItem::", "DdeGetPutItem::"]):
138 continue
139 if method == "class tools::SvRef<class FontCharMap> FontCharMap::GetDefaultMap(_Bool)":
140 continue
141 # these are loaded by dlopen() from somewhere
142 if "get_implementation" in d[1]:
143 continue
144 if "component_getFactory" in d[1]:
145 continue
146 if d[0]=="_Bool" and "_supportsService(const class rtl::OUString &)" in d[1]:
147 continue
148 if (d[0]=="class com::sun::star::uno::Reference<class com::sun::star::uno::XInterface>"
149 and "Instance(const class com::sun::star::uno::Reference<class com::sun::star::lang::XMultiServiceFactory> &)" in d[1]):
150 continue
151 # ignore the Java symbols, loaded from the JavaVM
152 if d[1].startswith("Java_"):
153 continue
154 # ignore external code
155 if definitionToSourceLocationMap[d].startswith("external/"):
156 continue
157 # ignore the VCL_BUILDER_DECL_FACTORY stuff
158 if d[0]=="void" and d[1].startswith("make") and ("(class VclPtr<class vcl::Window> &" in d[1]):
159 continue
160 # ignore methods used to dump objects to stream - normally used for debugging
161 if d[0] == "class std::basic_ostream<char> &" and d[1].startswith("operator<<(class std::basic_ostream<char> &"):
162 continue
163 if d[0] == "basic_ostream<type-parameter-?-?, type-parameter-?-?> &" and d[1].startswith("operator<<(basic_ostream<type-parameter-?-?"):
164 continue
166 location = definitionToSourceLocationMap[d];
167 # whacky template stuff
168 if location.startswith("sc/source/ui/vba/vbaformat.hxx"): continue
169 # not sure how this stuff is called
170 if location.startswith("include/test"): continue
171 # leave the debug/dump alone
172 if location.startswith("include/oox/dump"): continue
173 # plugin testing stuff
174 if location.startswith("compilerplugins/clang/test"): continue
175 # leave this alone for now
176 if location.startswith("include/LibreOfficeKit"): continue
178 unusedSet.add(d) # used by the "unused return types" analysis
179 tmp1set.add((method, location))
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):
184 f.write(t[1] + "\n")
185 f.write(" " + t[0] + "\n")
187 # --------------------------------------------------------------------------------------------
188 # "unused return types" analysis
189 # --------------------------------------------------------------------------------------------
191 tmp2set = set()
192 for d in definitionSet:
193 method = d[0] + " " + d[1]
194 if d in usedReturnSet:
195 continue
196 if d in unusedSet:
197 continue
198 if isOtherConstness(d, usedReturnSet):
199 continue
200 # ignore methods with no return type, and constructors
201 if d[0] == "void" or d[0] == "":
202 continue
203 # ignore UNO constructor method entrypoints
204 if "_get_implementation" in d[1] or "_getFactory" in d[1]:
205 continue
206 # the plugin can't see calls to these
207 if "::operator new" in d[1]:
208 continue
209 # unused return type is not a problem here
210 if ("operator=(" in d[1] or "operator&=" in d[1] or "operator|=" in d[1] or "operator^=" in d[1]
211 or "operator+=" in d[1] or "operator-=" in d[1]
212 or "operator<<" in d[1] or "operator>>" in d[1]
213 or "operator++" in d[1] or "operator--" in d[1]):
214 continue
215 # ignore external code
216 if definitionToSourceLocationMap[d].startswith("external/"):
217 continue
218 # ignore UNO constructor functions
219 if (d[0] == "class com::sun::star::uno::Reference<class com::sun::star::uno::XInterface>" and
220 d[1].endswith("_createInstance(const class com::sun::star::uno::Reference<class com::sun::star::lang::XMultiServiceFactory> &)")):
221 continue
222 if (d[0] == "class com::sun::star::uno::Reference<class com::sun::star::uno::XInterface>" and
223 d[1].endswith("_CreateInstance(const class com::sun::star::uno::Reference<class com::sun::star::lang::XMultiServiceFactory> &)")):
224 continue
225 # debug code
226 if d[1] == "writerfilter::ooxml::OOXMLPropertySet::toString()":
227 continue
228 location = definitionToSourceLocationMap[d];
229 # windows only
230 if location.startswith("include/svl/svdde.hxx"): continue
231 # fluent API (return ref to self)
232 if location.startswith("include/tools/stream.hxx"): continue
233 tmp2set.add((method, location))
235 # print output, sorted by name and line number
236 with open("compilerplugins/clang/unusedmethods.unused-returns.results", "wt") as f:
237 for t in sort_set_by_natural_key(tmp2set):
238 f.write(t[1] + "\n")
239 f.write(" " + t[0] + "\n")
242 # --------------------------------------------------------------------------------------------
243 # "method can be private" analysis
244 # --------------------------------------------------------------------------------------------
246 tmp3set = set()
247 for d in publicDefinitionSet:
248 method = d[0] + " " + d[1]
249 if d in calledFromOutsideSet:
250 continue
251 if d in virtualSet:
252 continue
253 # TODO ignore constructors for now, my called-from-outside analysis doesn't work here
254 if d[0] == "":
255 continue
256 if isOtherConstness(d, calledFromOutsideSet):
257 continue
258 # ignore external code
259 if definitionToSourceLocationMap[d].startswith("external/"):
260 continue
261 tmp3set.add((method, definitionToSourceLocationMap[d]))
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):
266 f.write(t[1] + "\n")
267 f.write(" " + t[0] + "\n")
271 # --------------------------------------------------------------------------------------------
272 # "all protected methods in class can be made private" analysis
273 # --------------------------------------------------------------------------------------------
275 potentialClasses = set()
276 excludedClasses = set()
277 potentialClassesSourceLocationMap = dict()
278 matchClassName = re.compile(r"(\w+)::")
279 for d in protectedDefinitionSet:
280 m = matchClassName.match(d[1])
281 if not m: continue
282 clazz = m.group(1)
283 if d in calledFromOutsideSet:
284 excludedClasses.add(clazz)
285 else:
286 potentialClasses.add(clazz)
287 potentialClassesSourceLocationMap[clazz] = definitionToSourceLocationMap[d]
289 tmp4set = set()
290 for d in (potentialClasses - excludedClasses):
291 tmp4set.add((d, potentialClassesSourceLocationMap[d]))
293 # print output, sorted by name and line number
294 with open("loplugin.unusedmethods.report-all-protected-can-be-private", "wt") as f:
295 for t in sort_set_by_natural_key(tmp4set):
296 f.write(t[1] + "\n")
297 f.write(" " + t[0] + "\n")