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