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# summary provider for NSSet
9import lldb
10import ctypes
11import lldb.runtime.objc.objc_runtime
12import lldb.formatters.metrics
13import CFBag
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# despite the similary to synthetic children providers, these classes are not
23# trying to provide anything but the port number of an NSMachPort, so they need not
24# obey the interface specification for synthetic children providers
25class NSCFSet_SummaryProvider:
26	def adjust_for_architecture(self):
27		pass
28
29	def __init__(self, valobj, params):
30		logger = lldb.formatters.Logger.Logger()
31		self.valobj = valobj;
32		self.sys_params = params
33		if not(self.sys_params.types_cache.NSUInteger):
34			if self.sys_params.is_64_bit:
35				self.sys_params.types_cache.NSUInteger = self.valobj.GetType().GetBasicType(lldb.eBasicTypeUnsignedLong)
36			else:
37				self.sys_params.types_cache.NSUInteger = self.valobj.GetType().GetBasicType(lldb.eBasicTypeUnsignedInt)
38		self.update();
39
40	def update(self):
41		logger = lldb.formatters.Logger.Logger()
42		self.adjust_for_architecture();
43
44	# one pointer is the ISA
45	# then we have one other internal pointer, plus
46	# 4 bytes worth of flags. hence, these values
47	def offset(self):
48		logger = lldb.formatters.Logger.Logger()
49		if self.sys_params.is_64_bit:
50			return 20
51		else:
52			return 12
53
54	def count(self):
55		logger = lldb.formatters.Logger.Logger()
56		vcount = self.valobj.CreateChildAtOffset("count",
57							self.offset(),
58							self.sys_params.types_cache.NSUInteger)
59		return vcount.GetValueAsUnsigned(0)
60
61
62class NSSetUnknown_SummaryProvider:
63	def adjust_for_architecture(self):
64		pass
65
66	def __init__(self, valobj, params):
67		logger = lldb.formatters.Logger.Logger()
68		self.valobj = valobj;
69		self.sys_params = params
70		self.update();
71
72	def update(self):
73		logger = lldb.formatters.Logger.Logger()
74		self.adjust_for_architecture();
75
76	def count(self):
77		logger = lldb.formatters.Logger.Logger()
78		stream = lldb.SBStream()
79		self.valobj.GetExpressionPath(stream)
80		expr = "(int)[" + stream.GetData() + " count]"
81		num_children_vo = self.valobj.CreateValueFromExpression("count",expr)
82		if num_children_vo.IsValid():
83			return num_children_vo.GetValueAsUnsigned(0)
84		return '<variable is not NSSet>'
85
86class NSSetI_SummaryProvider:
87	def adjust_for_architecture(self):
88		pass
89
90	def __init__(self, valobj, params):
91		logger = lldb.formatters.Logger.Logger()
92		self.valobj = valobj;
93		self.sys_params = params
94		if not(self.sys_params.types_cache.NSUInteger):
95			if self.sys_params.is_64_bit:
96				self.sys_params.types_cache.NSUInteger = self.valobj.GetType().GetBasicType(lldb.eBasicTypeUnsignedLong)
97			else:
98				self.sys_params.types_cache.NSUInteger = self.valobj.GetType().GetBasicType(lldb.eBasicTypeUnsignedInt)
99		self.update();
100
101	def update(self):
102		logger = lldb.formatters.Logger.Logger()
103		self.adjust_for_architecture();
104
105	# we just need to skip the ISA and the count immediately follows
106	def offset(self):
107		logger = lldb.formatters.Logger.Logger()
108		return self.sys_params.pointer_size
109
110	def count(self):
111		logger = lldb.formatters.Logger.Logger()
112		num_children_vo = self.valobj.CreateChildAtOffset("count",
113							self.offset(),
114							self.sys_params.types_cache.NSUInteger)
115		value = num_children_vo.GetValueAsUnsigned(0)
116		if value != None:
117			# the MSB on immutable sets seems to be taken by some other data
118			# not sure if it is a bug or some weird sort of feature, but masking it out
119			# gets the count right (unless, of course, someone's dictionaries grow
120			#                       too large - but I have not tested this)
121			if self.sys_params.is_64_bit:
122				value = value & ~0xFF00000000000000
123			else:
124				value = value & ~0xFF000000
125		return value
126
127class NSSetM_SummaryProvider:
128	def adjust_for_architecture(self):
129		pass
130
131	def __init__(self, valobj, params):
132		logger = lldb.formatters.Logger.Logger()
133		self.valobj = valobj;
134		self.sys_params = params
135		if not(self.sys_params.types_cache.NSUInteger):
136			if self.sys_params.is_64_bit:
137				self.sys_params.types_cache.NSUInteger = self.valobj.GetType().GetBasicType(lldb.eBasicTypeUnsignedLong)
138			else:
139				self.sys_params.types_cache.NSUInteger = self.valobj.GetType().GetBasicType(lldb.eBasicTypeUnsignedInt)
140		self.update();
141
142	def update(self):
143		logger = lldb.formatters.Logger.Logger()
144		self.adjust_for_architecture();
145
146	# we just need to skip the ISA and the count immediately follows
147	def offset(self):
148		logger = lldb.formatters.Logger.Logger()
149		return self.sys_params.pointer_size
150
151	def count(self):
152		logger = lldb.formatters.Logger.Logger()
153		num_children_vo = self.valobj.CreateChildAtOffset("count",
154							self.offset(),
155							self.sys_params.types_cache.NSUInteger)
156		return num_children_vo.GetValueAsUnsigned(0)
157
158
159class NSCountedSet_SummaryProvider:
160	def adjust_for_architecture(self):
161		pass
162
163	def __init__(self, valobj, params):
164		logger = lldb.formatters.Logger.Logger()
165		self.valobj = valobj;
166		self.sys_params = params
167		if not (self.sys_params.types_cache.voidptr):
168			self.sys_params.types_cache.voidptr = self.valobj.GetType().GetBasicType(lldb.eBasicTypeVoid).GetPointerType()
169		self.update();
170
171	def update(self):
172		logger = lldb.formatters.Logger.Logger()
173		self.adjust_for_architecture();
174
175	# an NSCountedSet is implemented using a CFBag whose pointer just follows the ISA
176	def offset(self):
177		logger = lldb.formatters.Logger.Logger()
178		return self.sys_params.pointer_size
179
180	def count(self):
181		logger = lldb.formatters.Logger.Logger()
182		cfbag_vo = self.valobj.CreateChildAtOffset("bag_impl",
183							self.offset(),
184							self.sys_params.types_cache.voidptr)
185		return CFBag.CFBagRef_SummaryProvider(cfbag_vo,self.sys_params).length()
186
187
188def GetSummary_Impl(valobj):
189	logger = lldb.formatters.Logger.Logger()
190	global statistics
191	class_data,wrapper =lldb.runtime.objc.objc_runtime.Utilities.prepare_class_detection(valobj,statistics)
192	if wrapper:
193		return wrapper
194
195	name_string = class_data.class_name()
196	logger >> "class name is: " + str(name_string)
197
198	if name_string == '__NSCFSet':
199		wrapper = NSCFSet_SummaryProvider(valobj, class_data.sys_params)
200		statistics.metric_hit('code_notrun',valobj)
201	elif name_string == '__NSSetI':
202		wrapper = NSSetI_SummaryProvider(valobj, class_data.sys_params)
203		statistics.metric_hit('code_notrun',valobj)
204	elif name_string == '__NSSetM':
205		wrapper = NSSetM_SummaryProvider(valobj, class_data.sys_params)
206		statistics.metric_hit('code_notrun',valobj)
207	elif name_string == 'NSCountedSet':
208		wrapper = NSCountedSet_SummaryProvider(valobj, class_data.sys_params)
209		statistics.metric_hit('code_notrun',valobj)
210	else:
211		wrapper = NSSetUnknown_SummaryProvider(valobj, class_data.sys_params)
212		statistics.metric_hit('unknown_class',valobj.GetName() + " seen as " + name_string)
213	return wrapper;
214
215
216def NSSet_SummaryProvider (valobj,dict):
217	logger = lldb.formatters.Logger.Logger()
218	provider = GetSummary_Impl(valobj);
219	if provider != None:
220		try:
221			summary = provider.count();
222		except:
223			summary = None
224		if summary == None:
225			summary = '<variable is not NSSet>'
226		if isinstance(summary, basestring):
227			return summary
228		else:
229			summary = str(summary) + (' objects' if summary != 1 else ' object')
230		return summary
231	return 'Summary Unavailable'
232
233def NSSet_SummaryProvider2 (valobj,dict):
234	logger = lldb.formatters.Logger.Logger()
235	provider = GetSummary_Impl(valobj);
236	if provider != None:
237		if isinstance(provider,lldb.runtime.objc.objc_runtime.SpecialSituation_Description):
238			return provider.message()
239		try:
240			summary = provider.count();
241		except:
242			summary = None
243		logger >> "got summary " + str(summary)
244		# for some reason, one needs to clear some bits for the count returned
245		# to be correct when using directly CF*SetRef as compared to NS*Set
246		# this only happens on 64bit, and the bit mask was derived through
247		# experimentation (if counts start looking weird, then most probably
248		#                  the mask needs to be changed)
249		if summary == None:
250			summary = '<variable is not CFSet>'
251		if isinstance(summary, basestring):
252			return summary
253		else:
254			if provider.sys_params.is_64_bit:
255				summary = summary & ~0x1fff000000000000
256		 	summary = '@"' + str(summary) + (' values"' if summary != 1 else ' value"')
257		return summary
258	return 'Summary Unavailable'
259
260
261def __lldb_init_module(debugger,dict):
262	debugger.HandleCommand("type summary add -F NSSet.NSSet_SummaryProvider NSSet")
263	debugger.HandleCommand("type summary add -F NSSet.NSSet_SummaryProvider2 CFSetRef CFMutableSetRef")
264