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