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