xref: /llvm-project/lldb/examples/summaries/cocoa/CFArray.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 NSArray
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
16statistics = lldb.formatters.metrics.Metrics()
17statistics.add_metric("invalid_isa")
18statistics.add_metric("invalid_pointer")
19statistics.add_metric("unknown_class")
20statistics.add_metric("code_notrun")
21
22# much less functional than the other two cases below
23# just runs code to get to the count and then returns
24# no children
25
26
27class NSArrayKVC_SynthProvider:
28    def adjust_for_architecture(self):
29        pass
30
31    def __init__(self, valobj, dict, params):
32        logger = lldb.formatters.Logger.Logger()
33        self.valobj = valobj
34        self.update()
35
36    def update(self):
37        logger = lldb.formatters.Logger.Logger()
38        self.adjust_for_architecture()
39
40    def num_children(self):
41        logger = lldb.formatters.Logger.Logger()
42        stream = lldb.SBStream()
43        self.valobj.GetExpressionPath(stream)
44        num_children_vo = self.valobj.CreateValueFromExpression(
45            "count", "(int)[" + stream.GetData() + " count]"
46        )
47        if num_children_vo.IsValid():
48            return num_children_vo.GetValueAsUnsigned(0)
49        return "<variable is not NSArray>"
50
51
52# much less functional than the other two cases below
53# just runs code to get to the count and then returns
54# no children
55
56
57class NSArrayCF_SynthProvider:
58    def adjust_for_architecture(self):
59        pass
60
61    def __init__(self, valobj, dict, params):
62        logger = lldb.formatters.Logger.Logger()
63        self.valobj = valobj
64        self.sys_params = params
65        if not (self.sys_params.types_cache.ulong):
66            self.sys_params.types_cache.ulong = self.valobj.GetType().GetBasicType(
67                lldb.eBasicTypeUnsignedLong
68            )
69        self.update()
70
71    def update(self):
72        logger = lldb.formatters.Logger.Logger()
73        self.adjust_for_architecture()
74
75    def num_children(self):
76        logger = lldb.formatters.Logger.Logger()
77        num_children_vo = self.valobj.CreateChildAtOffset(
78            "count", self.sys_params.cfruntime_size, self.sys_params.types_cache.ulong
79        )
80        return num_children_vo.GetValueAsUnsigned(0)
81
82
83class NSArrayI_SynthProvider:
84    def adjust_for_architecture(self):
85        pass
86
87    def __init__(self, valobj, dict, params):
88        logger = lldb.formatters.Logger.Logger()
89        self.valobj = valobj
90        self.sys_params = params
91        if not (self.sys_params.types_cache.long):
92            self.sys_params.types_cache.long = self.valobj.GetType().GetBasicType(
93                lldb.eBasicTypeLong
94            )
95        self.update()
96
97    def update(self):
98        logger = lldb.formatters.Logger.Logger()
99        self.adjust_for_architecture()
100
101    # skip the isa pointer and get at the size
102    def num_children(self):
103        logger = lldb.formatters.Logger.Logger()
104        count = self.valobj.CreateChildAtOffset(
105            "count", self.sys_params.pointer_size, self.sys_params.types_cache.long
106        )
107        return count.GetValueAsUnsigned(0)
108
109
110class NSArrayM_SynthProvider:
111    def adjust_for_architecture(self):
112        pass
113
114    def __init__(self, valobj, dict, params):
115        logger = lldb.formatters.Logger.Logger()
116        self.valobj = valobj
117        self.sys_params = params
118        if not (self.sys_params.types_cache.long):
119            self.sys_params.types_cache.long = self.valobj.GetType().GetBasicType(
120                lldb.eBasicTypeLong
121            )
122        self.update()
123
124    def update(self):
125        logger = lldb.formatters.Logger.Logger()
126        self.adjust_for_architecture()
127
128    # skip the isa pointer and get at the size
129    def num_children(self):
130        logger = lldb.formatters.Logger.Logger()
131        count = self.valobj.CreateChildAtOffset(
132            "count", self.sys_params.pointer_size, self.sys_params.types_cache.long
133        )
134        return count.GetValueAsUnsigned(0)
135
136
137# this is the actual synth provider, but is just a wrapper that checks
138# whether valobj is an instance of __NSArrayI or __NSArrayM and sets up an
139# appropriate backend layer to do the computations
140
141
142class NSArray_SynthProvider:
143    def adjust_for_architecture(self):
144        pass
145
146    def __init__(self, valobj, dict):
147        logger = lldb.formatters.Logger.Logger()
148        self.valobj = valobj
149        self.adjust_for_architecture()
150        self.error = False
151        self.wrapper = self.make_wrapper()
152        self.invalid = self.wrapper is None
153
154    def num_children(self):
155        logger = lldb.formatters.Logger.Logger()
156        if self.wrapper is None:
157            return 0
158        return self.wrapper.num_children()
159
160    def update(self):
161        logger = lldb.formatters.Logger.Logger()
162        if self.wrapper is None:
163            return
164        self.wrapper.update()
165
166    # this code acts as our defense against NULL and uninitialized
167    # NSArray pointers, which makes it much longer than it would be otherwise
168    def make_wrapper(self):
169        logger = lldb.formatters.Logger.Logger()
170        if self.valobj.GetValueAsUnsigned() == 0:
171            self.error = True
172            return lldb.runtime.objc.objc_runtime.InvalidPointer_Description(True)
173        else:
174            global statistics
175            (
176                class_data,
177                wrapper,
178            ) = lldb.runtime.objc.objc_runtime.Utilities.prepare_class_detection(
179                self.valobj, statistics
180            )
181            if wrapper:
182                self.error = True
183                return wrapper
184
185        name_string = class_data.class_name()
186
187        logger >> "Class name is " + str(name_string)
188
189        if name_string == "__NSArrayI":
190            wrapper = NSArrayI_SynthProvider(self.valobj, dict, class_data.sys_params)
191            statistics.metric_hit("code_notrun", self.valobj.GetName())
192        elif name_string == "__NSArrayM":
193            wrapper = NSArrayM_SynthProvider(self.valobj, dict, class_data.sys_params)
194            statistics.metric_hit("code_notrun", self.valobj.GetName())
195        elif name_string == "__NSCFArray":
196            wrapper = NSArrayCF_SynthProvider(self.valobj, dict, class_data.sys_params)
197            statistics.metric_hit("code_notrun", self.valobj.GetName())
198        else:
199            wrapper = NSArrayKVC_SynthProvider(self.valobj, dict, class_data.sys_params)
200            statistics.metric_hit(
201                "unknown_class", str(self.valobj.GetName()) + " seen as " + name_string
202            )
203        return wrapper
204
205
206def CFArray_SummaryProvider(valobj, dict):
207    logger = lldb.formatters.Logger.Logger()
208    provider = NSArray_SynthProvider(valobj, dict)
209    if not provider.invalid:
210        if provider.error:
211            return provider.wrapper.message()
212        try:
213            summary = int(provider.num_children())
214        except:
215            summary = None
216        logger >> "provider gave me " + str(summary)
217        if summary is None:
218            summary = "<variable is not NSArray>"
219        elif isinstance(summary, str):
220            pass
221        else:
222            # we format it like it were a CFString to make it look the same as
223            # the summary from Xcode
224            summary = (
225                '@"' + str(summary) + (" objects" if summary != 1 else " object") + '"'
226            )
227        return summary
228    return "Summary Unavailable"
229
230
231def __lldb_init_module(debugger, dict):
232    debugger.HandleCommand(
233        "type summary add -F CFArray.CFArray_SummaryProvider NSArray CFArrayRef CFMutableArrayRef"
234    )
235