xref: /llvm-project/lldb/examples/summaries/cocoa/NSSet.py (revision 2238dcc39358353cac21df75c3c3286ab20b8f53)
1"""
2LLDB AppKit formatters
3
4Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
5See https://llvm.org/LICENSE.txt for license information.
6SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
7"""
8# summary provider for NSSet
9import lldb
10import ctypes
11import lldb.runtime.objc.objc_runtime
12import lldb.formatters.metrics
13import CFBag
14import lldb.formatters.Logger
15
16statistics = lldb.formatters.metrics.Metrics()
17statistics.add_metric("invalid_isa")
18statistics.add_metric("invalid_pointer")
19statistics.add_metric("unknown_class")
20statistics.add_metric("code_notrun")
21
22# despite the similary to synthetic children providers, these classes are not
23# trying to provide anything but the port number of an NSMachPort, so they need not
24# obey the interface specification for synthetic children providers
25
26
27class NSCFSet_SummaryProvider:
28    def adjust_for_architecture(self):
29        pass
30
31    def __init__(self, valobj, params):
32        logger = lldb.formatters.Logger.Logger()
33        self.valobj = valobj
34        self.sys_params = params
35        if not (self.sys_params.types_cache.NSUInteger):
36            if self.sys_params.is_64_bit:
37                self.sys_params.types_cache.NSUInteger = (
38                    self.valobj.GetType().GetBasicType(lldb.eBasicTypeUnsignedLong)
39                )
40            else:
41                self.sys_params.types_cache.NSUInteger = (
42                    self.valobj.GetType().GetBasicType(lldb.eBasicTypeUnsignedInt)
43                )
44        self.update()
45
46    def update(self):
47        logger = lldb.formatters.Logger.Logger()
48        self.adjust_for_architecture()
49
50    # one pointer is the ISA
51    # then we have one other internal pointer, plus
52    # 4 bytes worth of flags. hence, these values
53    def offset(self):
54        logger = lldb.formatters.Logger.Logger()
55        if self.sys_params.is_64_bit:
56            return 20
57        else:
58            return 12
59
60    def count(self):
61        logger = lldb.formatters.Logger.Logger()
62        vcount = self.valobj.CreateChildAtOffset(
63            "count", self.offset(), self.sys_params.types_cache.NSUInteger
64        )
65        return vcount.GetValueAsUnsigned(0)
66
67
68class NSSetUnknown_SummaryProvider:
69    def adjust_for_architecture(self):
70        pass
71
72    def __init__(self, valobj, params):
73        logger = lldb.formatters.Logger.Logger()
74        self.valobj = valobj
75        self.sys_params = params
76        self.update()
77
78    def update(self):
79        logger = lldb.formatters.Logger.Logger()
80        self.adjust_for_architecture()
81
82    def count(self):
83        logger = lldb.formatters.Logger.Logger()
84        stream = lldb.SBStream()
85        self.valobj.GetExpressionPath(stream)
86        expr = "(int)[" + stream.GetData() + " count]"
87        num_children_vo = self.valobj.CreateValueFromExpression("count", expr)
88        if num_children_vo.IsValid():
89            return num_children_vo.GetValueAsUnsigned(0)
90        return "<variable is not NSSet>"
91
92
93class NSSetI_SummaryProvider:
94    def adjust_for_architecture(self):
95        pass
96
97    def __init__(self, valobj, params):
98        logger = lldb.formatters.Logger.Logger()
99        self.valobj = valobj
100        self.sys_params = params
101        if not (self.sys_params.types_cache.NSUInteger):
102            if self.sys_params.is_64_bit:
103                self.sys_params.types_cache.NSUInteger = (
104                    self.valobj.GetType().GetBasicType(lldb.eBasicTypeUnsignedLong)
105                )
106            else:
107                self.sys_params.types_cache.NSUInteger = (
108                    self.valobj.GetType().GetBasicType(lldb.eBasicTypeUnsignedInt)
109                )
110        self.update()
111
112    def update(self):
113        logger = lldb.formatters.Logger.Logger()
114        self.adjust_for_architecture()
115
116    # we just need to skip the ISA and the count immediately follows
117    def offset(self):
118        logger = lldb.formatters.Logger.Logger()
119        return self.sys_params.pointer_size
120
121    def count(self):
122        logger = lldb.formatters.Logger.Logger()
123        num_children_vo = self.valobj.CreateChildAtOffset(
124            "count", self.offset(), self.sys_params.types_cache.NSUInteger
125        )
126        value = num_children_vo.GetValueAsUnsigned(0)
127        if value is not None:
128            # the MSB on immutable sets seems to be taken by some other data
129            # not sure if it is a bug or some weird sort of feature, but masking it out
130            # gets the count right (unless, of course, someone's dictionaries grow
131            #                       too large - but I have not tested this)
132            if self.sys_params.is_64_bit:
133                value = value & ~0xFF00000000000000
134            else:
135                value = value & ~0xFF000000
136        return value
137
138
139class NSSetM_SummaryProvider:
140    def adjust_for_architecture(self):
141        pass
142
143    def __init__(self, valobj, params):
144        logger = lldb.formatters.Logger.Logger()
145        self.valobj = valobj
146        self.sys_params = params
147        if not (self.sys_params.types_cache.NSUInteger):
148            if self.sys_params.is_64_bit:
149                self.sys_params.types_cache.NSUInteger = (
150                    self.valobj.GetType().GetBasicType(lldb.eBasicTypeUnsignedLong)
151                )
152            else:
153                self.sys_params.types_cache.NSUInteger = (
154                    self.valobj.GetType().GetBasicType(lldb.eBasicTypeUnsignedInt)
155                )
156        self.update()
157
158    def update(self):
159        logger = lldb.formatters.Logger.Logger()
160        self.adjust_for_architecture()
161
162    # we just need to skip the ISA and the count immediately follows
163    def offset(self):
164        logger = lldb.formatters.Logger.Logger()
165        return self.sys_params.pointer_size
166
167    def count(self):
168        logger = lldb.formatters.Logger.Logger()
169        num_children_vo = self.valobj.CreateChildAtOffset(
170            "count", self.offset(), self.sys_params.types_cache.NSUInteger
171        )
172        return num_children_vo.GetValueAsUnsigned(0)
173
174
175class NSCountedSet_SummaryProvider:
176    def adjust_for_architecture(self):
177        pass
178
179    def __init__(self, valobj, params):
180        logger = lldb.formatters.Logger.Logger()
181        self.valobj = valobj
182        self.sys_params = params
183        if not (self.sys_params.types_cache.voidptr):
184            self.sys_params.types_cache.voidptr = (
185                self.valobj.GetType().GetBasicType(lldb.eBasicTypeVoid).GetPointerType()
186            )
187        self.update()
188
189    def update(self):
190        logger = lldb.formatters.Logger.Logger()
191        self.adjust_for_architecture()
192
193    # an NSCountedSet is implemented using a CFBag whose pointer just follows
194    # the ISA
195    def offset(self):
196        logger = lldb.formatters.Logger.Logger()
197        return self.sys_params.pointer_size
198
199    def count(self):
200        logger = lldb.formatters.Logger.Logger()
201        cfbag_vo = self.valobj.CreateChildAtOffset(
202            "bag_impl", self.offset(), self.sys_params.types_cache.voidptr
203        )
204        return CFBag.CFBagRef_SummaryProvider(cfbag_vo, self.sys_params).length()
205
206
207def GetSummary_Impl(valobj):
208    logger = lldb.formatters.Logger.Logger()
209    global statistics
210    (
211        class_data,
212        wrapper,
213    ) = lldb.runtime.objc.objc_runtime.Utilities.prepare_class_detection(
214        valobj, statistics
215    )
216    if wrapper:
217        return wrapper
218
219    name_string = class_data.class_name()
220    logger >> "class name is: " + str(name_string)
221
222    if name_string == "__NSCFSet":
223        wrapper = NSCFSet_SummaryProvider(valobj, class_data.sys_params)
224        statistics.metric_hit("code_notrun", valobj)
225    elif name_string == "__NSSetI":
226        wrapper = NSSetI_SummaryProvider(valobj, class_data.sys_params)
227        statistics.metric_hit("code_notrun", valobj)
228    elif name_string == "__NSSetM":
229        wrapper = NSSetM_SummaryProvider(valobj, class_data.sys_params)
230        statistics.metric_hit("code_notrun", valobj)
231    elif name_string == "NSCountedSet":
232        wrapper = NSCountedSet_SummaryProvider(valobj, class_data.sys_params)
233        statistics.metric_hit("code_notrun", valobj)
234    else:
235        wrapper = NSSetUnknown_SummaryProvider(valobj, class_data.sys_params)
236        statistics.metric_hit(
237            "unknown_class", valobj.GetName() + " seen as " + name_string
238        )
239    return wrapper
240
241
242def NSSet_SummaryProvider(valobj, dict):
243    logger = lldb.formatters.Logger.Logger()
244    provider = GetSummary_Impl(valobj)
245    if provider is not None:
246        try:
247            summary = provider.count()
248        except:
249            summary = None
250        if summary is None:
251            summary = "<variable is not NSSet>"
252        if isinstance(summary, str):
253            return summary
254        else:
255            summary = str(summary) + (" objects" if summary != 1 else " object")
256        return summary
257    return "Summary Unavailable"
258
259
260def NSSet_SummaryProvider2(valobj, dict):
261    logger = lldb.formatters.Logger.Logger()
262    provider = GetSummary_Impl(valobj)
263    if provider is not None:
264        if isinstance(
265            provider, lldb.runtime.objc.objc_runtime.SpecialSituation_Description
266        ):
267            return provider.message()
268        try:
269            summary = provider.count()
270        except:
271            summary = None
272        logger >> "got summary " + str(summary)
273        # for some reason, one needs to clear some bits for the count returned
274        # to be correct when using directly CF*SetRef as compared to NS*Set
275        # this only happens on 64bit, and the bit mask was derived through
276        # experimentation (if counts start looking weird, then most probably
277        #                  the mask needs to be changed)
278        if summary is None:
279            summary = "<variable is not CFSet>"
280        if isinstance(summary, str):
281            return summary
282        else:
283            if provider.sys_params.is_64_bit:
284                summary = summary & ~0x1FFF000000000000
285            summary = '@"' + str(summary) + (' values"' if summary != 1 else ' value"')
286        return summary
287    return "Summary Unavailable"
288
289
290def __lldb_init_module(debugger, dict):
291    debugger.HandleCommand("type summary add -F NSSet.NSSet_SummaryProvider NSSet")
292    debugger.HandleCommand(
293        "type summary add -F NSSet.NSSet_SummaryProvider2 CFSetRef CFMutableSetRef"
294    )
295