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