tdf#130857 qt weld: Support "Insert Breaks" dialog
[LibreOffice.git] / compilerplugins / clang / unusedmethods.py
bloba69ea6529d4adbed8f98149178a23fdb0b8c3927
1 #!/usr/bin/python3
3 import re
4 import io
6 # --------------------------------------------------------------------------------------------
7 # globals
8 # --------------------------------------------------------------------------------------------
10 definitionSet = set() # set of tuple(return_type, name_and_params)
11 definitionToSourceLocationMap = dict()
13 # for the "unused methods" analysis
14 callSet = set() # set of tuple(return_type, name_and_params)
16 # for the "method can be private" analysis
17 publicDefinitionSet = set() # set of tuple(return_type, name_and_params)
18 protectedDefinitionSet = set() # set of tuple(return_type, name_and_params)
19 calledFromOutsideSet = set() # set of tuple(return_type, name_and_params)
20 virtualSet = set() # set of tuple(return_type, name_and_params)
22 # for the "unused return types" analysis
23 usedReturnSet = set() # set of tuple(return_type, name_and_params)
25 # clang does not always use exactly the same numbers in the type-parameter vars it generates
26 # so I need to substitute them to ensure we can match correctly.
27 normalizeTypeParamsRegex1 = re.compile(r"type-parameter-\d+-\d+")
28 # clang sometimes generates a type name as either "class Foo" or "Foo"
29 # so I need to substitute them to ensure we can match correctly.
30 normalizeTypeParamsRegex2 = re.compile(r"class ")
31 def normalizeTypeParams( line ):
32 line = normalizeTypeParamsRegex1.sub("type-parameter-?-?", line)
33 line = normalizeTypeParamsRegex2.sub("", line)
34 return line
36 # --------------------------------------------------------------------------------------------
37 # primary input loop
38 # --------------------------------------------------------------------------------------------
40 with io.open("workdir/loplugin.unusedmethods.log", "r", buffering=16*1024*1024) as txt:
41 for line in txt:
42 tokens = line.strip().split("\t")
43 if tokens[0] == "definition:":
44 access = tokens[1]
45 returnType = tokens[2]
46 nameAndParams = tokens[3]
47 sourceLocation = tokens[4]
48 virtual = ""
49 if len(tokens)>=6:
50 virtual = tokens[5]
51 funcInfo = (normalizeTypeParams(returnType), normalizeTypeParams(nameAndParams))
52 definitionSet.add(funcInfo)
53 if access == "public":
54 publicDefinitionSet.add(funcInfo)
55 elif access == "protected":
56 protectedDefinitionSet.add(funcInfo)
57 definitionToSourceLocationMap[funcInfo] = sourceLocation
58 if virtual == "virtual":
59 virtualSet.add(funcInfo)
60 elif tokens[0] == "call:":
61 returnType = tokens[1]
62 nameAndParams = tokens[2]
63 callSet.add((normalizeTypeParams(returnType), normalizeTypeParams(nameAndParams)))
64 elif tokens[0] == "usedReturn:":
65 returnType = tokens[1]
66 nameAndParams = tokens[2]
67 usedReturnSet.add((normalizeTypeParams(returnType), normalizeTypeParams(nameAndParams)))
68 elif tokens[0] == "outside:":
69 returnType = tokens[1]
70 nameAndParams = tokens[2]
71 calledFromOutsideSet.add((normalizeTypeParams(returnType), normalizeTypeParams(nameAndParams)))
72 else:
73 print( "unknown line: " + line)
75 # Invert the definitionToSourceLocationMap.
76 sourceLocationToDefinitionMap = {}
77 for k, v in definitionToSourceLocationMap.items():
78 sourceLocationToDefinitionMap[v] = sourceLocationToDefinitionMap.get(v, [])
79 sourceLocationToDefinitionMap[v].append(k)
81 def isOtherConstness( d, callSet ):
82 method = d[0] + " " + d[1]
83 # 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
84 if d[0].startswith("const ") and d[1].endswith(" const"):
85 if ((d[0][6:],d[1][:-6]) in callSet):
86 return True
87 elif method.endswith(" const"):
88 method2 = method[:len(method)-6] # strip off " const"
89 if ((d[0],method2) in callSet):
90 return True
91 if method.endswith(" const") and ("::iterator" in method):
92 method2 = method[:len(method)-6] # strip off " const"
93 method2 = method2.replace("::const_iterator", "::iterator")
94 if ((d[0],method2) in callSet):
95 return True
96 # 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
97 if (not method.endswith(" const")) and ((d[0],"const " + method + " const") in callSet):
98 return True
99 if (not method.endswith(" const")) and ("::iterator" in method):
100 method2 = method.replace("::iterator", "::const_iterator") + " const"
101 if ((d[0],method2) in callSet):
102 return True
103 return False
105 # sort the results using a "natural order" so sequences like [item1,item2,item10] sort nicely
106 def natural_sort_key(s, _nsre=re.compile('([0-9]+)')):
107 return [int(text) if text.isdigit() else text.lower()
108 for text in re.split(_nsre, s)]
109 # sort by both the source-line and the datatype, so the output file ordering is stable
110 # when we have multiple items on the same source line
111 def v_sort_key(v):
112 return natural_sort_key(v[1]) + [v[0]]
113 def sort_set_by_natural_key(s):
114 return sorted(s, key=lambda v: v_sort_key(v))
117 # --------------------------------------------------------------------------------------------
118 # "unused methods" analysis
119 # --------------------------------------------------------------------------------------------
121 tmp1set = set() # set of tuple(method, source_location)
122 unusedSet = set() # set of tuple(return_type, name_and_params)
123 for d in definitionSet:
124 method = d[0] + " " + d[1]
125 if d in callSet:
126 continue
127 if isOtherConstness(d, callSet):
128 continue
129 # exclude assignment operators, if we remove them, the compiler creates a default one, which can have odd consequences
130 if "::operator=(" in d[1]:
131 continue
132 # these are only invoked implicitly, so the plugin does not see the calls
133 if "::operator new(" in d[1] or "::operator delete(" in d[1]:
134 continue
135 # just ignore iterators, they normally occur in pairs, and we typically want to leave one constness version alone
136 # alone if the other one is in use.
137 if d[1] == "begin() const" or d[1] == "begin()" or d[1] == "end()" or d[1] == "end() const":
138 continue
139 # used by Windows build
140 if any(x in d[1] for x in ["DdeTopic::", "DdeData::", "DdeService::", "DdeTransaction::", "DdeConnection::", "DdeLink::", "DdeItem::", "DdeGetPutItem::"]):
141 continue
142 if method == "class tools::SvRef<class FontCharMap> FontCharMap::GetDefaultMap(_Bool)":
143 continue
144 # these are loaded by dlopen() from somewhere
145 if "get_implementation" in d[1]:
146 continue
147 if "component_getFactory" in d[1]:
148 continue
149 if d[0]=="_Bool" and "_supportsService(const class rtl::OUString &)" in d[1]:
150 continue
151 if (d[0]=="class com::sun::star::uno::Reference<class com::sun::star::uno::XInterface>"
152 and "Instance(const class com::sun::star::uno::Reference<class com::sun::star::lang::XMultiServiceFactory> &)" in d[1]):
153 continue
154 # ignore the Java symbols, loaded from the JavaVM
155 if d[1].startswith("Java_"):
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
165 # ignore lambdas
166 if (" ::operator " in method) or (" ::__invoke(" in method) or (" ::operator())" in method):
167 continue
168 if ("(lambda at " in method):
169 continue
170 # ignore stuff generated by std::function parameters
171 if ("(anonymous)::operator " in method) and ("(*)" in method):
172 continue
173 # stuff generated by Qt
174 if "::tr(" in method or "::trUtf8(" in method:
175 continue
177 location = definitionToSourceLocationMap[d]
178 # whacky template stuff
179 if location.startswith("sc/source/ui/vba/vbaformat.hxx"):
180 continue
181 # not sure how this stuff is called
182 if location.startswith("include/test"):
183 continue
184 # leave the debug/dump alone
185 if location.startswith("include/oox/dump"):
186 continue
187 # plugin testing stuff
188 if location.startswith("compilerplugins/clang/test"):
189 continue
190 # leave this alone for now
191 if location.startswith("include/LibreOfficeKit"):
192 continue
193 # template stuff
194 if location.startswith("include/vcl/vclptr.hxx"):
195 continue
196 if location.startswith("include/oox/helper/refvector.hxx"):
197 continue
198 if location.startswith("include/oox/drawingml/chart/modelbase.hxx"):
199 continue
201 unusedSet.add(d) # used by the "unused return types" analysis
202 tmp1set.add((method, location))
204 # print out the results, sorted by name and line number
205 with open("compilerplugins/clang/unusedmethods.results", "wt") as f:
206 for t in sort_set_by_natural_key(tmp1set):
207 f.write(t[1] + "\n")
208 f.write(" " + t[0] + "\n")
210 # --------------------------------------------------------------------------------------------
211 # "unused return types" analysis
212 # --------------------------------------------------------------------------------------------
214 tmp2set = set()
215 for d in definitionSet:
216 method = d[0] + " " + d[1]
217 if d in usedReturnSet:
218 continue
219 if d in unusedSet:
220 continue
221 if isOtherConstness(d, usedReturnSet):
222 continue
223 # ignore methods with no return type, and constructors
224 if d[0] == "void" or d[0] == "":
225 continue
226 # ignore UNO constructor method entrypoints
227 if "_get_implementation" in d[1] or "_getFactory" in d[1]:
228 continue
229 # the plugin can't see calls to these
230 if "::operator new" in d[1]:
231 continue
232 # unused return type is not a problem here
233 if ("operator=(" in d[1] or "operator&=" in d[1] or "operator|=" in d[1] or "operator^=" in d[1]
234 or "operator+=" in d[1] or "operator-=" in d[1]
235 or "operator<<" in d[1] or "operator>>" in d[1]
236 or "operator++" in d[1] or "operator--" in d[1]):
237 continue
238 # ignore UNO constructor functions
239 if (d[0] == "class com::sun::star::uno::Reference<class com::sun::star::uno::XInterface>" and
240 d[1].endswith("_createInstance(const class com::sun::star::uno::Reference<class com::sun::star::lang::XMultiServiceFactory> &)")):
241 continue
242 if (d[0] == "class com::sun::star::uno::Reference<class com::sun::star::uno::XInterface>" and
243 d[1].endswith("_CreateInstance(const class com::sun::star::uno::Reference<class com::sun::star::lang::XMultiServiceFactory> &)")):
244 continue
245 # debug code
246 if d[1] == "writerfilter::ooxml::OOXMLPropertySet::toString()":
247 continue
248 # ignore lambdas
249 if "::__invoke(" in d[1]:
250 continue
251 if "(lambda at " in d[1]:
252 continue
253 if "::operator " in d[1] and "(*)(" in d[1]:
254 continue
255 location = definitionToSourceLocationMap[d]
256 # windows only
257 if location.startswith("include/svl/svdde.hxx"):
258 continue
259 # fluent API (return ref to self)
260 if location.startswith("include/tools/stream.hxx"):
261 continue
262 if location.startswith("include/oox/helper/refvector.hxx"):
263 continue
264 if location.startswith("include/oox/drawingml/chart/modelbase.hxx"):
265 continue
266 # templates
267 if location.startswith("include/vcl/vclptr.hxx"):
268 continue
269 # external API
270 if location.startswith("include/LibreOfficeKit/LibreOfficeKit.hxx"):
271 continue
272 tmp2set.add((method, location))
274 #Disable this for now, not really using it
275 # print output, sorted by name and line number
276 with open("compilerplugins/clang/unusedmethods.unused-returns.results", "wt") as f:
277 for t in sort_set_by_natural_key(tmp2set):
278 f.write(t[1] + "\n")
279 f.write(" " + t[0] + "\n")
282 # --------------------------------------------------------------------------------------------
283 # "method can be private" analysis
284 # --------------------------------------------------------------------------------------------
286 tmp3set = set()
287 for d in publicDefinitionSet:
288 method = d[0] + " " + d[1]
289 if d in calledFromOutsideSet:
290 continue
291 if d in virtualSet:
292 continue
293 # TODO ignore constructors for now, my called-from-outside analysis doesn't work here
294 if d[0] == "":
295 continue
296 if isOtherConstness(d, calledFromOutsideSet):
297 continue
298 tmp3set.add((method, definitionToSourceLocationMap[d]))
300 # print output, sorted by name and line number
301 with open("loplugin.unusedmethods.report-can-be-private", "wt") as f:
302 for t in sort_set_by_natural_key(tmp3set):
303 f.write(t[1] + "\n")
304 f.write(" " + t[0] + "\n")
308 # --------------------------------------------------------------------------------------------
309 # "all protected methods in class can be made private" analysis
310 # --------------------------------------------------------------------------------------------
312 potentialClasses = set()
313 excludedClasses = set()
314 potentialClassesSourceLocationMap = dict()
315 matchClassName = re.compile(r"(\w+)::")
316 for d in protectedDefinitionSet:
317 m = matchClassName.match(d[1])
318 if not m:
319 continue
320 clazz = m.group(1)
321 if d in calledFromOutsideSet:
322 excludedClasses.add(clazz)
323 else:
324 potentialClasses.add(clazz)
325 potentialClassesSourceLocationMap[clazz] = definitionToSourceLocationMap[d]
327 tmp4set = set()
328 for d in (potentialClasses - excludedClasses):
329 tmp4set.add((d, potentialClassesSourceLocationMap[d]))
331 # print output, sorted by name and line number
332 with open("loplugin.unusedmethods.report-all-protected-can-be-private", "wt") as f:
333 for t in sort_set_by_natural_key(tmp4set):
334 f.write(t[1] + "\n")
335 f.write(" " + t[0] + "\n")