xref: /llvm-project/lldb/examples/summaries/cocoa/CFArray.py (revision 7bc0ec3aad663a2c81fddf9da38dba46bba6be19)
1# synthetic children provider for NSArray
2import lldb
3import ctypes
4import objc_runtime
5import metrics
6
7statistics = metrics.Metrics()
8statistics.add_metric('invalid_isa')
9statistics.add_metric('invalid_pointer')
10statistics.add_metric('unknown_class')
11statistics.add_metric('code_notrun')
12
13# much less functional than the other two cases below
14# just runs code to get to the count and then returns
15# no children
16class NSArrayKVC_SynthProvider:
17
18	def adjust_for_architecture(self):
19		self.is_64_bit = (self.valobj.GetTarget().GetProcess().GetAddressByteSize() == 8)
20		self.is_little = (self.valobj.GetTarget().GetProcess().GetByteOrder() == lldb.eByteOrderLittle)
21		self.pointer_size = self.valobj.GetTarget().GetProcess().GetAddressByteSize()
22
23	def __init__(self, valobj, dict):
24		self.valobj = valobj;
25		self.update()
26
27	def update(self):
28		self.adjust_for_architecture();
29		self.id_type = self.valobj.GetType().GetBasicType(lldb.eBasicTypeObjCID)
30
31	def num_children(self):
32		stream = lldb.SBStream()
33		self.valobj.GetExpressionPath(stream)
34		num_children_vo = self.valobj.CreateValueFromExpression("count","(int)[" + stream.GetData() + " count]");
35		return num_children_vo.GetValueAsUnsigned(0)
36
37	def get_child_index(self,name):
38		if name == "len":
39			return self.num_children();
40		else:
41			return None
42
43	def get_child_at_index(self, index):
44		return None
45
46
47
48# much less functional than the other two cases below
49# just runs code to get to the count and then returns
50# no children
51class NSArrayCF_SynthProvider:
52
53	def adjust_for_architecture(self):
54		self.is_64_bit = (self.valobj.GetTarget().GetProcess().GetAddressByteSize() == 8)
55		self.is_little = (self.valobj.GetTarget().GetProcess().GetByteOrder() == lldb.eByteOrderLittle)
56		self.pointer_size = self.valobj.GetTarget().GetProcess().GetAddressByteSize()
57		self.cfruntime_size = self.size_of_cfruntime_base()
58
59	# CFRuntimeBase is defined as having an additional
60	# 4 bytes (padding?) on LP64 architectures
61	# to get its size we add up sizeof(pointer)+4
62	# and then add 4 more bytes if we are on a 64bit system
63	def size_of_cfruntime_base(self):
64		if self.is_64_bit == True:
65			return 8+4+4;
66		else:
67			return 4+4;
68
69	def __init__(self, valobj, dict):
70		self.valobj = valobj;
71		self.update()
72
73	def update(self):
74		self.adjust_for_architecture();
75		self.id_type = self.valobj.GetType().GetBasicType(lldb.eBasicTypeObjCID)
76
77	def num_children(self):
78		num_children_vo = self.valobj.CreateChildAtOffset("count",
79							self.cfruntime_size,
80							self.valobj.GetType().GetBasicType(lldb.eBasicTypeUnsignedLong))
81		return num_children_vo.GetValueAsUnsigned(0)
82
83	def get_child_index(self,name):
84		if name == "len":
85			return self.num_children();
86		else:
87			return None
88
89	def get_child_at_index(self, index):
90		return None
91
92
93class NSArrayI_SynthProvider:
94
95	def adjust_for_architecture(self):
96		self.is_64_bit = (self.valobj.GetTarget().GetProcess().GetAddressByteSize() == 8)
97		self.is_little = (self.valobj.GetTarget().GetProcess().GetByteOrder() == lldb.eByteOrderLittle)
98		self.pointer_size = self.valobj.GetTarget().GetProcess().GetAddressByteSize()
99
100	def __init__(self, valobj, dict):
101		self.valobj = valobj;
102		self.update()
103
104	def update(self):
105		self.adjust_for_architecture();
106		self.id_type = self.valobj.GetType().GetBasicType(lldb.eBasicTypeObjCID)
107
108	# skip the isa pointer and get at the size
109	def num_children(self):
110		offset = self.pointer_size;
111		datatype = self.valobj.GetType().GetBasicType(lldb.eBasicTypeLong)
112		count = self.valobj.CreateChildAtOffset("count",
113				offset,
114				datatype);
115		return int(count.GetValue(), 0)
116
117	def get_child_index(self,name):
118		if name == "len":
119			return self.num_children();
120		else:
121			return int(name.lstrip('[').rstrip(']'), 0)
122
123	def get_child_at_index(self, index):
124		if index == self.num_children():
125			return self.valobj.CreateValueFromExpression("len",
126				str(index))
127		offset = 2 * self.pointer_size + self.id_type.GetByteSize()*index
128		return self.valobj.CreateChildAtOffset('[' + str(index) + ']',
129				offset,
130				self.id_type)
131
132
133class NSArrayM_SynthProvider:
134
135	def adjust_for_architecture(self):
136		self.is_64_bit = (self.valobj.GetTarget().GetProcess().GetAddressByteSize() == 8)
137		self.is_little = (self.valobj.GetTarget().GetProcess().GetByteOrder() == lldb.eByteOrderLittle)
138		self.pointer_size = self.valobj.GetTarget().GetProcess().GetAddressByteSize()
139
140	def __init__(self, valobj, dict):
141		self.valobj = valobj;
142		self.update();
143
144	def update(self):
145		self.adjust_for_architecture();
146		self.id_type = self.valobj.GetType().GetBasicType(lldb.eBasicTypeObjCID)
147
148	# skip the isa pointer and get at the size
149	def num_children(self):
150		offset = self.pointer_size;
151		datatype = self.valobj.GetType().GetBasicType(lldb.eBasicTypeLong)
152		count = self.valobj.CreateChildAtOffset("count",
153				offset,
154				datatype);
155		return int(count.GetValue(), 0)
156
157	def get_child_index(self,name):
158		if name == "len":
159			return self.num_children();
160		else:
161			return int(name.lstrip('[').rstrip(']'), 0)
162
163	def data_offset(self):
164		offset = self.pointer_size; # isa
165		offset += self.pointer_size; # _used
166		offset += self.pointer_size; # _doHardRetain, _doWeakAccess, _size
167		offset += self.pointer_size; # _hasObjects, _hasStrongReferences, _offset
168		offset += self.pointer_size; # _mutations
169		return offset;
170
171	# the _offset field is used to calculate the actual offset
172	# when reading a value out of the array. we need to read it
173	# to do so we read a whole pointer_size of data from the
174	# right spot, and then zero out the two LSB
175	def read_offset_field(self):
176		disp = self.pointer_size;  # isa
177		disp += self.pointer_size; # _used
178		disp += self.pointer_size; # _doHardRetain, _doWeakAccess, _size
179		offset = self.valobj.CreateChildAtOffset("offset",
180					disp,
181					self.valobj.GetType().GetBasicType(lldb.eBasicTypeLong))
182		offset_value = int(offset.GetValue(), 0)
183		offset_value = ctypes.c_uint32((offset_value & 0xFFFFFFFC) >> 2).value
184		return offset_value
185
186	# the _used field tells how many items are in the array
187	# but since this is a mutable array, it allocates more space
188	# for performance reasons. we need to get the real _size of
189	# the array to calculate the actual offset of each element
190	# in get_child_at_index() (see NSArray.m for details)
191	def read_size_field(self):
192		disp = self.pointer_size;  # isa
193		disp += self.pointer_size; # _used
194		size = self.valobj.CreateChildAtOffset("size",
195					disp,
196					self.valobj.GetType().GetBasicType(lldb.eBasicTypeLong))
197		size_value = int(size.GetValue(), 0)
198		size_value = ctypes.c_uint32((size_value & 0xFFFFFFFA) >> 2).value
199		return size_value
200
201	def get_child_at_index(self, index):
202		if index == self.num_children():
203			return self.valobj.CreateValueFromExpression("len",
204				str(index))
205		size = self.read_size_field()
206		offset = self.read_offset_field()
207		phys_idx = offset + index
208		if size <= phys_idx:
209			phys_idx -=size;
210		# we still need to multiply by element size to do a correct pointer read
211		phys_idx *= self.id_type.GetByteSize()
212		list_ptr = self.valobj.CreateChildAtOffset("_list",
213            self.data_offset(),
214            self.id_type.GetBasicType(lldb.eBasicTypeUnsignedLongLong))
215		list_addr = int(list_ptr.GetValue(), 0)
216		return self.valobj.CreateValueFromAddress('[' + str(index) + ']',
217				list_addr + phys_idx,
218				self.id_type)
219
220# this is the actual synth provider, but is just a wrapper that checks
221# whether valobj is an instance of __NSArrayI or __NSArrayM and sets up an
222# appropriate backend layer to do the computations
223class NSArray_SynthProvider:
224
225	def adjust_for_architecture(self):
226		self.is_64_bit = (self.valobj.GetTarget().GetProcess().GetAddressByteSize() == 8)
227		self.is_little = (self.valobj.GetTarget().GetProcess().GetByteOrder() == lldb.eByteOrderLittle)
228		self.pointer_size = self.valobj.GetTarget().GetProcess().GetAddressByteSize()
229		self.id_type = self.valobj.GetType().GetBasicType(lldb.eBasicTypeObjCID)
230
231	def __init__(self, valobj, dict):
232		self.valobj = valobj;
233		self.adjust_for_architecture()
234		self.wrapper = self.make_wrapper(valobj,dict)
235		self.invalid = (self.wrapper == None)
236
237	def get_child_at_index(self, index):
238		if self.wrapper == None:
239			return None;
240		return self.wrapper.get_child_at_index(index)
241
242	def get_child_index(self,name):
243		if self.wrapper == None:
244			return None;
245		return self.wrapper.get_child_index(name)
246
247	def num_children(self):
248		if self.wrapper == None:
249			return 0;
250		return self.wrapper.num_children()
251
252	def update(self):
253		if self.wrapper == None:
254			return None;
255		return self.wrapper.update()
256
257	def read_ascii(self, pointer):
258		process = self.valobj.GetTarget().GetProcess()
259		error = lldb.SBError()
260		pystr = ''
261		# cannot do the read at once because there is no length byte
262		while True:
263			content = process.ReadMemory(pointer, 1, error)
264			new_bytes = bytearray(content)
265			b0 = new_bytes[0]
266			pointer = pointer + 1
267			if b0 == 0:
268				break
269			pystr = pystr + chr(b0)
270		return pystr
271
272	# this code acts as our defense against NULL and unitialized
273	# NSArray pointers, which makes it much longer than it would be otherwise
274	def make_wrapper(self,valobj,dict):
275		global statistics
276		class_data = objc_runtime.ObjCRuntime(valobj)
277		if class_data.is_valid() == False:
278			statistics.metric_hit('invalid_pointer',valobj)
279			wrapper = None
280			return
281		class_data = class_data.read_class_data()
282		if class_data.is_valid() == False:
283			statistics.metric_hit('invalid_isa',valobj)
284			wrapper = None
285			return
286		if class_data.is_kvo():
287			class_data = class_data.get_superclass()
288		if class_data.is_valid() == False:
289			statistics.metric_hit('invalid_isa',valobj)
290			wrapper = None
291			return
292
293		name_string = class_data.class_name()
294		if name_string == '__NSArrayI':
295			wrapper = NSArrayI_SynthProvider(valobj, dict)
296			statistics.metric_hit('code_notrun',valobj)
297		elif name_string == '__NSArrayM':
298			wrapper = NSArrayM_SynthProvider(valobj, dict)
299			statistics.metric_hit('code_notrun',valobj)
300		elif name_string == '__NSCFArray':
301			wrapper = NSArrayCF_SynthProvider(valobj, dict)
302			statistics.metric_hit('code_notrun',valobj)
303		else:
304			wrapper = NSArrayKVC_SynthProvider(valobj, dict)
305			statistics.metric_hit('unknown_class',str(valobj) + " seen as " + name_string)
306		return wrapper;
307
308def CFArray_SummaryProvider (valobj,dict):
309	provider = NSArray_SynthProvider(valobj,dict);
310	if provider.invalid == False:
311	    try:
312	        summary = str(provider.num_children());
313	    except:
314	        summary = None
315	    if summary == None:
316	        summary = 'no valid array here'
317	    return summary + " objects"
318	return ''
319
320def __lldb_init_module(debugger,dict):
321	debugger.HandleCommand("type summary add -F CFArray.CFArray_SummaryProvider NSArray CFArrayRef CFMutableArrayRef")
322