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 NSNumber
9# the real summary is now C++ code built into LLDB
10
11from __future__ import print_function
12
13import lldb
14import ctypes
15import lldb.runtime.objc.objc_runtime
16import lldb.formatters.metrics
17import struct
18import lldb.formatters.Logger
19
20statistics = lldb.formatters.metrics.Metrics()
21statistics.add_metric('invalid_isa')
22statistics.add_metric('invalid_pointer')
23statistics.add_metric('unknown_class')
24statistics.add_metric('code_notrun')
25
26# despite the similary to synthetic children providers, these classes are not
27# trying to provide anything but the port number of an NSNumber, so they need not
28# obey the interface specification for synthetic children providers
29
30
31class NSTaggedNumber_SummaryProvider:
32
33    def adjust_for_architecture(self):
34        pass
35
36    def __init__(self, valobj, info_bits, data, params):
37        logger = lldb.formatters.Logger.Logger()
38        self.valobj = valobj
39        self.sys_params = params
40        self.info_bits = info_bits
41        self.data = data
42        self.update()
43
44    def update(self):
45        logger = lldb.formatters.Logger.Logger()
46        self.adjust_for_architecture()
47
48    def value(self):
49        logger = lldb.formatters.Logger.Logger()
50        # in spite of the plenty of types made available by the public NSNumber API
51        # only a bunch of these are actually used in the internal implementation
52        # unfortunately, the original type information appears to be lost
53        # so we try to at least recover the proper magnitude of the data
54        if self.info_bits == 0:
55            return '(char)' + \
56                str(ord(ctypes.c_char(chr(self.data % 256)).value))
57        if self.info_bits == 4:
58            return '(short)' + \
59                str(ctypes.c_short(self.data % (256 * 256)).value)
60        if self.info_bits == 8:
61            return '(int)' + str(ctypes.c_int(self.data %
62                                              (256 * 256 * 256 * 256)).value)
63        if self.info_bits == 12:
64            return '(long)' + str(ctypes.c_long(self.data).value)
65        else:
66            return 'unexpected value:(info=' + str(self.info_bits) + \
67                ", value = " + str(self.data) + ')'
68
69
70class NSUntaggedNumber_SummaryProvider:
71
72    def adjust_for_architecture(self):
73        pass
74
75    def __init__(self, valobj, params):
76        logger = lldb.formatters.Logger.Logger()
77        self.valobj = valobj
78        self.sys_params = params
79        if not(self.sys_params.types_cache.char):
80            self.sys_params.types_cache.char = self.valobj.GetType(
81            ).GetBasicType(lldb.eBasicTypeChar)
82        if not(self.sys_params.types_cache.short):
83            self.sys_params.types_cache.short = self.valobj.GetType(
84            ).GetBasicType(lldb.eBasicTypeShort)
85        if not(self.sys_params.types_cache.ushort):
86            self.sys_params.types_cache.ushort = self.valobj.GetType(
87            ).GetBasicType(lldb.eBasicTypeUnsignedShort)
88        if not(self.sys_params.types_cache.int):
89            self.sys_params.types_cache.int = self.valobj.GetType().GetBasicType(lldb.eBasicTypeInt)
90        if not(self.sys_params.types_cache.long):
91            self.sys_params.types_cache.long = self.valobj.GetType(
92            ).GetBasicType(lldb.eBasicTypeLong)
93        if not(self.sys_params.types_cache.ulong):
94            self.sys_params.types_cache.ulong = self.valobj.GetType(
95            ).GetBasicType(lldb.eBasicTypeUnsignedLong)
96        if not(self.sys_params.types_cache.longlong):
97            self.sys_params.types_cache.longlong = self.valobj.GetType(
98            ).GetBasicType(lldb.eBasicTypeLongLong)
99        if not(self.sys_params.types_cache.ulonglong):
100            self.sys_params.types_cache.ulonglong = self.valobj.GetType(
101            ).GetBasicType(lldb.eBasicTypeUnsignedLongLong)
102        if not(self.sys_params.types_cache.float):
103            self.sys_params.types_cache.float = self.valobj.GetType(
104            ).GetBasicType(lldb.eBasicTypeFloat)
105        if not(self.sys_params.types_cache.double):
106            self.sys_params.types_cache.double = self.valobj.GetType(
107            ).GetBasicType(lldb.eBasicTypeDouble)
108        self.update()
109
110    def update(self):
111        logger = lldb.formatters.Logger.Logger()
112        self.adjust_for_architecture()
113
114    def value(self):
115        logger = lldb.formatters.Logger.Logger()
116        global statistics
117        # we need to skip the ISA, then the next byte tells us what to read
118        # we then skip one other full pointer worth of data and then fetch the contents
119        # if we are fetching an int64 value, one more pointer must be skipped
120        # to get at our data
121        data_type_vo = self.valobj.CreateChildAtOffset(
122            "dt", self.sys_params.pointer_size, self.sys_params.types_cache.char)
123        data_type = ((data_type_vo.GetValueAsUnsigned(0) % 256) & 0x1F)
124        data_offset = 2 * self.sys_params.pointer_size
125        if data_type == 0B00001:
126            data_vo = self.valobj.CreateChildAtOffset(
127                "data", data_offset, self.sys_params.types_cache.char)
128            statistics.metric_hit('code_notrun', self.valobj)
129            return '(char)' + \
130                str(ord(ctypes.c_char(chr(data_vo.GetValueAsUnsigned(0))).value))
131        elif data_type == 0B0010:
132            data_vo = self.valobj.CreateChildAtOffset(
133                "data", data_offset, self.sys_params.types_cache.short)
134            statistics.metric_hit('code_notrun', self.valobj)
135            return '(short)' + str(
136                ctypes.c_short(
137                    data_vo.GetValueAsUnsigned(0) %
138                    (256 * 256)).value)
139        # IF tagged pointers are possible on 32bit+v2 runtime
140        # (of which the only existing instance should be iOS)
141        # then values of this type might be tagged
142        elif data_type == 0B0011:
143            data_vo = self.valobj.CreateChildAtOffset(
144                "data", data_offset, self.sys_params.types_cache.int)
145            statistics.metric_hit('code_notrun', self.valobj)
146            return '(int)' + str(ctypes.c_int(data_vo.GetValueAsUnsigned(0) %
147                                              (256 * 256 * 256 * 256)).value)
148        # apparently, on is_64_bit architectures, these are the only values that will ever
149        # be represented by a non tagged pointers
150        elif data_type == 0B10001:
151            data_offset = data_offset + 8  # 8 is needed even if we are on 32bit
152            data_vo = self.valobj.CreateChildAtOffset(
153                "data", data_offset, self.sys_params.types_cache.longlong)
154            statistics.metric_hit('code_notrun', self.valobj)
155            return '(long)' + \
156                str(ctypes.c_long(data_vo.GetValueAsUnsigned(0)).value)
157        elif data_type == 0B0100:
158            if self.sys_params.is_64_bit:
159                data_offset = data_offset + self.sys_params.pointer_size
160            data_vo = self.valobj.CreateChildAtOffset(
161                "data", data_offset, self.sys_params.types_cache.longlong)
162            statistics.metric_hit('code_notrun', self.valobj)
163            return '(long)' + \
164                str(ctypes.c_long(data_vo.GetValueAsUnsigned(0)).value)
165        elif data_type == 0B0101:
166            data_vo = self.valobj.CreateChildAtOffset(
167                "data", data_offset, self.sys_params.types_cache.longlong)
168            data_plain = int(
169                str(data_vo.GetValueAsUnsigned(0) & 0x00000000FFFFFFFF))
170            packed = struct.pack('I', data_plain)
171            data_float = struct.unpack('f', packed)[0]
172            statistics.metric_hit('code_notrun', self.valobj)
173            return '(float)' + str(data_float)
174        elif data_type == 0B0110:
175            data_vo = self.valobj.CreateChildAtOffset(
176                "data", data_offset, self.sys_params.types_cache.longlong)
177            data_plain = data_vo.GetValueAsUnsigned(0)
178            data_double = struct.unpack('d', struct.pack('Q', data_plain))[0]
179            statistics.metric_hit('code_notrun', self.valobj)
180            return '(double)' + str(data_double)
181        statistics.metric_hit(
182            'unknown_class', str(
183                valobj.GetName()) + " had unknown data_type " + str(data_type))
184        return 'unexpected: dt = ' + str(data_type)
185
186
187class NSUnknownNumber_SummaryProvider:
188
189    def adjust_for_architecture(self):
190        pass
191
192    def __init__(self, valobj, params):
193        logger = lldb.formatters.Logger.Logger()
194        self.valobj = valobj
195        self.sys_params = params
196        self.update()
197
198    def update(self):
199        logger = lldb.formatters.Logger.Logger()
200        self.adjust_for_architecture()
201
202    def value(self):
203        logger = lldb.formatters.Logger.Logger()
204        stream = lldb.SBStream()
205        self.valobj.GetExpressionPath(stream)
206        expr = "(NSString*)[" + stream.GetData() + " stringValue]"
207        num_children_vo = self.valobj.CreateValueFromExpression("str", expr)
208        if num_children_vo.IsValid():
209            return num_children_vo.GetSummary()
210        return '<variable is not NSNumber>'
211
212
213def GetSummary_Impl(valobj):
214    logger = lldb.formatters.Logger.Logger()
215    global statistics
216    class_data, wrapper = lldb.runtime.objc.objc_runtime.Utilities.prepare_class_detection(
217        valobj, statistics)
218    if wrapper:
219        return wrapper
220
221    name_string = class_data.class_name()
222    logger >> "class name is: " + str(name_string)
223
224    if name_string == 'NSNumber' or name_string == '__NSCFNumber':
225        if class_data.is_tagged():
226            wrapper = NSTaggedNumber_SummaryProvider(
227                valobj, class_data.info_bits(), class_data.value(), class_data.sys_params)
228            statistics.metric_hit('code_notrun', valobj)
229        else:
230            # the wrapper might be unable to decipher what is into the NSNumber
231            # and then have to run code on it
232            wrapper = NSUntaggedNumber_SummaryProvider(
233                valobj, class_data.sys_params)
234    else:
235        wrapper = NSUnknownNumber_SummaryProvider(
236            valobj, class_data.sys_params)
237        statistics.metric_hit(
238            'unknown_class',
239            valobj.GetName() +
240            " seen as " +
241            name_string)
242    return wrapper
243
244
245def NSNumber_SummaryProvider(valobj, dict):
246    logger = lldb.formatters.Logger.Logger()
247    provider = GetSummary_Impl(valobj)
248    if provider is not None:
249        if isinstance(
250                provider,
251                lldb.runtime.objc.objc_runtime.SpecialSituation_Description):
252            return provider.message()
253        try:
254            summary = provider.value()
255        except Exception as foo:
256            print(foo)
257#		except:
258            summary = None
259        logger >> "got summary " + str(summary)
260        if summary is None:
261            summary = '<variable is not NSNumber>'
262        return str(summary)
263    return 'Summary Unavailable'
264
265
266def __lldb_init_module(debugger, dict):
267    debugger.HandleCommand(
268        "type summary add -F NSNumber.NSNumber_SummaryProvider NSNumber")
269    debugger.HandleCommand(
270        "type summary add -F NSNumber.NSNumber_SummaryProvider __NSCFBoolean")
271    debugger.HandleCommand(
272        "type summary add -F NSNumber.NSNumber_SummaryProvider __NSCFNumber")
273