7 # --------------------------------------------------------------------------------------------
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 # --------------------------------------------------------------------------------------------
34 # --------------------------------------------------------------------------------------------
36 with io
.open("workdir/loplugin.unusedmethods.log", "rb", buffering
=1024*1024) as txt
:
38 tokens
= line
.strip().split("\t")
39 if tokens
[0] == "definition:":
41 returnType
= tokens
[2]
42 nameAndParams
= tokens
[3]
43 sourceLocation
= tokens
[4]
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
)))
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:
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
):
88 elif method
.endswith(" const"):
89 method2
= method
[:len(method
)-6] # strip off " const"
90 if ((d
[0],method2
) in callSet
):
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
):
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
):
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
):
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]
124 if isOtherConstness(d
, callSet
):
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]:
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]:
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":
136 # used by Windows build
137 if any(x
in d
[1] for x
in ["DdeTopic::", "DdeData::", "DdeService::", "DdeTransaction::", "DdeConnection::", "DdeLink::", "DdeItem::", "DdeGetPutItem::"]):
139 if method
== "class tools::SvRef<class FontCharMap> FontCharMap::GetDefaultMap(_Bool)":
141 # these are loaded by dlopen() from somewhere
142 if "get_implementation" in d
[1]:
144 if "component_getFactory" in d
[1]:
146 if d
[0]=="_Bool" and "_supportsService(const class rtl::OUString &)" in d
[1]:
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]):
151 # ignore the Java symbols, loaded from the JavaVM
152 if d
[1].startswith("Java_"):
154 # ignore external code
155 if definitionToSourceLocationMap
[d
].startswith("external/"):
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]):
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> &"):
163 if d
[0] == "basic_ostream<type-parameter-?-?, type-parameter-?-?> &" and d
[1].startswith("operator<<(basic_ostream<type-parameter-?-?"):
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
):
185 f
.write(" " + t
[0] + "\n")
187 # --------------------------------------------------------------------------------------------
188 # "unused return types" analysis
189 # --------------------------------------------------------------------------------------------
192 for d
in definitionSet
:
193 method
= d
[0] + " " + d
[1]
194 if d
in usedReturnSet
:
198 if isOtherConstness(d
, usedReturnSet
):
200 # ignore methods with no return type, and constructors
201 if d
[0] == "void" or d
[0] == "":
203 # ignore UNO constructor method entrypoints
204 if "_get_implementation" in d
[1] or "_getFactory" in d
[1]:
206 # the plugin can't see calls to these
207 if "::operator new" in d
[1]:
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]):
215 # ignore external code
216 if definitionToSourceLocationMap
[d
].startswith("external/"):
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> &)")):
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> &)")):
226 if d
[1] == "writerfilter::ooxml::OOXMLPropertySet::toString()":
228 location
= definitionToSourceLocationMap
[d
];
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
):
239 f
.write(" " + t
[0] + "\n")
242 # --------------------------------------------------------------------------------------------
243 # "method can be private" analysis
244 # --------------------------------------------------------------------------------------------
247 for d
in publicDefinitionSet
:
248 method
= d
[0] + " " + d
[1]
249 if d
in calledFromOutsideSet
:
253 # TODO ignore constructors for now, my called-from-outside analysis doesn't work here
256 if isOtherConstness(d
, calledFromOutsideSet
):
258 # ignore external code
259 if definitionToSourceLocationMap
[d
].startswith("external/"):
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
):
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])
283 if d
in calledFromOutsideSet
:
284 excludedClasses
.add(clazz
)
286 potentialClasses
.add(clazz
)
287 potentialClassesSourceLocationMap
[clazz
] = definitionToSourceLocationMap
[d
]
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
):
297 f
.write(" " + t
[0] + "\n")