xref: /llvm-project/lldb/examples/summaries/cocoa/CFDictionary.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# example summary provider for NSDictionary
9# the real summary is now C++ code built into LLDB
10import lldb
11import ctypes
12import lldb.runtime.objc.objc_runtime
13import lldb.formatters.metrics
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 count for an NSDictionary, so they need not
24# obey the interface specification for synthetic children providers
25
26
27class NSCFDictionary_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    # empirically determined on both 32 and 64bit desktop Mac OS X
51    # probably boils down to 2 pointers and 4 bytes of data, but
52    # the description of __CFDictionary is not readily available so most
53    # of this is guesswork, plain and simple
54    def offset(self):
55        logger = lldb.formatters.Logger.Logger()
56        if self.sys_params.is_64_bit:
57            return 20
58        else:
59            return 12
60
61    def num_children(self):
62        logger = lldb.formatters.Logger.Logger()
63        num_children_vo = self.valobj.CreateChildAtOffset(
64            "count", self.offset(), self.sys_params.types_cache.NSUInteger
65        )
66        return num_children_vo.GetValueAsUnsigned(0)
67
68
69class NSDictionaryI_SummaryProvider:
70    def adjust_for_architecture(self):
71        pass
72
73    def __init__(self, valobj, params):
74        logger = lldb.formatters.Logger.Logger()
75        self.valobj = valobj
76        self.sys_params = params
77        if not (self.sys_params.types_cache.NSUInteger):
78            if self.sys_params.is_64_bit:
79                self.sys_params.types_cache.NSUInteger = (
80                    self.valobj.GetType().GetBasicType(lldb.eBasicTypeUnsignedLong)
81                )
82            else:
83                self.sys_params.types_cache.NSUInteger = (
84                    self.valobj.GetType().GetBasicType(lldb.eBasicTypeUnsignedInt)
85                )
86        self.update()
87
88    def update(self):
89        logger = lldb.formatters.Logger.Logger()
90        self.adjust_for_architecture()
91
92    # we just need to skip the ISA and the count immediately follows
93    def offset(self):
94        logger = lldb.formatters.Logger.Logger()
95        return self.sys_params.pointer_size
96
97    def num_children(self):
98        logger = lldb.formatters.Logger.Logger()
99        num_children_vo = self.valobj.CreateChildAtOffset(
100            "count", self.offset(), self.sys_params.types_cache.NSUInteger
101        )
102        value = num_children_vo.GetValueAsUnsigned(0)
103        if value is not None:
104            # the MS6bits on immutable dictionaries seem to be taken by the LSB of capacity
105            # not sure if it is a bug or some weird sort of feature, but masking that out
106            # gets the count right
107            if self.sys_params.is_64_bit:
108                value = value & ~0xFC00000000000000
109            else:
110                value = value & ~0xFC000000
111        return value
112
113
114class NSDictionaryM_SummaryProvider:
115    def adjust_for_architecture(self):
116        pass
117
118    def __init__(self, valobj, params):
119        logger = lldb.formatters.Logger.Logger()
120        self.valobj = valobj
121        self.sys_params = params
122        if not (self.sys_params.types_cache.NSUInteger):
123            if self.sys_params.is_64_bit:
124                self.sys_params.types_cache.NSUInteger = (
125                    self.valobj.GetType().GetBasicType(lldb.eBasicTypeUnsignedLong)
126                )
127            else:
128                self.sys_params.types_cache.NSUInteger = (
129                    self.valobj.GetType().GetBasicType(lldb.eBasicTypeUnsignedInt)
130                )
131        self.update()
132
133    def update(self):
134        logger = lldb.formatters.Logger.Logger()
135        self.adjust_for_architecture()
136
137    # we just need to skip the ISA and the count immediately follows
138    def offset(self):
139        return self.sys_params.pointer_size
140
141    def num_children(self):
142        logger = lldb.formatters.Logger.Logger()
143        num_children_vo = self.valobj.CreateChildAtOffset(
144            "count", self.offset(), self.sys_params.types_cache.NSUInteger
145        )
146        value = num_children_vo.GetValueAsUnsigned(0)
147        if value is not None:
148            # the MS6bits on immutable dictionaries seem to be taken by the LSB of capacity
149            # not sure if it is a bug or some weird sort of feature, but masking that out
150            # gets the count right
151            if self.sys_params.is_64_bit:
152                value = value & ~0xFC00000000000000
153            else:
154                value = value & ~0xFC000000
155        return value
156
157
158class NSDictionaryUnknown_SummaryProvider:
159    def adjust_for_architecture(self):
160        pass
161
162    def __init__(self, valobj, params):
163        logger = lldb.formatters.Logger.Logger()
164        self.valobj = valobj
165        self.sys_params = params
166        self.update()
167
168    def update(self):
169        logger = lldb.formatters.Logger.Logger()
170        self.adjust_for_architecture()
171
172    def num_children(self):
173        logger = lldb.formatters.Logger.Logger()
174        stream = lldb.SBStream()
175        self.valobj.GetExpressionPath(stream)
176        num_children_vo = self.valobj.CreateValueFromExpression(
177            "count", "(int)[" + stream.GetData() + " count]"
178        )
179        if num_children_vo.IsValid():
180            return num_children_vo.GetValueAsUnsigned(0)
181        return "<variable is not NSDictionary>"
182
183
184def GetSummary_Impl(valobj):
185    logger = lldb.formatters.Logger.Logger()
186    global statistics
187    (
188        class_data,
189        wrapper,
190    ) = lldb.runtime.objc.objc_runtime.Utilities.prepare_class_detection(
191        valobj, statistics
192    )
193    if wrapper:
194        return wrapper
195
196    name_string = class_data.class_name()
197
198    logger >> "class name is: " + str(name_string)
199
200    if name_string == "__NSCFDictionary":
201        wrapper = NSCFDictionary_SummaryProvider(valobj, class_data.sys_params)
202        statistics.metric_hit("code_notrun", valobj)
203    elif name_string == "__NSDictionaryI":
204        wrapper = NSDictionaryI_SummaryProvider(valobj, class_data.sys_params)
205        statistics.metric_hit("code_notrun", valobj)
206    elif name_string == "__NSDictionaryM":
207        wrapper = NSDictionaryM_SummaryProvider(valobj, class_data.sys_params)
208        statistics.metric_hit("code_notrun", valobj)
209    else:
210        wrapper = NSDictionaryUnknown_SummaryProvider(valobj, class_data.sys_params)
211        statistics.metric_hit(
212            "unknown_class", valobj.GetName() + " seen as " + name_string
213        )
214    return wrapper
215
216
217def CFDictionary_SummaryProvider(valobj, dict):
218    logger = lldb.formatters.Logger.Logger()
219    provider = GetSummary_Impl(valobj)
220    if provider is not None:
221        if isinstance(
222            provider, lldb.runtime.objc.objc_runtime.SpecialSituation_Description
223        ):
224            return provider.message()
225        try:
226            summary = provider.num_children()
227        except:
228            summary = None
229        logger >> "got summary " + str(summary)
230        if summary is None:
231            return "<variable is not NSDictionary>"
232        if isinstance(summary, str):
233            return summary
234        return str(summary) + (
235            " key/value pairs" if summary != 1 else " key/value pair"
236        )
237    return "Summary Unavailable"
238
239
240def CFDictionary_SummaryProvider2(valobj, dict):
241    logger = lldb.formatters.Logger.Logger()
242    provider = GetSummary_Impl(valobj)
243    if provider is not None:
244        if isinstance(
245            provider, lldb.runtime.objc.objc_runtime.SpecialSituation_Description
246        ):
247            return provider.message()
248        try:
249            summary = provider.num_children()
250        except:
251            summary = None
252        logger >> "got summary " + str(summary)
253        if summary is None:
254            summary = "<variable is not CFDictionary>"
255        if isinstance(summary, str):
256            return summary
257        else:
258            # needed on OSX Mountain Lion
259            if provider.sys_params.is_64_bit:
260                summary = summary & ~0x0F1F000000000000
261            summary = '@"' + str(summary) + (' entries"' if summary != 1 else ' entry"')
262        return summary
263    return "Summary Unavailable"
264
265
266def __lldb_init_module(debugger, dict):
267    debugger.HandleCommand(
268        "type summary add -F CFDictionary.CFDictionary_SummaryProvider NSDictionary"
269    )
270    debugger.HandleCommand(
271        "type summary add -F CFDictionary.CFDictionary_SummaryProvider2 CFDictionaryRef CFMutableDictionaryRef"
272    )
273