1#!/usr/local/bin/python3.8
2
3import sys
4import re
5import io
6
7definitionSet = set()
8protectedAndPublicDefinitionSet = set() # set of tuple(type, name)
9definitionToSourceLocationMap = dict()
10definitionToTypeMap = dict()
11touchedFromInsideSet = set()
12touchedFromOutsideSet = set()
13touchedFromOutsideConstructorSet = set()
14readFromSet = set()
15writeToSet = set()
16sourceLocationSet = set()
17
18# clang does not always use exactly the same numbers in the type-parameter vars it generates
19# so I need to substitute them to ensure we can match correctly.
20normalizeTypeParamsRegex = re.compile(r"type-parameter-\d+-\d+")
21def normalizeTypeParams( line ):
22    return normalizeTypeParamsRegex.sub("type-parameter-?-?", line)
23
24def parseFieldInfo( tokens ):
25    if len(tokens) == 3:
26        return (normalizeTypeParams(tokens[1]), tokens[2])
27    else:
28        return (normalizeTypeParams(tokens[1]), "")
29
30with io.open("workdir/loplugin.unusedfields.log", "rb", buffering=1024*1024) as txt:
31    for line in txt:
32        tokens = line.strip().split("\t")
33        if tokens[0] == "definition:":
34            access = tokens[1]
35            fieldInfo = (normalizeTypeParams(tokens[2]), tokens[3])
36            srcLoc = tokens[5]
37            # ignore external source code
38            if (srcLoc.startswith("external/")):
39                continue
40            # ignore build folder
41            if (srcLoc.startswith("workdir/")):
42                continue
43            definitionSet.add(fieldInfo)
44            definitionToTypeMap[fieldInfo] = tokens[4]
45            if access == "protected" or access == "public":
46                protectedAndPublicDefinitionSet.add(fieldInfo)
47            definitionToSourceLocationMap[fieldInfo] = tokens[5]
48        elif tokens[0] == "inside:":
49            touchedFromInsideSet.add(parseFieldInfo(tokens))
50        elif tokens[0] == "outside:":
51            touchedFromOutsideSet.add(parseFieldInfo(tokens))
52        elif tokens[0] == "outside-constructor:":
53            touchedFromOutsideConstructorSet.add(parseFieldInfo(tokens))
54        elif tokens[0] == "read:":
55            readFromSet.add(parseFieldInfo(tokens))
56        elif tokens[0] == "write:":
57            writeToSet.add(parseFieldInfo(tokens))
58        else:
59            print( "unknown line: " + line)
60
61# Calculate untouched
62untouchedSet = set()
63untouchedSetD = set()
64for d in definitionSet:
65    if d in touchedFromOutsideSet or d in touchedFromInsideSet:
66        continue
67    srcLoc = definitionToSourceLocationMap[d];
68    # this is all representations of on-disk data structures
69    if (srcLoc.startswith("sc/source/filter/inc/scflt.hxx")
70        or srcLoc.startswith("sw/source/filter/ww8/")
71        or srcLoc.startswith("vcl/source/filter/sgvmain.hxx")
72        or srcLoc.startswith("vcl/source/filter/sgfbram.hxx")
73        or srcLoc.startswith("vcl/inc/unx/XIM.h")
74        or srcLoc.startswith("vcl/inc/unx/gtk/gloactiongroup.h")
75        or srcLoc.startswith("include/svl/svdde.hxx")
76        or srcLoc.startswith("lotuswordpro/source/filter/lwpsdwdrawheader.hxx")
77        or srcLoc.startswith("hwpfilter/")
78        or srcLoc.startswith("embeddedobj/source/inc/")
79        or srcLoc.startswith("svtools/source/dialogs/insdlg.cxx")
80        or srcLoc.startswith("bridges/")):
81        continue
82    if d[0] in set([ "AtkObjectWrapperClass", "AtkObjectWrapper", "GLOMenu", "GLOAction", "_XRegion", "SalMenuButtonItem", "Vertex",
83            "OOoMountOperationClass", "SwCSS1ItemIds", "ScCompiler::AddInMap", "MemoryByteGrabber", "textcat_t", "fp_t", "ngram_t",
84            "ImplPPTParaPropSet", "DataNode"]):
85        continue
86    # unit testing code
87    if srcLoc.startswith("cppu/source/uno/check.cxx"):
88        continue
89    fieldType = definitionToTypeMap[d]
90    if "ModuleClient" in fieldType:
91        continue
92    # leave the weld stuff alone until Caolan is finished
93    if "weld::" in fieldType:
94        continue
95    if "::sfx2::sidebar::ControllerItem" in fieldType:
96        continue
97    untouchedSet.add((d[0] + " " + d[1] + " " + fieldType, srcLoc))
98    untouchedSetD.add(d)
99
100# Calculate only-touched-in-constructor set
101onlyUsedInConstructorSet = set()
102for d in definitionSet:
103    if d in touchedFromOutsideSet or d in touchedFromOutsideConstructorSet:
104        continue
105    srcLoc = definitionToSourceLocationMap[d];
106    # this is all representations of on-disk data structures
107    if (srcLoc.startswith("sc/source/filter/inc/scflt.hxx")
108        or srcLoc.startswith("sw/source/filter/ww8/")
109        or srcLoc.startswith("vcl/source/filter/sgvmain.hxx")
110        or srcLoc.startswith("vcl/source/filter/sgfbram.hxx")
111        or srcLoc.startswith("vcl/inc/unx/XIM.h")
112        or srcLoc.startswith("vcl/inc/unx/gtk/gloactiongroup.h")
113        or srcLoc.startswith("include/svl/svdde.hxx")
114        or srcLoc.startswith("lotuswordpro/source/filter/lwpsdwdrawheader.hxx")
115        or srcLoc.startswith("hwpfilter/")
116        or srcLoc.startswith("embeddedobj/source/inc/")
117        or srcLoc.startswith("svtools/source/dialogs/insdlg.cxx")
118        or srcLoc.startswith("bridges/")):
119        continue
120    fieldType = definitionToTypeMap[d]
121    if "std::unique_ptr" in fieldType:
122        continue
123    if "std::shared_ptr" in fieldType:
124        continue
125    if "Reference<" in fieldType:
126        continue
127    if "VclPtr<" in fieldType:
128        continue
129    # leave the weld stuff alone until Caolan is finished
130    if "weld::" in fieldType:
131        continue
132    if "osl::Mutex" in fieldType:
133        continue
134    if "::sfx2::sidebar::ControllerItem" in fieldType:
135        continue
136    onlyUsedInConstructorSet.add((d[0] + " " + d[1] + " " + fieldType, srcLoc))
137
138writeonlySet = set()
139for d in definitionSet:
140    parentClazz = d[0];
141    if d in readFromSet or d in untouchedSetD:
142        continue
143    srcLoc = definitionToSourceLocationMap[d];
144    # this is all representations of on-disk data structures
145    if (srcLoc.startswith("sc/source/filter/inc/scflt.hxx")
146        or srcLoc.startswith("sw/source/filter/ww8/")
147        or srcLoc.startswith("vcl/source/filter/sgvmain.hxx")
148        or srcLoc.startswith("vcl/source/filter/sgfbram.hxx")
149        or srcLoc.startswith("vcl/inc/unx/XIM.h")
150        or srcLoc.startswith("vcl/inc/unx/gtk/gloactiongroup.h")
151        or srcLoc.startswith("include/svl/svdde.hxx")
152        or srcLoc.startswith("lotuswordpro/source/filter/lwpsdwdrawheader.hxx")
153        or srcLoc.startswith("svtools/source/dialogs/insdlg.cxx")):
154        continue
155    fieldType = definitionToTypeMap[d]
156    if "ModuleClient" in fieldType:
157        continue
158    if "::sfx2::sidebar::ControllerItem" in fieldType:
159        continue
160    # ignore reference fields, because writing to them actually writes to another field somewhere else
161    if fieldType.endswith("&"):
162        continue
163    # ignore the import/export data model stuff
164    if srcLoc.startswith("sc/source/filter/inc/") and "Model" in fieldType:
165        continue
166    if srcLoc.startswith("sc/source/filter/inc/") and (parentClazz.startswith("Xcl") or parentClazz.startswith("oox::xls::")):
167        continue
168    # implement some kind of registration of errors
169    if fieldType == "class SfxErrorHandler *":
170        continue
171    # mutex locking
172    if "Guard" in fieldType:
173        continue
174    # leave the weld stuff alone until Caolan is finished
175    if "weld::" in fieldType:
176        continue
177    # these are just all model classes
178    if (srcLoc.startswith("oox/")
179        or srcLoc.startswith("lotuswordpro/")
180        or srcLoc.startswith("include/oox/")
181        or srcLoc.startswith("include/filter/")
182        or srcLoc.startswith("hwpfilter/")
183        or srcLoc.startswith("filter/")):
184        continue
185
186    writeonlySet.add((d[0] + " " + d[1] + " " + definitionToTypeMap[d], srcLoc))
187
188
189readonlySet = set()
190for d in definitionSet:
191    parentClazz = d[0];
192    if d in writeToSet or d in untouchedSetD:
193        continue
194    fieldType = definitionToTypeMap[d]
195    srcLoc = definitionToSourceLocationMap[d];
196    if "ModuleClient" in fieldType:
197        continue
198    # this is all representations of on-disk data structures
199    if (srcLoc.startswith("sc/source/filter/inc/scflt.hxx")
200        or srcLoc.startswith("sw/source/filter/ww8/")
201        or srcLoc.startswith("vcl/source/filter/sgvmain.hxx")
202        or srcLoc.startswith("vcl/source/filter/sgfbram.hxx")
203        or srcLoc.startswith("vcl/inc/unx/XIM.h")
204        or srcLoc.startswith("vcl/inc/unx/gtk/gloactiongroup.h")
205        or srcLoc.startswith("include/svl/svdde.hxx")):
206        continue
207    # I really don't care about these ancient file formats
208    if (srcLoc.startswith("hwpfilter/")
209        or srcLoc.startswith("lotuswordpro/")):
210        continue
211    # leave the weld stuff alone until Caolan is finished
212    if "weld::" in fieldType:
213        continue
214    readonlySet.add((d[0] + " " + d[1] + " " + definitionToTypeMap[d], srcLoc))
215
216
217canBePrivateSet = set()
218for d in protectedAndPublicDefinitionSet:
219    clazz = d[0] + " " + d[1]
220    if d in touchedFromOutsideSet:
221        continue
222    srcLoc = definitionToSourceLocationMap[d];
223
224    canBePrivateSet.add((clazz + " " + definitionToTypeMap[d], srcLoc))
225
226
227# --------------------------------------------------------------------------------------------
228# "all fields in class can be made private" analysis
229# --------------------------------------------------------------------------------------------
230
231potentialClasses = set()
232excludedClasses = set()
233potentialClassesSourceLocationMap = dict()
234matchClassName = re.compile(r"(\w+)::")
235for d in protectedAndPublicDefinitionSet:
236    clazz = d[0]
237    if d in touchedFromOutsideSet:
238        excludedClasses.add(clazz)
239    else:
240        potentialClasses.add(clazz)
241        potentialClassesSourceLocationMap[clazz] = definitionToSourceLocationMap[d]
242allFieldsCanBePrivateSet = set()
243for d in (potentialClasses - excludedClasses):
244    sourceLoc = potentialClassesSourceLocationMap[d]
245    # when the class is inside a compile unit, assume that the compiler can figure this out for itself, much less interesting to me
246    if not ".cxx" in sourceLoc:
247        allFieldsCanBePrivateSet.add((d, sourceLoc))
248
249# sort the results using a "natural order" so sequences like [item1,item2,item10] sort nicely
250def natural_sort_key(s, _nsre=re.compile('([0-9]+)')):
251    return [int(text) if text.isdigit() else text.lower()
252            for text in re.split(_nsre, s)]
253
254# sort results by name and line number
255tmp1list = sorted(untouchedSet, key=lambda v: natural_sort_key(v[1]))
256tmp2list = sorted(writeonlySet, key=lambda v: natural_sort_key(v[1]))
257tmp3list = sorted(canBePrivateSet, key=lambda v: natural_sort_key(v[1]))
258tmp4list = sorted(readonlySet, key=lambda v: natural_sort_key(v[1]))
259tmp5list = sorted(onlyUsedInConstructorSet, key=lambda v: natural_sort_key(v[1]))
260tmp6list = sorted(allFieldsCanBePrivateSet, key=lambda v: natural_sort_key(v[1]))
261
262# print out the results
263with open("compilerplugins/clang/unusedfields.untouched.results", "wt") as f:
264    for t in tmp1list:
265        f.write( t[1] + "\n" )
266        f.write( "    " + t[0] + "\n" )
267with open("compilerplugins/clang/unusedfields.writeonly.results", "wt") as f:
268    for t in tmp2list:
269        f.write( t[1] + "\n" )
270        f.write( "    " + t[0] + "\n" )
271# this one is not checked in yet because I haven't actually done anything with it
272with open("loplugin.unusedfields.report-can-be-private", "wt") as f:
273    for t in tmp3list:
274        f.write( t[1] + "\n" )
275        f.write( "    " + t[0] + "\n" )
276with open("compilerplugins/clang/unusedfields.readonly.results", "wt") as f:
277    for t in tmp4list:
278        f.write( t[1] + "\n" )
279        f.write( "    " + t[0] + "\n" )
280with open("compilerplugins/clang/unusedfields.only-used-in-constructor.results", "wt") as f:
281    for t in tmp5list:
282        f.write( t[1] + "\n" )
283        f.write( "    " + t[0] + "\n" )
284with open("compilerplugins/clang/unusedfields.report-all-can-be-private", "wt") as f:
285    for t in tmp6list:
286        f.write( t[1] + "\n" )
287        f.write( "    " + t[0] + "\n" )
288
289
290