xref: /llvm-project/lldb/examples/summaries/cocoa/CFString.py (revision 86ea8d821a90d7d52b5023e4aa367e5265ca16ce)
1"""
2LLDB AppKit formatters
3
4part of The LLVM Compiler Infrastructure
5This file is distributed under the University of Illinois Open Source
6License. See LICENSE.TXT for details.
7"""
8# synthetic children and summary provider for CFString
9# (and related NSString class)
10import lldb
11import objc_runtime
12
13def CFString_SummaryProvider (valobj,dict):
14	provider = CFStringSynthProvider(valobj,dict);
15	if provider.invalid == False:
16		try:
17			summary = provider.get_child_at_index(provider.get_child_index("content"))
18			if type(summary) == lldb.SBValue:
19				summary = summary.GetSummary()
20			else:
21				summary = '"' + summary + '"'
22		except:
23			summary = None
24		if summary == None:
25			summary = '<variable is not NSString>'
26		return '@'+summary
27	return ''
28
29def CFAttributedString_SummaryProvider (valobj,dict):
30	offset = valobj.GetTarget().GetProcess().GetAddressByteSize()
31	pointee = valobj.GetValueAsUnsigned(0)
32	summary = '<variable is not NSAttributedString>'
33	if pointee != None and pointee != 0:
34		pointee = pointee + offset
35		child_ptr = valobj.CreateValueFromAddress("string_ptr",pointee,valobj.GetType())
36		child = child_ptr.CreateValueFromAddress("string_data",child_ptr.GetValueAsUnsigned(),valobj.GetType()).AddressOf()
37		provider = CFStringSynthProvider(child,dict);
38		if provider.invalid == False:
39			try:
40				summary = provider.get_child_at_index(provider.get_child_index("content")).GetSummary();
41			except:
42				summary = '<variable is not NSAttributedString>'
43	if summary == None:
44		summary = '<variable is not NSAttributedString>'
45	return '@'+summary
46
47
48def __lldb_init_module(debugger,dict):
49	debugger.HandleCommand("type summary add -F CFString.CFString_SummaryProvider NSString CFStringRef CFMutableStringRef")
50	debugger.HandleCommand("type summary add -F CFString.CFAttributedString_SummaryProvider NSAttributedString")
51
52class CFStringSynthProvider:
53	def __init__(self,valobj,dict):
54		self.valobj = valobj;
55		self.update()
56
57	# children other than "content" are for debugging only and must not be used in production code
58	def num_children(self):
59		if self.invalid:
60			return 0;
61		return 6;
62
63	def read_unicode(self, pointer):
64		process = self.valobj.GetTarget().GetProcess()
65		error = lldb.SBError()
66		pystr = u''
67		# cannot do the read at once because the length value has
68		# a weird encoding. better play it safe here
69		while True:
70			content = process.ReadMemory(pointer, 2, error)
71			new_bytes = bytearray(content)
72			b0 = new_bytes[0]
73			b1 = new_bytes[1]
74			pointer = pointer + 2
75			if b0 == 0 and b1 == 0:
76				break
77			# rearrange bytes depending on endianness
78			# (do we really need this or is Cocoa going to
79			#  use Windows-compatible little-endian even
80			#  if the target is big endian?)
81			if self.is_little:
82				value = b1 * 256 + b0
83			else:
84				value = b0 * 256 + b1
85			pystr = pystr + unichr(value)
86		return pystr
87
88	# handle the special case strings
89	# only use the custom code for the tested LP64 case
90	def handle_special(self):
91		if self.is_64_bit == False:
92			# for 32bit targets, use safe ObjC code
93			return self.handle_unicode_string_safe()
94		offset = 12
95		pointer = self.valobj.GetValueAsUnsigned(0) + offset
96		pystr = self.read_unicode(pointer)
97		return self.valobj.CreateValueFromExpression("content",
98			"(char*)\"" + pystr.encode('utf-8') + "\"")
99
100	# last resort call, use ObjC code to read; the final aim is to
101	# be able to strip this call away entirely and only do the read
102	# ourselves
103	def handle_unicode_string_safe(self):
104		return self.valobj.CreateValueFromExpression("content",
105			"(char*)\"" + self.valobj.GetObjectDescription() + "\"");
106
107	def handle_unicode_string(self):
108		# step 1: find offset
109		if self.inline:
110			pointer = self.valobj.GetValueAsUnsigned(0) + self.size_of_cfruntime_base();
111			if self.explicit == False:
112				# untested, use the safe code path
113				return self.handle_unicode_string_safe();
114			else:
115				# a full pointer is skipped here before getting to the live data
116				pointer = pointer + self.pointer_size
117		else:
118			pointer = self.valobj.GetValueAsUnsigned(0) + self.size_of_cfruntime_base()
119			# read 8 bytes here and make an address out of them
120			try:
121				char_type = self.valobj.GetType().GetBasicType(lldb.eBasicTypeChar).GetPointerType()
122				vopointer = self.valobj.CreateValueFromAddress("dummy",pointer,char_type);
123				pointer = vopointer.GetValueAsUnsigned(0)
124			except:
125				return self.valobj.CreateValueFromExpression("content",
126                                                             '(char*)"@\"invalid NSString\""')
127		# step 2: read Unicode data at pointer
128		pystr = self.read_unicode(pointer)
129		# step 3: return it
130		return pystr.encode('utf-8')
131
132	def handle_inline_explicit(self):
133		offset = 3*self.pointer_size
134		offset = offset + self.valobj.GetValueAsUnsigned(0)
135		return self.valobj.CreateValueFromExpression("content",
136				"(char*)(" + str(offset) + ")")
137
138	def handle_mutable_string(self):
139		offset = 2 * self.pointer_size
140		data = self.valobj.CreateChildAtOffset("content",
141			offset, self.valobj.GetType().GetBasicType(lldb.eBasicTypeChar).GetPointerType());
142		data_value = data.GetValueAsUnsigned(0)
143		data_value = data_value + 1
144		return self.valobj.CreateValueFromExpression("content", "(char*)(" + str(data_value) + ")")
145
146	def handle_UTF8_inline(self):
147		offset = self.valobj.GetValueAsUnsigned(0) + self.size_of_cfruntime_base();
148		if self.explicit == False:
149			offset = offset + 1;
150		return self.valobj.CreateValueFromAddress("content",
151				offset, self.valobj.GetType().GetBasicType(lldb.eBasicTypeChar)).AddressOf();
152
153	def handle_UTF8_not_inline(self):
154		offset = self.size_of_cfruntime_base();
155		return self.valobj.CreateChildAtOffset("content",
156				offset,self.valobj.GetType().GetBasicType(lldb.eBasicTypeChar).GetPointerType());
157
158	def get_child_at_index(self,index):
159		if index == 0:
160			return self.valobj.CreateValueFromExpression("mutable",
161				str(int(self.mutable)));
162		if index == 1:
163			return self.valobj.CreateValueFromExpression("inline",
164				str(int(self.inline)));
165		if index == 2:
166			return self.valobj.CreateValueFromExpression("explicit",
167				str(int(self.explicit)));
168		if index == 3:
169			return self.valobj.CreateValueFromExpression("unicode",
170				str(int(self.unicode)));
171		if index == 4:
172			return self.valobj.CreateValueFromExpression("special",
173				str(int(self.special)));
174		if index == 5:
175			# we are handling the several possible combinations of flags.
176			# for each known combination we have a function that knows how to
177			# go fetch the data from memory instead of running code. if a string is not
178			# correctly displayed, one should start by finding a combination of flags that
179			# makes it different from these known cases, and provide a new reader function
180			# if this is not possible, a new flag might have to be made up (like the "special" flag
181			# below, which is not a real flag in CFString), or alternatively one might need to use
182			# the ObjC runtime helper to detect the new class and deal with it accordingly
183			#print 'mutable = ' + str(self.mutable)
184			#print 'inline = ' + str(self.inline)
185			#print 'explicit = ' + str(self.explicit)
186			#print 'unicode = ' + str(self.unicode)
187			#print 'special = ' + str(self.special)
188			if self.mutable == True:
189				return self.handle_mutable_string()
190			elif self.inline == True and self.explicit == True and \
191			   self.unicode == False and self.special == False and \
192			   self.mutable == False:
193				return self.handle_inline_explicit()
194			elif self.unicode == True:
195				return self.handle_unicode_string();
196			elif self.special == True:
197				return self.handle_special();
198			elif self.inline == True:
199				return self.handle_UTF8_inline();
200			else:
201				return self.handle_UTF8_not_inline();
202
203	def get_child_index(self,name):
204		if name == "content":
205			return self.num_children() - 1;
206		if name == "mutable":
207			return 0;
208		if name == "inline":
209			return 1;
210		if name == "explicit":
211			return 2;
212		if name == "unicode":
213			return 3;
214		if name == "special":
215			return 4;
216
217	# CFRuntimeBase is defined as having an additional
218	# 4 bytes (padding?) on LP64 architectures
219	# to get its size we add up sizeof(pointer)+4
220	# and then add 4 more bytes if we are on a 64bit system
221	def size_of_cfruntime_base(self):
222		return self.pointer_size+4+(4 if self.is_64_bit else 0)
223
224	# the info bits are part of the CFRuntimeBase structure
225	# to get at them we have to skip a uintptr_t and then get
226	# at the least-significant byte of a 4 byte array. If we are
227	# on big-endian this means going to byte 3, if we are on
228	# little endian (OSX & iOS), this means reading byte 0
229	def offset_of_info_bits(self):
230		offset = self.pointer_size
231		if self.is_little == False:
232			offset = offset + 3;
233		return offset;
234
235	def read_info_bits(self):
236		cfinfo = self.valobj.CreateChildAtOffset("cfinfo",
237					self.offset_of_info_bits(),
238					self.valobj.GetType().GetBasicType(lldb.eBasicTypeChar));
239		cfinfo.SetFormat(11)
240		info = cfinfo.GetValue();
241		if info != None:
242			self.invalid = False;
243			return int(info,0);
244		else:
245			self.invalid = True;
246			return None;
247
248	# calculating internal flag bits of the CFString object
249	# this stuff is defined and discussed in CFString.c
250	def is_mutable(self):
251		return (self.info_bits & 1) == 1;
252
253	def is_inline(self):
254		return (self.info_bits & 0x60) == 0;
255
256	# this flag's name is ambiguous, it turns out
257	# we must skip a length byte to get at the data
258	# when this flag is False
259	def has_explicit_length(self):
260		return (self.info_bits & (1 | 4)) != 4;
261
262	# probably a subclass of NSString. obtained this from [str pathExtension]
263	# here info_bits = 0 and Unicode data at the start of the padding word
264	# in the long run using the isa value might be safer as a way to identify this
265	# instead of reading the info_bits
266	def is_special_case(self):
267		return self.info_bits == 0;
268
269	def is_unicode(self):
270		return (self.info_bits & 0x10) == 0x10;
271
272	# preparing ourselves to read into memory
273	# by adjusting architecture-specific info
274	def adjust_for_architecture(self):
275		self.pointer_size = self.valobj.GetTarget().GetProcess().GetAddressByteSize()
276		self.is_64_bit = self.pointer_size == 8
277		self.is_little = self.valobj.GetTarget().GetProcess().GetByteOrder() == lldb.eByteOrderLittle
278
279	# reading info bits out of the CFString and computing
280	# useful values to get at the real data
281	def compute_flags(self):
282		self.info_bits = self.read_info_bits();
283		if self.info_bits == None:
284			return;
285		self.mutable = self.is_mutable();
286		self.inline = self.is_inline();
287		self.explicit = self.has_explicit_length();
288		self.unicode = self.is_unicode();
289		self.special = self.is_special_case();
290
291	def update(self):
292		self.adjust_for_architecture();
293		self.compute_flags();