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