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 CFBag
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 lldb.formatters.Logger
15
16try:
17    basestring
18except NameError:
19    basestring = str
20
21statistics = lldb.formatters.metrics.Metrics()
22statistics.add_metric('invalid_isa')
23statistics.add_metric('invalid_pointer')
24statistics.add_metric('unknown_class')
25statistics.add_metric('code_notrun')
26
27# despite the similary to synthetic children providers, these classes are not
28# trying to provide anything but the length for an CFBag, so they need not
29# obey the interface specification for synthetic children providers
30
31
32class CFBagRef_SummaryProvider:
33
34    def adjust_for_architecture(self):
35        pass
36
37    def __init__(self, valobj, params):
38        logger = lldb.formatters.Logger.Logger()
39        self.valobj = valobj
40        self.sys_params = params
41        if not(self.sys_params.types_cache.NSUInteger):
42            if self.sys_params.is_64_bit:
43                self.sys_params.types_cache.NSUInteger = self.valobj.GetType(
44                ).GetBasicType(lldb.eBasicTypeUnsignedLong)
45            else:
46                self.sys_params.types_cache.NSUInteger = self.valobj.GetType(
47                ).GetBasicType(lldb.eBasicTypeUnsignedInt)
48        self.update()
49
50    def update(self):
51        logger = lldb.formatters.Logger.Logger()
52        self.adjust_for_architecture()
53
54    # 12 bytes on i386
55    # 20 bytes on x64
56    # most probably 2 pointers and 4 bytes of data
57    def offset(self):
58        logger = lldb.formatters.Logger.Logger()
59        if self.sys_params.is_64_bit:
60            return 20
61        else:
62            return 12
63
64    def length(self):
65        logger = lldb.formatters.Logger.Logger()
66        size = self.valobj.CreateChildAtOffset(
67            "count", self.offset(), self.sys_params.types_cache.NSUInteger)
68        return size.GetValueAsUnsigned(0)
69
70
71class CFBagUnknown_SummaryProvider:
72
73    def adjust_for_architecture(self):
74        pass
75
76    def __init__(self, valobj, params):
77        logger = lldb.formatters.Logger.Logger()
78        self.valobj = valobj
79        self.sys_params = params
80        self.update()
81
82    def update(self):
83        logger = lldb.formatters.Logger.Logger()
84        self.adjust_for_architecture()
85
86    def length(self):
87        logger = lldb.formatters.Logger.Logger()
88        stream = lldb.SBStream()
89        self.valobj.GetExpressionPath(stream)
90        num_children_vo = self.valobj.CreateValueFromExpression(
91            "count", "(int)CFBagGetCount(" + stream.GetData() + " )")
92        if num_children_vo.IsValid():
93            return num_children_vo.GetValueAsUnsigned(0)
94        return "<variable is not CFBag>"
95
96
97def GetSummary_Impl(valobj):
98    logger = lldb.formatters.Logger.Logger()
99    global statistics
100    class_data, wrapper = lldb.runtime.objc.objc_runtime.Utilities.prepare_class_detection(
101        valobj, statistics)
102    if wrapper:
103        return wrapper
104
105    name_string = class_data.class_name()
106    actual_name = name_string
107
108    logger >> "name string got was " + \
109        str(name_string) + " but actual name is " + str(actual_name)
110
111    if class_data.is_cftype():
112        # CFBag does not expose an actual NSWrapper type, so we have to check that this is
113        # an NSCFType and then check we are a pointer-to __CFBag
114        valobj_type = valobj.GetType()
115        if valobj_type.IsValid() and valobj_type.IsPointerType():
116            valobj_type = valobj_type.GetPointeeType()
117            if valobj_type.IsValid():
118                actual_name = valobj_type.GetName()
119        if actual_name == '__CFBag' or \
120           actual_name == 'const struct __CFBag':
121            wrapper = CFBagRef_SummaryProvider(valobj, class_data.sys_params)
122            statistics.metric_hit('code_notrun', valobj)
123            return wrapper
124    wrapper = CFBagUnknown_SummaryProvider(valobj, class_data.sys_params)
125    statistics.metric_hit(
126        'unknown_class',
127        valobj.GetName() +
128        " seen as " +
129        actual_name)
130    return wrapper
131
132
133def CFBag_SummaryProvider(valobj, dict):
134    logger = lldb.formatters.Logger.Logger()
135    provider = GetSummary_Impl(valobj)
136    if provider is not None:
137        if isinstance(
138                provider,
139                lldb.runtime.objc.objc_runtime.SpecialSituation_Description):
140            return provider.message()
141        try:
142            summary = provider.length()
143        except:
144            summary = None
145        logger >> "summary got from provider: " + str(summary)
146        # for some reason, one needs to clear some bits for the count
147        # to be correct when using CF(Mutable)BagRef on x64
148        # the bit mask was derived through experimentation
149        # (if counts start looking weird, then most probably
150        #  the mask needs to be changed)
151        if summary is None:
152            summary = '<variable is not CFBag>'
153        elif isinstance(summary, basestring):
154            pass
155        else:
156            if provider.sys_params.is_64_bit:
157                summary = summary & ~0x1fff000000000000
158            if summary == 1:
159                summary = '@"1 value"'
160            else:
161                summary = '@"' + str(summary) + ' values"'
162        return summary
163    return 'Summary Unavailable'
164
165
166def __lldb_init_module(debugger, dict):
167    debugger.HandleCommand(
168        "type summary add -F CFBag.CFBag_SummaryProvider CFBagRef CFMutableBagRef")
169