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