xref: /llvm-project/lldb/examples/summaries/cocoa/objc_runtime.py (revision 2238dcc39358353cac21df75c3c3286ab20b8f53)
1"""
2Objective-C runtime wrapper for use by LLDB Python 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"""
8import lldb
9import lldb.formatters.cache
10import lldb.formatters.attrib_fromdict
11import functools
12import lldb.formatters.Logger
13
14
15class Utilities:
16    @staticmethod
17    def read_ascii(process, pointer, max_len=128):
18        logger = lldb.formatters.Logger.Logger()
19        error = lldb.SBError()
20        content = None
21        try:
22            content = process.ReadCStringFromMemory(pointer, max_len, error)
23        except:
24            pass
25        if content is None or len(content) == 0 or error.fail:
26            return None
27        return content
28
29    @staticmethod
30    def is_valid_pointer(pointer, pointer_size, allow_tagged=0, allow_NULL=0):
31        logger = lldb.formatters.Logger.Logger()
32        if pointer is None:
33            return 0
34        if pointer == 0:
35            return allow_NULL
36        if allow_tagged and (pointer % 2) == 1:
37            return 1
38        return (pointer % pointer_size) == 0
39
40    # Objective-C runtime has a rule that pointers in a class_t will only have bits 0 thru 46 set
41    # so if any pointer has bits 47 thru 63 high we know that this is not a
42    # valid isa
43    @staticmethod
44    def is_allowed_pointer(pointer):
45        logger = lldb.formatters.Logger.Logger()
46        if pointer is None:
47            return 0
48        return (pointer & 0xFFFF800000000000) == 0
49
50    @staticmethod
51    def read_child_of(valobj, offset, type):
52        logger = lldb.formatters.Logger.Logger()
53        if offset == 0 and type.GetByteSize() == valobj.GetByteSize():
54            return valobj.GetValueAsUnsigned()
55        child = valobj.CreateChildAtOffset("childUNK", offset, type)
56        if child is None or child.IsValid() == 0:
57            return None
58        return child.GetValueAsUnsigned()
59
60    @staticmethod
61    def is_valid_identifier(name):
62        logger = lldb.formatters.Logger.Logger()
63        if name is None:
64            return None
65        if len(name) == 0:
66            return None
67        # technically, the ObjC runtime does not enforce any rules about what name a class can have
68        # in practice, the commonly used byte values for a class name are the letters, digits and some
69        # symbols: $, %, -, _, .
70        # WARNING: this means that you cannot use this runtime implementation if you need to deal
71        # with class names that use anything but what is allowed here
72        ok_values = dict.fromkeys(
73            "$%_.-ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz1234567890"
74        )
75        return all(c in ok_values for c in name)
76
77    @staticmethod
78    def check_is_osx_lion(target):
79        logger = lldb.formatters.Logger.Logger()
80        # assume the only thing that has a Foundation.framework is a Mac
81        # assume anything < Lion does not even exist
82        try:
83            mod = target.module["Foundation"]
84        except:
85            mod = None
86        if mod is None or mod.IsValid() == 0:
87            return None
88        ver = mod.GetVersion()
89        if ver is None or ver == []:
90            return None
91        return ver[0] < 900
92
93    # a utility method that factors out code common to almost all the formatters
94    # takes in an SBValue and a metrics object
95    # returns a class_data and a wrapper (or None, if the runtime alone can't
96    # decide on a wrapper)
97    @staticmethod
98    def prepare_class_detection(valobj, statistics):
99        logger = lldb.formatters.Logger.Logger()
100        class_data = ObjCRuntime(valobj)
101        if class_data.is_valid() == 0:
102            statistics.metric_hit("invalid_pointer", valobj)
103            wrapper = InvalidPointer_Description(valobj.GetValueAsUnsigned(0) == 0)
104            return class_data, wrapper
105        class_data = class_data.read_class_data()
106        if class_data.is_valid() == 0:
107            statistics.metric_hit("invalid_isa", valobj)
108            wrapper = InvalidISA_Description()
109            return class_data, wrapper
110        if class_data.is_kvo():
111            class_data = class_data.get_superclass()
112        if class_data.class_name() == "_NSZombie_OriginalClass":
113            wrapper = ThisIsZombie_Description()
114            return class_data, wrapper
115        return class_data, None
116
117
118class RoT_Data:
119    def __init__(self, rot_pointer, params):
120        logger = lldb.formatters.Logger.Logger()
121        if Utilities.is_valid_pointer(
122            rot_pointer.GetValueAsUnsigned(), params.pointer_size, allow_tagged=0
123        ):
124            self.sys_params = params
125            self.valobj = rot_pointer
126            # self.flags = Utilities.read_child_of(self.valobj,0,self.sys_params.uint32_t)
127            # self.instanceStart = Utilities.read_child_of(self.valobj,4,self.sys_params.uint32_t)
128            self.instanceSize = None  # lazy fetching
129            offset = 24 if self.sys_params.is_64_bit else 16
130            # self.ivarLayoutPtr = Utilities.read_child_of(self.valobj,offset,self.sys_params.addr_ptr_type)
131            self.namePointer = Utilities.read_child_of(
132                self.valobj, offset, self.sys_params.types_cache.addr_ptr_type
133            )
134            self.valid = 1  # self.check_valid()
135        else:
136            logger >> "Marking as invalid - rot is invalid"
137            self.valid = 0
138        if self.valid:
139            self.name = Utilities.read_ascii(
140                self.valobj.GetTarget().GetProcess(), self.namePointer
141            )
142            if not (Utilities.is_valid_identifier(self.name)):
143                logger >> "Marking as invalid - name is invalid"
144                self.valid = 0
145
146    # perform sanity checks on the contents of this class_ro_t
147    def check_valid(self):
148        self.valid = 1
149        # misaligned pointers seem to be possible for this field
150        # if not(Utilities.is_valid_pointer(self.namePointer,self.sys_params.pointer_size,allow_tagged=0)):
151        # 	self.valid = 0
152        # 	pass
153
154    def __str__(self):
155        logger = lldb.formatters.Logger.Logger()
156        return (
157            "instanceSize = "
158            + hex(self.instance_size())
159            + "\n"
160            + "namePointer = "
161            + hex(self.namePointer)
162            + " --> "
163            + self.name
164        )
165
166    def is_valid(self):
167        return self.valid
168
169    def instance_size(self, align=0):
170        logger = lldb.formatters.Logger.Logger()
171        if self.is_valid() == 0:
172            return None
173        if self.instanceSize is None:
174            self.instanceSize = Utilities.read_child_of(
175                self.valobj, 8, self.sys_params.types_cache.uint32_t
176            )
177        if align:
178            unalign = self.instance_size(0)
179            if self.sys_params.is_64_bit:
180                return ((unalign + 7) & ~7) % 0x100000000
181            else:
182                return ((unalign + 3) & ~3) % 0x100000000
183        else:
184            return self.instanceSize
185
186
187class RwT_Data:
188    def __init__(self, rwt_pointer, params):
189        logger = lldb.formatters.Logger.Logger()
190        if Utilities.is_valid_pointer(
191            rwt_pointer.GetValueAsUnsigned(), params.pointer_size, allow_tagged=0
192        ):
193            self.sys_params = params
194            self.valobj = rwt_pointer
195            # self.flags = Utilities.read_child_of(self.valobj,0,self.sys_params.uint32_t)
196            # self.version = Utilities.read_child_of(self.valobj,4,self.sys_params.uint32_t)
197            self.roPointer = Utilities.read_child_of(
198                self.valobj, 8, self.sys_params.types_cache.addr_ptr_type
199            )
200            self.check_valid()
201        else:
202            logger >> "Marking as invalid - rwt is invald"
203            self.valid = 0
204        if self.valid:
205            self.rot = self.valobj.CreateValueFromData(
206                "rot",
207                lldb.SBData.CreateDataFromUInt64Array(
208                    self.sys_params.endianness,
209                    self.sys_params.pointer_size,
210                    [self.roPointer],
211                ),
212                self.sys_params.types_cache.addr_ptr_type,
213            )
214            # 			self.rot = self.valobj.CreateValueFromAddress("rot",self.roPointer,self.sys_params.types_cache.addr_ptr_type).AddressOf()
215            self.data = RoT_Data(self.rot, self.sys_params)
216
217    # perform sanity checks on the contents of this class_rw_t
218    def check_valid(self):
219        logger = lldb.formatters.Logger.Logger()
220        self.valid = 1
221        if not (
222            Utilities.is_valid_pointer(
223                self.roPointer, self.sys_params.pointer_size, allow_tagged=0
224            )
225        ):
226            logger >> "Marking as invalid - ropointer is invalid"
227            self.valid = 0
228
229    def __str__(self):
230        logger = lldb.formatters.Logger.Logger()
231        return "roPointer = " + hex(self.roPointer)
232
233    def is_valid(self):
234        logger = lldb.formatters.Logger.Logger()
235        if self.valid:
236            return self.data.is_valid()
237        return 0
238
239
240class Class_Data_V2:
241    def __init__(self, isa_pointer, params):
242        logger = lldb.formatters.Logger.Logger()
243        if (isa_pointer is not None) and (
244            Utilities.is_valid_pointer(
245                isa_pointer.GetValueAsUnsigned(), params.pointer_size, allow_tagged=0
246            )
247        ):
248            self.sys_params = params
249            self.valobj = isa_pointer
250            self.check_valid()
251        else:
252            logger >> "Marking as invalid - isa is invalid or None"
253            self.valid = 0
254        if self.valid:
255            self.rwt = self.valobj.CreateValueFromData(
256                "rwt",
257                lldb.SBData.CreateDataFromUInt64Array(
258                    self.sys_params.endianness,
259                    self.sys_params.pointer_size,
260                    [self.dataPointer],
261                ),
262                self.sys_params.types_cache.addr_ptr_type,
263            )
264            # 			self.rwt = self.valobj.CreateValueFromAddress("rwt",self.dataPointer,self.sys_params.types_cache.addr_ptr_type).AddressOf()
265            self.data = RwT_Data(self.rwt, self.sys_params)
266
267    # perform sanity checks on the contents of this class_t
268    # this call tries to minimize the amount of data fetched- as soon as we have "proven"
269    # that we have an invalid object, we stop reading
270    def check_valid(self):
271        logger = lldb.formatters.Logger.Logger()
272        self.valid = 1
273
274        self.isaPointer = Utilities.read_child_of(
275            self.valobj, 0, self.sys_params.types_cache.addr_ptr_type
276        )
277        if not (
278            Utilities.is_valid_pointer(
279                self.isaPointer, self.sys_params.pointer_size, allow_tagged=0
280            )
281        ):
282            logger >> "Marking as invalid - isaPointer is invalid"
283            self.valid = 0
284            return
285        if not (Utilities.is_allowed_pointer(self.isaPointer)):
286            logger >> "Marking as invalid - isaPointer is not allowed"
287            self.valid = 0
288            return
289
290        self.cachePointer = Utilities.read_child_of(
291            self.valobj,
292            2 * self.sys_params.pointer_size,
293            self.sys_params.types_cache.addr_ptr_type,
294        )
295        if not (
296            Utilities.is_valid_pointer(
297                self.cachePointer, self.sys_params.pointer_size, allow_tagged=0
298            )
299        ):
300            logger >> "Marking as invalid - cachePointer is invalid"
301            self.valid = 0
302            return
303        if not (Utilities.is_allowed_pointer(self.cachePointer)):
304            logger >> "Marking as invalid - cachePointer is not allowed"
305            self.valid = 0
306            return
307        self.dataPointer = Utilities.read_child_of(
308            self.valobj,
309            4 * self.sys_params.pointer_size,
310            self.sys_params.types_cache.addr_ptr_type,
311        )
312        if not (
313            Utilities.is_valid_pointer(
314                self.dataPointer, self.sys_params.pointer_size, allow_tagged=0
315            )
316        ):
317            logger >> "Marking as invalid - dataPointer is invalid"
318            self.valid = 0
319            return
320        if not (Utilities.is_allowed_pointer(self.dataPointer)):
321            logger >> "Marking as invalid - dataPointer is not allowed"
322            self.valid = 0
323            return
324
325        self.superclassIsaPointer = Utilities.read_child_of(
326            self.valobj,
327            1 * self.sys_params.pointer_size,
328            self.sys_params.types_cache.addr_ptr_type,
329        )
330        if not (
331            Utilities.is_valid_pointer(
332                self.superclassIsaPointer,
333                self.sys_params.pointer_size,
334                allow_tagged=0,
335                allow_NULL=1,
336            )
337        ):
338            logger >> "Marking as invalid - superclassIsa is invalid"
339            self.valid = 0
340            return
341        if not (Utilities.is_allowed_pointer(self.superclassIsaPointer)):
342            logger >> "Marking as invalid - superclassIsa is not allowed"
343            self.valid = 0
344            return
345
346    # in general, KVO is implemented by transparently subclassing
347    # however, there could be exceptions where a class does something else
348    # internally to implement the feature - this method will have no clue that a class
349    # has been KVO'ed unless the standard implementation technique is used
350    def is_kvo(self):
351        logger = lldb.formatters.Logger.Logger()
352        if self.is_valid():
353            if self.class_name().startswith("NSKVONotifying_"):
354                return 1
355        return 0
356
357    # some CF classes have a valid ObjC isa in their CFRuntimeBase
358    # but instead of being class-specific this isa points to a match-'em-all class
359    # which is __NSCFType (the versions without __ also exists and we are matching to it
360    #                      just to be on the safe side)
361    def is_cftype(self):
362        logger = lldb.formatters.Logger.Logger()
363        if self.is_valid():
364            return self.class_name() == "__NSCFType" or self.class_name() == "NSCFType"
365
366    def get_superclass(self):
367        logger = lldb.formatters.Logger.Logger()
368        if self.is_valid():
369            parent_isa_pointer = self.valobj.CreateChildAtOffset(
370                "parent_isa",
371                self.sys_params.pointer_size,
372                self.sys_params.addr_ptr_type,
373            )
374            return Class_Data_V2(parent_isa_pointer, self.sys_params)
375        else:
376            return None
377
378    def class_name(self):
379        logger = lldb.formatters.Logger.Logger()
380        if self.is_valid():
381            return self.data.data.name
382        else:
383            return None
384
385    def is_valid(self):
386        logger = lldb.formatters.Logger.Logger()
387        if self.valid:
388            return self.data.is_valid()
389        return 0
390
391    def __str__(self):
392        logger = lldb.formatters.Logger.Logger()
393        return (
394            "isaPointer = "
395            + hex(self.isaPointer)
396            + "\n"
397            + "superclassIsaPointer = "
398            + hex(self.superclassIsaPointer)
399            + "\n"
400            + "cachePointer = "
401            + hex(self.cachePointer)
402            + "\n"
403            + "data = "
404            + hex(self.dataPointer)
405        )
406
407    def is_tagged(self):
408        return 0
409
410    def instance_size(self, align=0):
411        logger = lldb.formatters.Logger.Logger()
412        if self.is_valid() == 0:
413            return None
414        return self.rwt.rot.instance_size(align)
415
416
417# runtime v1 is much less intricate than v2 and stores relevant
418# information directly in the class_t object
419
420
421class Class_Data_V1:
422    def __init__(self, isa_pointer, params):
423        logger = lldb.formatters.Logger.Logger()
424        if (isa_pointer is not None) and (
425            Utilities.is_valid_pointer(
426                isa_pointer.GetValueAsUnsigned(), params.pointer_size, allow_tagged=0
427            )
428        ):
429            self.valid = 1
430            self.sys_params = params
431            self.valobj = isa_pointer
432            self.check_valid()
433        else:
434            logger >> "Marking as invalid - isaPointer is invalid or None"
435            self.valid = 0
436        if self.valid:
437            self.name = Utilities.read_ascii(
438                self.valobj.GetTarget().GetProcess(), self.namePointer
439            )
440            if not (Utilities.is_valid_identifier(self.name)):
441                logger >> "Marking as invalid - name is not valid"
442                self.valid = 0
443
444    # perform sanity checks on the contents of this class_t
445    def check_valid(self):
446        logger = lldb.formatters.Logger.Logger()
447        self.valid = 1
448
449        self.isaPointer = Utilities.read_child_of(
450            self.valobj, 0, self.sys_params.types_cache.addr_ptr_type
451        )
452        if not (
453            Utilities.is_valid_pointer(
454                self.isaPointer, self.sys_params.pointer_size, allow_tagged=0
455            )
456        ):
457            logger >> "Marking as invalid - isaPointer is invalid"
458            self.valid = 0
459            return
460
461        self.superclassIsaPointer = Utilities.read_child_of(
462            self.valobj,
463            1 * self.sys_params.pointer_size,
464            self.sys_params.types_cache.addr_ptr_type,
465        )
466        if not (
467            Utilities.is_valid_pointer(
468                self.superclassIsaPointer,
469                self.sys_params.pointer_size,
470                allow_tagged=0,
471                allow_NULL=1,
472            )
473        ):
474            logger >> "Marking as invalid - superclassIsa is invalid"
475            self.valid = 0
476            return
477
478        self.namePointer = Utilities.read_child_of(
479            self.valobj,
480            2 * self.sys_params.pointer_size,
481            self.sys_params.types_cache.addr_ptr_type,
482        )
483        # if not(Utilities.is_valid_pointer(self.namePointer,self.sys_params.pointer_size,allow_tagged=0,allow_NULL=0)):
484        # 	self.valid = 0
485        # 	return
486
487    # in general, KVO is implemented by transparently subclassing
488    # however, there could be exceptions where a class does something else
489    # internally to implement the feature - this method will have no clue that a class
490    # has been KVO'ed unless the standard implementation technique is used
491    def is_kvo(self):
492        logger = lldb.formatters.Logger.Logger()
493        if self.is_valid():
494            if self.class_name().startswith("NSKVONotifying_"):
495                return 1
496        return 0
497
498    # some CF classes have a valid ObjC isa in their CFRuntimeBase
499    # but instead of being class-specific this isa points to a match-'em-all class
500    # which is __NSCFType (the versions without __ also exists and we are matching to it
501    #                      just to be on the safe side)
502    def is_cftype(self):
503        logger = lldb.formatters.Logger.Logger()
504        if self.is_valid():
505            return self.class_name() == "__NSCFType" or self.class_name() == "NSCFType"
506
507    def get_superclass(self):
508        logger = lldb.formatters.Logger.Logger()
509        if self.is_valid():
510            parent_isa_pointer = self.valobj.CreateChildAtOffset(
511                "parent_isa",
512                self.sys_params.pointer_size,
513                self.sys_params.addr_ptr_type,
514            )
515            return Class_Data_V1(parent_isa_pointer, self.sys_params)
516        else:
517            return None
518
519    def class_name(self):
520        logger = lldb.formatters.Logger.Logger()
521        if self.is_valid():
522            return self.name
523        else:
524            return None
525
526    def is_valid(self):
527        return self.valid
528
529    def __str__(self):
530        logger = lldb.formatters.Logger.Logger()
531        return (
532            "isaPointer = "
533            + hex(self.isaPointer)
534            + "\n"
535            + "superclassIsaPointer = "
536            + hex(self.superclassIsaPointer)
537            + "\n"
538            + "namePointer = "
539            + hex(self.namePointer)
540            + " --> "
541            + self.name
542            + "instanceSize = "
543            + hex(self.instanceSize())
544            + "\n"
545        )
546
547    def is_tagged(self):
548        return 0
549
550    def instance_size(self, align=0):
551        logger = lldb.formatters.Logger.Logger()
552        if self.is_valid() == 0:
553            return None
554        if self.instanceSize is None:
555            self.instanceSize = Utilities.read_child_of(
556                self.valobj,
557                5 * self.sys_params.pointer_size,
558                self.sys_params.types_cache.addr_ptr_type,
559            )
560        if align:
561            unalign = self.instance_size(0)
562            if self.sys_params.is_64_bit:
563                return ((unalign + 7) & ~7) % 0x100000000
564            else:
565                return ((unalign + 3) & ~3) % 0x100000000
566        else:
567            return self.instanceSize
568
569
570# these are the only tagged pointers values for current versions
571# of OSX - they might change in future OS releases, and no-one is
572# advised to rely on these values, or any of the bitmasking formulas
573# in TaggedClass_Data. doing otherwise is at your own risk
574TaggedClass_Values_Lion = {
575    1: "NSNumber",
576    5: "NSManagedObject",
577    6: "NSDate",
578    7: "NSDateTS",
579}
580TaggedClass_Values_NMOS = {
581    0: "NSAtom",
582    3: "NSNumber",
583    4: "NSDateTS",
584    5: "NSManagedObject",
585    6: "NSDate",
586}
587
588
589class TaggedClass_Data:
590    def __init__(self, pointer, params):
591        logger = lldb.formatters.Logger.Logger()
592        global TaggedClass_Values_Lion, TaggedClass_Values_NMOS
593        self.valid = 1
594        self.name = None
595        self.sys_params = params
596        self.valobj = pointer
597        self.val = (pointer & ~0x0000000000000000FF) >> 8
598        self.class_bits = (pointer & 0xE) >> 1
599        self.i_bits = (pointer & 0xF0) >> 4
600
601        if self.sys_params.is_lion:
602            if self.class_bits in TaggedClass_Values_Lion:
603                self.name = TaggedClass_Values_Lion[self.class_bits]
604            else:
605                logger >> "Marking as invalid - not a good tagged pointer for Lion"
606                self.valid = 0
607        else:
608            if self.class_bits in TaggedClass_Values_NMOS:
609                self.name = TaggedClass_Values_NMOS[self.class_bits]
610            else:
611                logger >> "Marking as invalid - not a good tagged pointer for NMOS"
612                self.valid = 0
613
614    def is_valid(self):
615        return self.valid
616
617    def class_name(self):
618        logger = lldb.formatters.Logger.Logger()
619        if self.is_valid():
620            return self.name
621        else:
622            return 0
623
624    def value(self):
625        return self.val if self.is_valid() else None
626
627    def info_bits(self):
628        return self.i_bits if self.is_valid() else None
629
630    def is_kvo(self):
631        return 0
632
633    def is_cftype(self):
634        return 0
635
636    # we would need to go around looking for the superclass or ask the runtime
637    # for now, we seem not to require support for this operation so we will merrily
638    # pretend to be at a root point in the hierarchy
639    def get_superclass(self):
640        return None
641
642    # anything that is handled here is tagged
643    def is_tagged(self):
644        return 1
645
646    # it seems reasonable to say that a tagged pointer is the size of a pointer
647    def instance_size(self, align=0):
648        logger = lldb.formatters.Logger.Logger()
649        if self.is_valid() == 0:
650            return None
651        return self.sys_params.pointer_size
652
653
654class InvalidClass_Data:
655    def __init__(self):
656        pass
657
658    def is_valid(self):
659        return 0
660
661
662class Version:
663    def __init__(self, major, minor, release, build_string):
664        self._major = major
665        self._minor = minor
666        self._release = release
667        self._build_string = build_string
668
669    def get_major(self):
670        return self._major
671
672    def get_minor(self):
673        return self._minor
674
675    def get_release(self):
676        return self._release
677
678    def get_build_string(self):
679        return self._build_string
680
681    major = property(get_major, None)
682    minor = property(get_minor, None)
683    release = property(get_release, None)
684    build_string = property(get_build_string, None)
685
686    def __lt__(self, other):
687        if self.major < other.major:
688            return 1
689        if self.minor < other.minor:
690            return 1
691        if self.release < other.release:
692            return 1
693        # build strings are not compared since they are heavily platform-dependent and might not always
694        # be available
695        return 0
696
697    def __eq__(self, other):
698        return (
699            (self.major == other.major)
700            and (self.minor == other.minor)
701            and (self.release == other.release)
702            and (self.build_string == other.build_string)
703        )
704
705    # Python 2.6 doesn't have functools.total_ordering, so we have to implement
706    # other comparators
707    def __gt__(self, other):
708        return other < self
709
710    def __le__(self, other):
711        return not other < self
712
713    def __ge__(self, other):
714        return not self < other
715
716
717runtime_version = lldb.formatters.cache.Cache()
718os_version = lldb.formatters.cache.Cache()
719types_caches = lldb.formatters.cache.Cache()
720isa_caches = lldb.formatters.cache.Cache()
721
722
723class SystemParameters:
724    def __init__(self, valobj):
725        logger = lldb.formatters.Logger.Logger()
726        self.adjust_for_architecture(valobj)
727        self.adjust_for_process(valobj)
728
729    def adjust_for_process(self, valobj):
730        logger = lldb.formatters.Logger.Logger()
731        global runtime_version
732        global os_version
733        global types_caches
734        global isa_caches
735
736        process = valobj.GetTarget().GetProcess()
737        # using the unique ID for added guarantees (see svn revision 172628 for
738        # further details)
739        self.pid = process.GetUniqueID()
740
741        if runtime_version.look_for_key(self.pid):
742            self.runtime_version = runtime_version.get_value(self.pid)
743        else:
744            self.runtime_version = ObjCRuntime.runtime_version(process)
745            runtime_version.add_item(self.pid, self.runtime_version)
746
747        if os_version.look_for_key(self.pid):
748            self.is_lion = os_version.get_value(self.pid)
749        else:
750            self.is_lion = Utilities.check_is_osx_lion(valobj.GetTarget())
751            os_version.add_item(self.pid, self.is_lion)
752
753        if types_caches.look_for_key(self.pid):
754            self.types_cache = types_caches.get_value(self.pid)
755        else:
756            self.types_cache = lldb.formatters.attrib_fromdict.AttributesDictionary(
757                allow_reset=0
758            )
759            self.types_cache.addr_type = valobj.GetType().GetBasicType(
760                lldb.eBasicTypeUnsignedLong
761            )
762            self.types_cache.addr_ptr_type = self.types_cache.addr_type.GetPointerType()
763            self.types_cache.uint32_t = valobj.GetType().GetBasicType(
764                lldb.eBasicTypeUnsignedInt
765            )
766            types_caches.add_item(self.pid, self.types_cache)
767
768        if isa_caches.look_for_key(self.pid):
769            self.isa_cache = isa_caches.get_value(self.pid)
770        else:
771            self.isa_cache = lldb.formatters.cache.Cache()
772            isa_caches.add_item(self.pid, self.isa_cache)
773
774    def adjust_for_architecture(self, valobj):
775        process = valobj.GetTarget().GetProcess()
776        self.pointer_size = process.GetAddressByteSize()
777        self.is_64_bit = self.pointer_size == 8
778        self.endianness = process.GetByteOrder()
779        self.is_little = self.endianness == lldb.eByteOrderLittle
780        self.cfruntime_size = 16 if self.is_64_bit else 8
781
782    # a simple helper function that makes it more explicit that one is calculating
783    # an offset that is made up of X pointers and Y bytes of additional data
784    # taking into account pointer size - if you know there is going to be some padding
785    # you can pass that in and it will be taken into account (since padding may be different between
786    # 32 and 64 bit versions, you can pass padding value for both, the right
787    # one will be used)
788    def calculate_offset(self, num_pointers=0, bytes_count=0, padding32=0, padding64=0):
789        value = bytes_count + num_pointers * self.pointer_size
790        return value + padding64 if self.is_64_bit else value + padding32
791
792
793class ObjCRuntime:
794    # the ObjC runtime has no explicit "version" field that we can use
795    # instead, we discriminate v1 from v2 by looking for the presence
796    # of a well-known section only present in v1
797    @staticmethod
798    def runtime_version(process):
799        logger = lldb.formatters.Logger.Logger()
800        if process.IsValid() == 0:
801            logger >> "No process - bailing out"
802            return None
803        target = process.GetTarget()
804        num_modules = target.GetNumModules()
805        module_objc = None
806        for idx in range(num_modules):
807            module = target.GetModuleAtIndex(idx)
808            if module.GetFileSpec().GetFilename() == "libobjc.A.dylib":
809                module_objc = module
810                break
811        if module_objc is None or module_objc.IsValid() == 0:
812            logger >> "no libobjc - bailing out"
813            return None
814        num_sections = module.GetNumSections()
815        section_objc = None
816        for idx in range(num_sections):
817            section = module.GetSectionAtIndex(idx)
818            if section.GetName() == "__OBJC":
819                section_objc = section
820                break
821        if section_objc is not None and section_objc.IsValid():
822            logger >> "found __OBJC: v1"
823            return 1
824        logger >> "no __OBJC: v2"
825        return 2
826
827    @staticmethod
828    def runtime_from_isa(isa):
829        logger = lldb.formatters.Logger.Logger()
830        runtime = ObjCRuntime(isa)
831        runtime.isa = isa
832        return runtime
833
834    def __init__(self, valobj):
835        logger = lldb.formatters.Logger.Logger()
836        self.valobj = valobj
837        self.adjust_for_architecture()
838        self.sys_params = SystemParameters(self.valobj)
839        self.unsigned_value = self.valobj.GetValueAsUnsigned()
840        self.isa_value = None
841
842    def adjust_for_architecture(self):
843        pass
844
845    # an ObjC pointer can either be tagged or must be aligned
846    def is_tagged(self):
847        logger = lldb.formatters.Logger.Logger()
848        if self.valobj is None:
849            return 0
850        return Utilities.is_valid_pointer(
851            self.unsigned_value, self.sys_params.pointer_size, allow_tagged=1
852        ) and not (
853            Utilities.is_valid_pointer(
854                self.unsigned_value, self.sys_params.pointer_size, allow_tagged=0
855            )
856        )
857
858    def is_valid(self):
859        logger = lldb.formatters.Logger.Logger()
860        if self.valobj is None:
861            return 0
862        if self.valobj.IsInScope() == 0:
863            return 0
864        return Utilities.is_valid_pointer(
865            self.unsigned_value, self.sys_params.pointer_size, allow_tagged=1
866        )
867
868    def is_nil(self):
869        return self.unsigned_value == 0
870
871    def read_isa(self):
872        logger = lldb.formatters.Logger.Logger()
873        if self.isa_value is not None:
874            logger >> "using cached isa"
875            return self.isa_value
876        self.isa_pointer = self.valobj.CreateChildAtOffset(
877            "cfisa", 0, self.sys_params.types_cache.addr_ptr_type
878        )
879        if self.isa_pointer is None or self.isa_pointer.IsValid() == 0:
880            logger >> "invalid isa - bailing out"
881            return None
882        self.isa_value = self.isa_pointer.GetValueAsUnsigned(1)
883        if self.isa_value == 1:
884            logger >> "invalid isa value - bailing out"
885            return None
886        return Ellipsis
887
888    def read_class_data(self):
889        logger = lldb.formatters.Logger.Logger()
890        global isa_cache
891        if self.is_tagged():
892            # tagged pointers only exist in ObjC v2
893            if self.sys_params.runtime_version == 2:
894                logger >> "on v2 and tagged - maybe"
895                # not every odd-valued pointer is actually tagged. most are just plain wrong
896                # we could try and predetect this before even creating a TaggedClass_Data object
897                # but unless performance requires it, this seems a cleaner way
898                # to tackle the task
899                tentative_tagged = TaggedClass_Data(
900                    self.unsigned_value, self.sys_params
901                )
902                if tentative_tagged.is_valid():
903                    logger >> "truly tagged"
904                    return tentative_tagged
905                else:
906                    logger >> "not tagged - error"
907                    return InvalidClass_Data()
908            else:
909                logger >> "on v1 and tagged - error"
910                return InvalidClass_Data()
911        if self.is_valid() == 0 or self.read_isa() is None:
912            return InvalidClass_Data()
913        data = self.sys_params.isa_cache.get_value(self.isa_value, default=None)
914        if data is not None:
915            return data
916        if self.sys_params.runtime_version == 2:
917            data = Class_Data_V2(self.isa_pointer, self.sys_params)
918        else:
919            data = Class_Data_V1(self.isa_pointer, self.sys_params)
920        if data is None:
921            return InvalidClass_Data()
922        if data.is_valid():
923            self.sys_params.isa_cache.add_item(self.isa_value, data, ok_to_replace=1)
924        return data
925
926
927# these classes below can be used by the data formatters to provide a
928# consistent message that describes a given runtime-generated situation
929
930
931class SpecialSituation_Description:
932    def message(self):
933        return ""
934
935
936class InvalidPointer_Description(SpecialSituation_Description):
937    def __init__(self, nil):
938        self.is_nil = nil
939
940    def message(self):
941        if self.is_nil:
942            return '@"<nil>"'
943        else:
944            return "<invalid pointer>"
945
946
947class InvalidISA_Description(SpecialSituation_Description):
948    def __init__(self):
949        pass
950
951    def message(self):
952        return "<not an Objective-C object>"
953
954
955class ThisIsZombie_Description(SpecialSituation_Description):
956    def message(self):
957        return "<freed object>"
958