xref: /llvm-project/lldb/examples/summaries/cocoa/NSDate.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 NSDate
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 struct
15import time
16import datetime
17import CFString
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# Python promises to start counting time at midnight on Jan 1st on the epoch year
27# hence, all we need to know is the epoch year
28python_epoch = time.gmtime(0).tm_year
29
30osx_epoch = datetime.date(2001, 1, 1).timetuple()
31
32
33def mkgmtime(t):
34    logger = lldb.formatters.Logger.Logger()
35    return time.mktime(t) - time.timezone
36
37
38osx_epoch = mkgmtime(osx_epoch)
39
40
41def osx_to_python_time(osx):
42    logger = lldb.formatters.Logger.Logger()
43    if python_epoch <= 2001:
44        return osx + osx_epoch
45    else:
46        return osx - osx_epoch
47
48
49# represent a struct_time as a string in the format used by Xcode
50
51
52def xcode_format_time(X):
53    logger = lldb.formatters.Logger.Logger()
54    return time.strftime("%Y-%m-%d %H:%M:%S %Z", X)
55
56
57# represent a count-since-epoch as a string in the format used by Xcode
58
59
60def xcode_format_count(X):
61    logger = lldb.formatters.Logger.Logger()
62    return xcode_format_time(time.localtime(X))
63
64
65# despite the similary to synthetic children providers, these classes are not
66# trying to provide anything but the summary for NSDate, so they need not
67# obey the interface specification for synthetic children providers
68
69
70class NSTaggedDate_SummaryProvider:
71    def adjust_for_architecture(self):
72        pass
73
74    def __init__(self, valobj, info_bits, data, params):
75        logger = lldb.formatters.Logger.Logger()
76        self.valobj = valobj
77        self.sys_params = params
78        self.update()
79        # NSDate is not using its info_bits for info like NSNumber is
80        # so we need to regroup info_bits and data
81        self.data = (data << 8) | (info_bits << 4)
82
83    def update(self):
84        logger = lldb.formatters.Logger.Logger()
85        self.adjust_for_architecture()
86
87    def value(self):
88        logger = lldb.formatters.Logger.Logger()
89        # the value of the date-time object is wrapped into the pointer value
90        # unfortunately, it is made as a time-delta after Jan 1 2001 midnight GMT
91        # while all Python knows about is the "epoch", which is a platform-dependent
92        # year (1970 of *nix) whose Jan 1 at midnight is taken as reference
93        value_double = struct.unpack("d", struct.pack("Q", self.data))[0]
94        if value_double == -63114076800.0:
95            return "0001-12-30 00:00:00 +0000"
96        return xcode_format_count(osx_to_python_time(value_double))
97
98
99class NSUntaggedDate_SummaryProvider:
100    def adjust_for_architecture(self):
101        pass
102
103    def __init__(self, valobj, params):
104        logger = lldb.formatters.Logger.Logger()
105        self.valobj = valobj
106        self.sys_params = params
107        if not (self.sys_params.types_cache.double):
108            self.sys_params.types_cache.double = self.valobj.GetType().GetBasicType(
109                lldb.eBasicTypeDouble
110            )
111        self.update()
112
113    def update(self):
114        logger = lldb.formatters.Logger.Logger()
115        self.adjust_for_architecture()
116
117    def offset(self):
118        logger = lldb.formatters.Logger.Logger()
119        return self.sys_params.pointer_size
120
121    def value(self):
122        logger = lldb.formatters.Logger.Logger()
123        value = self.valobj.CreateChildAtOffset(
124            "value", self.offset(), self.sys_params.types_cache.double
125        )
126        value_double = struct.unpack("d", struct.pack("Q", value.GetData().uint64[0]))[
127            0
128        ]
129        if value_double == -63114076800.0:
130            return "0001-12-30 00:00:00 +0000"
131        return xcode_format_count(osx_to_python_time(value_double))
132
133
134class NSCalendarDate_SummaryProvider:
135    def adjust_for_architecture(self):
136        pass
137
138    def __init__(self, valobj, params):
139        logger = lldb.formatters.Logger.Logger()
140        self.valobj = valobj
141        self.sys_params = params
142        if not (self.sys_params.types_cache.double):
143            self.sys_params.types_cache.double = self.valobj.GetType().GetBasicType(
144                lldb.eBasicTypeDouble
145            )
146        self.update()
147
148    def update(self):
149        logger = lldb.formatters.Logger.Logger()
150        self.adjust_for_architecture()
151
152    def offset(self):
153        logger = lldb.formatters.Logger.Logger()
154        return 2 * self.sys_params.pointer_size
155
156    def value(self):
157        logger = lldb.formatters.Logger.Logger()
158        value = self.valobj.CreateChildAtOffset(
159            "value", self.offset(), self.sys_params.types_cache.double
160        )
161        value_double = struct.unpack("d", struct.pack("Q", value.GetData().uint64[0]))[
162            0
163        ]
164        return xcode_format_count(osx_to_python_time(value_double))
165
166
167class NSTimeZoneClass_SummaryProvider:
168    def adjust_for_architecture(self):
169        pass
170
171    def __init__(self, valobj, params):
172        logger = lldb.formatters.Logger.Logger()
173        self.valobj = valobj
174        self.sys_params = params
175        if not (self.sys_params.types_cache.voidptr):
176            self.sys_params.types_cache.voidptr = (
177                self.valobj.GetType().GetBasicType(lldb.eBasicTypeVoid).GetPointerType()
178            )
179        self.update()
180
181    def update(self):
182        logger = lldb.formatters.Logger.Logger()
183        self.adjust_for_architecture()
184
185    def offset(self):
186        logger = lldb.formatters.Logger.Logger()
187        return self.sys_params.pointer_size
188
189    def timezone(self):
190        logger = lldb.formatters.Logger.Logger()
191        tz_string = self.valobj.CreateChildAtOffset(
192            "tz_name", self.offset(), self.sys_params.types_cache.voidptr
193        )
194        return CFString.CFString_SummaryProvider(tz_string, None)
195
196
197class NSUnknownDate_SummaryProvider:
198    def adjust_for_architecture(self):
199        pass
200
201    def __init__(self, valobj):
202        logger = lldb.formatters.Logger.Logger()
203        self.valobj = valobj
204        self.update()
205
206    def update(self):
207        logger = lldb.formatters.Logger.Logger()
208        self.adjust_for_architecture()
209
210    def value(self):
211        logger = lldb.formatters.Logger.Logger()
212        stream = lldb.SBStream()
213        self.valobj.GetExpressionPath(stream)
214        expr = "(NSString*)[" + stream.GetData() + " description]"
215        num_children_vo = self.valobj.CreateValueFromExpression("str", expr)
216        if num_children_vo.IsValid():
217            return num_children_vo.GetSummary()
218        return "<variable is not NSDate>"
219
220
221def GetSummary_Impl(valobj):
222    logger = lldb.formatters.Logger.Logger()
223    global statistics
224    (
225        class_data,
226        wrapper,
227    ) = lldb.runtime.objc.objc_runtime.Utilities.prepare_class_detection(
228        valobj, statistics
229    )
230    if wrapper:
231        return wrapper
232
233    name_string = class_data.class_name()
234    logger >> "class name is: " + str(name_string)
235
236    if (
237        name_string == "NSDate"
238        or name_string == "__NSDate"
239        or name_string == "__NSTaggedDate"
240    ):
241        if class_data.is_tagged():
242            wrapper = NSTaggedDate_SummaryProvider(
243                valobj,
244                class_data.info_bits(),
245                class_data.value(),
246                class_data.sys_params,
247            )
248            statistics.metric_hit("code_notrun", valobj)
249        else:
250            wrapper = NSUntaggedDate_SummaryProvider(valobj, class_data.sys_params)
251            statistics.metric_hit("code_notrun", valobj)
252    elif name_string == "NSCalendarDate":
253        wrapper = NSCalendarDate_SummaryProvider(valobj, class_data.sys_params)
254        statistics.metric_hit("code_notrun", valobj)
255    elif name_string == "__NSTimeZone":
256        wrapper = NSTimeZoneClass_SummaryProvider(valobj, class_data.sys_params)
257        statistics.metric_hit("code_notrun", valobj)
258    else:
259        wrapper = NSUnknownDate_SummaryProvider(valobj)
260        statistics.metric_hit(
261            "unknown_class", valobj.GetName() + " seen as " + name_string
262        )
263    return wrapper
264
265
266def NSDate_SummaryProvider(valobj, dict):
267    logger = lldb.formatters.Logger.Logger()
268    provider = GetSummary_Impl(valobj)
269    if provider is not None:
270        if isinstance(
271            provider, lldb.runtime.objc.objc_runtime.SpecialSituation_Description
272        ):
273            return provider.message()
274        try:
275            summary = provider.value()
276        except:
277            summary = None
278        if summary is None:
279            summary = "<variable is not NSDate>"
280        return str(summary)
281    return "Summary Unavailable"
282
283
284def NSTimeZone_SummaryProvider(valobj, dict):
285    logger = lldb.formatters.Logger.Logger()
286    provider = GetSummary_Impl(valobj)
287    if provider is not None:
288        if isinstance(
289            provider, lldb.runtime.objc.objc_runtime.SpecialSituation_Description
290        ):
291            return provider.message()
292        try:
293            summary = provider.timezone()
294        except:
295            summary = None
296        logger >> "got summary " + str(summary)
297        if summary is None:
298            summary = "<variable is not NSTimeZone>"
299        return str(summary)
300    return "Summary Unavailable"
301
302
303def CFAbsoluteTime_SummaryProvider(valobj, dict):
304    logger = lldb.formatters.Logger.Logger()
305    try:
306        value_double = struct.unpack("d", struct.pack("Q", valobj.GetData().uint64[0]))[
307            0
308        ]
309        return xcode_format_count(osx_to_python_time(value_double))
310    except:
311        return "Summary Unavailable"
312
313
314def __lldb_init_module(debugger, dict):
315    debugger.HandleCommand("type summary add -F NSDate.NSDate_SummaryProvider NSDate")
316    debugger.HandleCommand(
317        "type summary add -F NSDate.CFAbsoluteTime_SummaryProvider CFAbsoluteTime"
318    )
319    debugger.HandleCommand(
320        "type summary add -F NSDate.NSTimeZone_SummaryProvider NSTimeZone CFTimeZoneRef"
321    )
322