-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathobjc-runtime-old.mm
3336 lines (2782 loc) · 110 KB
/
objc-runtime-old.mm
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
/*
* Copyright (c) 1999-2007 Apple Inc. All Rights Reserved.
*
* @APPLE_LICENSE_HEADER_START@
*
* This file contains Original Code and/or Modifications of Original Code
* as defined in and that are subject to the Apple Public Source License
* Version 2.0 (the 'License'). You may not use this file except in
* compliance with the License. Please obtain a copy of the License at
* http://www.opensource.apple.com/apsl/ and read it before using this
* file.
*
* The Original Code and all software distributed under the License are
* distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
* EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
* INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
* Please see the License for the specific language governing rights and
* limitations under the License.
*
* @APPLE_LICENSE_HEADER_END@
*/
/***********************************************************************
* objc-runtime-old.m
* Support for old-ABI classes and images.
**********************************************************************/
/***********************************************************************
* Class loading and connecting (GrP 2004-2-11)
*
* When images are loaded (during program startup or otherwise), the
* runtime needs to load classes and categories from the images, connect
* classes to superclasses and categories to parent classes, and call
* +load methods.
*
* The Objective-C runtime can cope with classes arriving in any order.
* That is, a class may be discovered by the runtime before some
* superclass is known. To handle out-of-order class loads, the
* runtime uses a "pending class" system.
*
* (Historical note)
* Panther and earlier: many classes arrived out-of-order because of
* the poorly-ordered callback from dyld. However, the runtime's
* pending mechanism only handled "missing superclass" and not
* "present superclass but missing higher class". See Radar #3225652.
* Tiger: The runtime's pending mechanism was augmented to handle
* arbitrary missing classes. In addition, dyld was rewritten and
* now sends the callbacks in strictly bottom-up link order.
* The pending mechanism may now be needed only for rare and
* hard to construct programs.
* (End historical note)
*
* A class when first seen in an image is considered "unconnected".
* It is stored in `unconnected_class_hash`. If all of the class's
* superclasses exist and are already "connected", then the new class
* can be connected to its superclasses and moved to `class_hash` for
* normal use. Otherwise, the class waits in `unconnected_class_hash`
* until the superclasses finish connecting.
*
* A "connected" class is
* (1) in `class_hash`,
* (2) connected to its superclasses,
* (3) has no unconnected superclasses,
* (4) is otherwise initialized and ready for use, and
* (5) is eligible for +load if +load has not already been called.
*
* An "unconnected" class is
* (1) in `unconnected_class_hash`,
* (2) not connected to its superclasses,
* (3) has an immediate superclass which is either missing or unconnected,
* (4) is not ready for use, and
* (5) is not yet eligible for +load.
*
* Image mapping is NOT CURRENTLY THREAD-SAFE with respect to just about
* anything. Image mapping IS RE-ENTRANT in several places: superclass
* lookup may cause ZeroLink to load another image, and +load calls may
* cause dyld to load another image.
*
* Image mapping sequence:
*
* Read all classes in all new images.
* Add them all to unconnected_class_hash.
* Note any +load implementations before categories are attached.
* Attach any pending categories.
* Read all categories in all new images.
* Attach categories whose parent class exists (connected or not),
* and pend the rest.
* Mark them all eligible for +load (if implemented), even if the
* parent class is missing.
* Try to connect all classes in all new images.
* If the superclass is missing, pend the class
* If the superclass is unconnected, try to recursively connect it
* If the superclass is connected:
* connect the class
* mark the class eligible for +load, if implemented
* fix up any pended classrefs referring to the class
* connect any pended subclasses of the class
* Resolve selector refs and class refs in all new images.
* Class refs whose classes still do not exist are pended.
* Fix up protocol objects in all new images.
* Call +load for classes and categories.
* May include classes or categories that are not in these images,
* but are newly eligible because of these image.
* Class +loads will be called superclass-first because of the
* superclass-first nature of the connecting process.
* Category +load needs to be deferred until the parent class is
* connected and has had its +load called.
*
* Performance: all classes are read before any categories are read.
* Fewer categories need be pended for lack of a parent class.
*
* Performance: all categories are attempted to be attached before
* any classes are connected. Fewer class caches need be flushed.
* (Unconnected classes and their respective subclasses are guaranteed
* to be un-messageable, so their caches will be empty.)
*
* Performance: all classes are read before any classes are connected.
* Fewer classes need be pended for lack of a superclass.
*
* Correctness: all selector and class refs are fixed before any
* protocol fixups or +load methods. libobjc itself contains selector
* and class refs which are used in protocol fixup and +load.
*
* Correctness: +load methods are scheduled in bottom-up link order.
* This constraint is in addition to superclass order. Some +load
* implementations expect to use another class in a linked-to library,
* even if the two classes don't share a direct superclass relationship.
*
* Correctness: all classes are scanned for +load before any categories
* are attached. Otherwise, if a category implements +load and its class
* has no class methods, the class's +load scan would find the category's
* +load method, which would then be called twice.
*
* Correctness: pended class refs are not fixed up until the class is
* connected. Classes with missing weak superclasses remain unconnected.
* Class refs to classes with missing weak superclasses must be nil.
* Therefore class refs to unconnected classes must remain un-fixed.
*
**********************************************************************/
#if !__OBJC2__
#include "objc-private.h"
#include "objc-runtime-old.h"
#include "objc-file-old.h"
#include "objc-cache-old.h"
#include "objc-loadmethod.h"
typedef struct _objc_unresolved_category
{
struct _objc_unresolved_category *next;
old_category *cat; // may be nil
long version;
} _objc_unresolved_category;
typedef struct _PendingSubclass
{
Class subclass; // subclass to finish connecting; may be nil
struct _PendingSubclass *next;
} PendingSubclass;
typedef struct _PendingClassRef
{
Class *ref; // class reference to fix up; may be nil
// (ref & 1) is a metaclass reference
struct _PendingClassRef *next;
} PendingClassRef;
static uintptr_t classHash(void *info, Class data);
static int classIsEqual(void *info, Class name, Class cls);
static int _objc_defaultClassHandler(const char *clsName);
static inline NXMapTable *pendingClassRefsMapTable(void);
static inline NXMapTable *pendingSubclassesMapTable(void);
static void pendClassInstallation(Class cls, const char *superName);
static void pendClassReference(Class *ref, const char *className, bool isMeta);
static void resolve_references_to_class(Class cls);
static void resolve_subclasses_of_class(Class cls);
static void really_connect_class(Class cls, Class supercls);
static bool connect_class(Class cls);
static void map_method_descs (struct objc_method_description_list * methods, bool copy);
static void _objcTweakMethodListPointerForClass(Class cls);
static inline void _objc_add_category(Class cls, old_category *category, int version);
static bool _objc_add_category_flush_caches(Class cls, old_category *category, int version);
static _objc_unresolved_category *reverse_cat(_objc_unresolved_category *cat);
static void resolve_categories_for_class(Class cls);
static bool _objc_register_category(old_category *cat, int version);
// Function called when a class is loaded from an image
void (*callbackFunction)(Class, Category) = 0;
// Hash table of classes
NXHashTable * class_hash = 0;
static NXHashTablePrototype classHashPrototype =
{
(uintptr_t (*) (const void *, const void *)) classHash,
(int (*)(const void *, const void *, const void *)) classIsEqual,
NXNoEffectFree, 0
};
// Hash table of unconnected classes
static NXHashTable *unconnected_class_hash = nil;
// Exported copy of class_hash variable (hook for debugging tools)
NXHashTable *_objc_debug_class_hash = nil;
// Category and class registries
// Keys are COPIES of strings, to prevent stale pointers with unloaded bundles
// Use NXMapKeyCopyingInsert and NXMapKeyFreeingRemove
static NXMapTable * category_hash = nil;
// Keys are COPIES of strings, to prevent stale pointers with unloaded bundles
// Use NXMapKeyCopyingInsert and NXMapKeyFreeingRemove
static NXMapTable * pendingClassRefsMap = nil;
static NXMapTable * pendingSubclassesMap = nil;
// Protocols
static NXMapTable *protocol_map = nil; // name -> protocol
static NXMapTable *protocol_ext_map = nil; // protocol -> protocol ext
// Function pointer objc_getClass calls through when class is not found
static int (*objc_classHandler) (const char *) = _objc_defaultClassHandler;
// Function pointer called by objc_getClass and objc_lookupClass when
// class is not found. _objc_classLoader is called before objc_classHandler.
static BOOL (*_objc_classLoader)(const char *) = nil;
/***********************************************************************
* objc_dump_class_hash. Log names of all known classes.
**********************************************************************/
void objc_dump_class_hash(void)
{
NXHashTable *table;
unsigned count;
Class data;
NXHashState state;
table = class_hash;
count = 0;
state = NXInitHashState (table);
while (NXNextHashState (table, &state, (void **) &data))
printf ("class %d: %s\n", ++count, data->nameForLogging());
}
/***********************************************************************
* _objc_init_class_hash. Return the class lookup table, create it if
* necessary.
**********************************************************************/
void _objc_init_class_hash(void)
{
// Do nothing if class hash table already exists
if (class_hash)
return;
// class_hash starts small, with only enough capacity for libobjc itself.
// If a second library is found by map_images(), class_hash is immediately
// resized to capacity 1024 to cut down on rehashes.
// Old numbers: A smallish Foundation+AppKit program will have
// about 520 classes. Larger apps (like IB or WOB) have more like
// 800 classes. Some customers have massive quantities of classes.
// Foundation-only programs aren't likely to notice the ~6K loss.
class_hash = NXCreateHashTable(classHashPrototype, 16, nil);
_objc_debug_class_hash = class_hash;
}
/***********************************************************************
* objc_getClassList. Return the known classes.
**********************************************************************/
int objc_getClassList(Class *buffer, int bufferLen)
{
NXHashState state;
Class cls;
int cnt, num;
mutex_locker_t lock(classLock);
if (!class_hash) return 0;
num = NXCountHashTable(class_hash);
if (nil == buffer) return num;
cnt = 0;
state = NXInitHashState(class_hash);
while (cnt < bufferLen &&
NXNextHashState(class_hash, &state, (void **)&cls))
{
buffer[cnt++] = cls;
}
return num;
}
/***********************************************************************
* objc_copyClassList
* Returns pointers to all classes.
* This requires all classes be realized, which is regretfully non-lazy.
*
* outCount may be nil. *outCount is the number of classes returned.
* If the returned array is not nil, it is nil-terminated and must be
* freed with free().
* Locking: acquires classLock
**********************************************************************/
Class *
objc_copyClassList(unsigned int *outCount)
{
Class *result;
unsigned int count;
mutex_locker_t lock(classLock);
result = nil;
count = class_hash ? NXCountHashTable(class_hash) : 0;
if (count > 0) {
Class cls;
NXHashState state = NXInitHashState(class_hash);
result = (Class *)malloc((1+count) * sizeof(Class));
count = 0;
while (NXNextHashState(class_hash, &state, (void **)&cls)) {
result[count++] = cls;
}
result[count] = nil;
}
if (outCount) *outCount = count;
return result;
}
/***********************************************************************
* objc_copyProtocolList
* Returns pointers to all protocols.
* Locking: acquires classLock
**********************************************************************/
Protocol * __unsafe_unretained *
objc_copyProtocolList(unsigned int *outCount)
{
int count, i;
Protocol *proto;
const char *name;
NXMapState state;
Protocol **result;
mutex_locker_t lock(classLock);
count = NXCountMapTable(protocol_map);
if (count == 0) {
if (outCount) *outCount = 0;
return nil;
}
result = (Protocol **)calloc(1 + count, sizeof(Protocol *));
i = 0;
state = NXInitMapState(protocol_map);
while (NXNextMapState(protocol_map, &state,
(const void **)&name, (const void **)&proto))
{
result[i++] = proto;
}
result[i++] = nil;
assert(i == count+1);
if (outCount) *outCount = count;
return result;
}
/***********************************************************************
* objc_getClasses. Return class lookup table.
*
* NOTE: This function is very dangerous, since you cannot safely use
* the hashtable without locking it, and the lock is private!
**********************************************************************/
void *objc_getClasses(void)
{
OBJC_WARN_DEPRECATED;
// Return the class lookup hash table
return class_hash;
}
/***********************************************************************
* classHash.
**********************************************************************/
static uintptr_t classHash(void *info, Class data)
{
// Nil classes hash to zero
if (!data)
return 0;
// Call through to real hash function
return _objc_strhash (data->mangledName());
}
/***********************************************************************
* classIsEqual. Returns whether the class names match. If we ever
* check more than the name, routines like objc_lookUpClass have to
* change as well.
**********************************************************************/
static int classIsEqual(void *info, Class name, Class cls)
{
// Standard string comparison
return strcmp(name->mangledName(), cls->mangledName()) == 0;
}
// Unresolved future classes
static NXHashTable *future_class_hash = nil;
// Resolved future<->original classes
static NXMapTable *future_class_to_original_class_map = nil;
static NXMapTable *original_class_to_future_class_map = nil;
// CF requests about 20 future classes; HIToolbox requests one.
#define FUTURE_COUNT 32
/***********************************************************************
* setOriginalClassForFutureClass
* Record resolution of a future class.
**********************************************************************/
static void setOriginalClassForFutureClass(Class futureClass,
Class originalClass)
{
if (!future_class_to_original_class_map) {
future_class_to_original_class_map =
NXCreateMapTable(NXPtrValueMapPrototype, FUTURE_COUNT);
original_class_to_future_class_map =
NXCreateMapTable(NXPtrValueMapPrototype, FUTURE_COUNT);
}
NXMapInsert (future_class_to_original_class_map,
futureClass, originalClass);
NXMapInsert (original_class_to_future_class_map,
originalClass, futureClass);
if (PrintFuture) {
_objc_inform("FUTURE: using %p instead of %p for %s", (void*)futureClass, (void*)originalClass, originalClass->name);
}
}
/***********************************************************************
* getOriginalClassForFutureClass
* getFutureClassForOriginalClass
* Switch between a future class and its corresponding original class.
* The future class is the one actually in use.
* The original class is the one from disk.
**********************************************************************/
/*
static Class
getOriginalClassForFutureClass(Class futureClass)
{
if (!future_class_to_original_class_map) return Nil;
return NXMapGet (future_class_to_original_class_map, futureClass);
}
*/
static Class
getFutureClassForOriginalClass(Class originalClass)
{
if (!original_class_to_future_class_map) return Nil;
return (Class)NXMapGet(original_class_to_future_class_map, originalClass);
}
/***********************************************************************
* makeFutureClass
* Initialize the memory in *cls with an unresolved future class with the
* given name. The memory is recorded in future_class_hash.
**********************************************************************/
static void makeFutureClass(Class cls, const char *name)
{
// CF requests about 20 future classes, plus HIToolbox has one.
if (!future_class_hash) {
future_class_hash =
NXCreateHashTable(classHashPrototype, FUTURE_COUNT, nil);
}
cls->name = strdup(name);
NXHashInsert(future_class_hash, cls);
if (PrintFuture) {
_objc_inform("FUTURE: reserving %p for %s", (void*)cls, name);
}
}
/***********************************************************************
* _objc_allocateFutureClass
* Allocate an unresolved future class for the given class name.
* Returns any existing allocation if one was already made.
* Assumes the named class doesn't exist yet.
* Not thread safe.
**********************************************************************/
Class _objc_allocateFutureClass(const char *name)
{
Class cls;
if (future_class_hash) {
objc_class query;
query.name = name;
if ((cls = (Class)NXHashGet(future_class_hash, &query))) {
// Already have a future class for this name.
return cls;
}
}
cls = _calloc_class(sizeof(objc_class));
makeFutureClass(cls, name);
return cls;
}
/***********************************************************************
* objc_getFutureClass. Return the id of the named class.
* If the class does not exist, return an uninitialized class
* structure that will be used for the class when and if it
* does get loaded.
* Not thread safe.
**********************************************************************/
Class objc_getFutureClass(const char *name)
{
Class cls;
// YES unconnected, NO class handler
// (unconnected is OK because it will someday be the real class)
cls = look_up_class(name, YES, NO);
if (cls) {
if (PrintFuture) {
_objc_inform("FUTURE: found %p already in use for %s",
(void*)cls, name);
}
return cls;
}
// No class or future class with that name yet. Make one.
// fixme not thread-safe with respect to
// simultaneous library load or getFutureClass.
return _objc_allocateFutureClass(name);
}
BOOL _class_isFutureClass(Class cls)
{
return cls && cls->isFuture();
}
bool objc_class::isFuture()
{
return future_class_hash && NXHashGet(future_class_hash, this);
}
/***********************************************************************
* _objc_defaultClassHandler. Default objc_classHandler. Does nothing.
**********************************************************************/
static int _objc_defaultClassHandler(const char *clsName)
{
// Return zero so objc_getClass doesn't bother re-searching
return 0;
}
/***********************************************************************
* objc_setClassHandler. Set objc_classHandler to the specified value.
*
* NOTE: This should probably deal with userSuppliedHandler being nil,
* because the objc_classHandler caller does not check... it would bus
* error. It would make sense to handle nil by restoring the default
* handler. Is anyone hacking with this, though?
**********************************************************************/
void objc_setClassHandler(int (*userSuppliedHandler)(const char *))
{
OBJC_WARN_DEPRECATED;
objc_classHandler = userSuppliedHandler;
}
/***********************************************************************
* _objc_setClassLoader
* Similar to objc_setClassHandler, but objc_classLoader is used for
* both objc_getClass() and objc_lookupClass(), and objc_classLoader
* pre-empts objc_classHandler.
**********************************************************************/
void _objc_setClassLoader(BOOL (*newClassLoader)(const char *))
{
_objc_classLoader = newClassLoader;
}
/***********************************************************************
* objc_getProtocol
* Get a protocol by name, or nil.
**********************************************************************/
Protocol *objc_getProtocol(const char *name)
{
mutex_locker_t lock(classLock);
if (!protocol_map) return nil;
return (Protocol *)NXMapGet(protocol_map, name);
}
/***********************************************************************
* look_up_class
* Map a class name to a class using various methods.
* This is the common implementation of objc_lookUpClass and objc_getClass,
* and is also used internally to get additional search options.
* Sequence:
* 1. class_hash
* 2. unconnected_class_hash (optional)
* 3. classLoader callback
* 4. classHandler callback (optional)
**********************************************************************/
Class look_up_class(const char *aClassName, bool includeUnconnected,
bool includeClassHandler)
{
bool includeClassLoader = YES; // class loader cannot be skipped
Class result = nil;
struct objc_class query;
query.name = aClassName;
retry:
if (!result && class_hash) {
// Check ordinary classes
mutex_locker_t lock(classLock);
result = (Class)NXHashGet(class_hash, &query);
}
if (!result && includeUnconnected && unconnected_class_hash) {
// Check not-yet-connected classes
mutex_locker_t lock(classLock);
result = (Class)NXHashGet(unconnected_class_hash, &query);
}
if (!result && includeClassLoader && _objc_classLoader) {
// Try class loader callback
if ((*_objc_classLoader)(aClassName)) {
// Re-try lookup without class loader
includeClassLoader = NO;
goto retry;
}
}
if (!result && includeClassHandler && objc_classHandler) {
// Try class handler callback
if ((*objc_classHandler)(aClassName)) {
// Re-try lookup without class handler or class loader
includeClassLoader = NO;
includeClassHandler = NO;
goto retry;
}
}
return result;
}
/***********************************************************************
* objc_class::isConnected
* Returns TRUE if class cls is connected.
* A connected class has either a connected superclass or a nil superclass,
* and is present in class_hash.
**********************************************************************/
bool objc_class::isConnected()
{
mutex_locker_t lock(classLock);
return NXHashMember(class_hash, this);
}
/***********************************************************************
* pendingClassRefsMapTable. Return a pointer to the lookup table for
* pending class refs.
**********************************************************************/
static inline NXMapTable *pendingClassRefsMapTable(void)
{
// Allocate table if needed
if (!pendingClassRefsMap) {
pendingClassRefsMap = NXCreateMapTable(NXStrValueMapPrototype, 10);
}
// Return table pointer
return pendingClassRefsMap;
}
/***********************************************************************
* pendingSubclassesMapTable. Return a pointer to the lookup table for
* pending subclasses.
**********************************************************************/
static inline NXMapTable *pendingSubclassesMapTable(void)
{
// Allocate table if needed
if (!pendingSubclassesMap) {
pendingSubclassesMap = NXCreateMapTable(NXStrValueMapPrototype, 10);
}
// Return table pointer
return pendingSubclassesMap;
}
/***********************************************************************
* pendClassInstallation
* Finish connecting class cls when its superclass becomes connected.
* Check for multiple pends of the same class because connect_class does not.
**********************************************************************/
static void pendClassInstallation(Class cls, const char *superName)
{
NXMapTable *table;
PendingSubclass *pending;
PendingSubclass *oldList;
PendingSubclass *l;
// Create and/or locate pending class lookup table
table = pendingSubclassesMapTable ();
// Make sure this class isn't already in the pending list.
oldList = (PendingSubclass *)NXMapGet(table, superName);
for (l = oldList; l != nil; l = l->next) {
if (l->subclass == cls) return; // already here, nothing to do
}
// Create entry referring to this class
pending = (PendingSubclass *)malloc(sizeof(PendingSubclass));
pending->subclass = cls;
// Link new entry into head of list of entries for this class
pending->next = oldList;
// (Re)place entry list in the table
NXMapKeyCopyingInsert (table, superName, pending);
}
/***********************************************************************
* pendClassReference
* Fix up a class ref when the class with the given name becomes connected.
**********************************************************************/
static void pendClassReference(Class *ref, const char *className, bool isMeta)
{
NXMapTable *table;
PendingClassRef *pending;
// Create and/or locate pending class lookup table
table = pendingClassRefsMapTable ();
// Create entry containing the class reference
pending = (PendingClassRef *)malloc(sizeof(PendingClassRef));
pending->ref = ref;
if (isMeta) {
pending->ref = (Class *)((uintptr_t)pending->ref | 1);
}
// Link new entry into head of list of entries for this class
pending->next = (PendingClassRef *)NXMapGet(table, className);
// (Re)place entry list in the table
NXMapKeyCopyingInsert (table, className, pending);
if (PrintConnecting) {
_objc_inform("CONNECT: pended reference to class '%s%s' at %p",
className, isMeta ? " (meta)" : "", (void *)ref);
}
}
/***********************************************************************
* resolve_references_to_class
* Fix up any pending class refs to this class.
**********************************************************************/
static void resolve_references_to_class(Class cls)
{
PendingClassRef *pending;
if (!pendingClassRefsMap) return; // no unresolved refs for any class
pending = (PendingClassRef *)NXMapGet(pendingClassRefsMap, cls->name);
if (!pending) return; // no unresolved refs for this class
NXMapKeyFreeingRemove(pendingClassRefsMap, cls->name);
if (PrintConnecting) {
_objc_inform("CONNECT: resolving references to class '%s'", cls->name);
}
while (pending) {
PendingClassRef *next = pending->next;
if (pending->ref) {
bool isMeta = (uintptr_t)pending->ref & 1;
Class *ref =
(Class *)((uintptr_t)pending->ref & ~(uintptr_t)1);
*ref = isMeta ? cls->ISA() : cls;
}
free(pending);
pending = next;
}
if (NXCountMapTable(pendingClassRefsMap) == 0) {
NXFreeMapTable(pendingClassRefsMap);
pendingClassRefsMap = nil;
}
}
/***********************************************************************
* resolve_subclasses_of_class
* Fix up any pending subclasses of this class.
**********************************************************************/
static void resolve_subclasses_of_class(Class cls)
{
PendingSubclass *pending;
if (!pendingSubclassesMap) return; // no unresolved subclasses
pending = (PendingSubclass *)NXMapGet(pendingSubclassesMap, cls->name);
if (!pending) return; // no unresolved subclasses for this class
NXMapKeyFreeingRemove(pendingSubclassesMap, cls->name);
// Destroy the pending table if it's now empty, to save memory.
if (NXCountMapTable(pendingSubclassesMap) == 0) {
NXFreeMapTable(pendingSubclassesMap);
pendingSubclassesMap = nil;
}
if (PrintConnecting) {
_objc_inform("CONNECT: resolving subclasses of class '%s'", cls->name);
}
while (pending) {
PendingSubclass *next = pending->next;
if (pending->subclass) connect_class(pending->subclass);
free(pending);
pending = next;
}
}
/***********************************************************************
* really_connect_class
* Connect cls to superclass supercls unconditionally.
* Also adjust the class hash tables and handle pended subclasses.
*
* This should be called from connect_class() ONLY.
**********************************************************************/
static void really_connect_class(Class cls,
Class supercls)
{
Class oldCls;
// Connect superclass pointers.
set_superclass(cls, supercls, YES);
// Done!
cls->info |= CLS_CONNECTED;
{
mutex_locker_t lock(classLock);
// Update hash tables.
NXHashRemove(unconnected_class_hash, cls);
oldCls = (Class)NXHashInsert(class_hash, cls);
// Delete unconnected_class_hash if it is now empty.
if (NXCountHashTable(unconnected_class_hash) == 0) {
NXFreeHashTable(unconnected_class_hash);
unconnected_class_hash = nil;
}
// No duplicate classes allowed.
// Duplicates should have been rejected by _objc_read_classes_from_image
assert(!oldCls);
}
// Fix up pended class refs to this class, if any
resolve_references_to_class(cls);
// Connect newly-connectable subclasses
resolve_subclasses_of_class(cls);
// Debugging: if this class has ivars, make sure this class's ivars don't
// overlap with its super's. This catches some broken fragile base classes.
// Do not use super->instance_size vs. self->ivar[0] to check this.
// Ivars may be packed across instance_size boundaries.
if (DebugFragileSuperclasses && cls->ivars && cls->ivars->ivar_count) {
Class ivar_cls = supercls;
// Find closest superclass that has some ivars, if one exists.
while (ivar_cls &&
(!ivar_cls->ivars || ivar_cls->ivars->ivar_count == 0))
{
ivar_cls = ivar_cls->superclass;
}
if (ivar_cls) {
// Compare superclass's last ivar to this class's first ivar
old_ivar *super_ivar =
&ivar_cls->ivars->ivar_list[ivar_cls->ivars->ivar_count - 1];
old_ivar *self_ivar =
&cls->ivars->ivar_list[0];
// fixme could be smarter about super's ivar size
if (self_ivar->ivar_offset <= super_ivar->ivar_offset) {
_objc_inform("WARNING: ivars of superclass '%s' and "
"subclass '%s' overlap; superclass may have "
"changed since subclass was compiled",
ivar_cls->name, cls->name);
}
}
}
}
/***********************************************************************
* connect_class
* Connect class cls to its superclasses, if possible.
* If cls becomes connected, move it from unconnected_class_hash
* to connected_class_hash.
* Returns TRUE if cls is connected.
* Returns FALSE if cls could not be connected for some reason
* (missing superclass or still-unconnected superclass)
**********************************************************************/
static bool connect_class(Class cls)
{
if (cls->isConnected()) {
// This class is already connected to its superclass.
// Do nothing.
return TRUE;
}
else if (cls->superclass == nil) {
// This class is a root class.
// Connect it to itself.
if (PrintConnecting) {
_objc_inform("CONNECT: class '%s' now connected (root class)",
cls->name);
}
really_connect_class(cls, nil);
return TRUE;
}
else {
// This class is not a root class and is not yet connected.
// Connect it if its superclass and root class are already connected.
// Otherwise, add this class to the to-be-connected list,
// pending the completion of its superclass and root class.
// At this point, cls->superclass and cls->ISA()->ISA() are still STRINGS
char *supercls_name = (char *)cls->superclass;
Class supercls;
// YES unconnected, YES class handler
if (nil == (supercls = look_up_class(supercls_name, YES, YES))) {
// Superclass does not exist yet.
// pendClassInstallation will handle duplicate pends of this class
pendClassInstallation(cls, supercls_name);
if (PrintConnecting) {
_objc_inform("CONNECT: class '%s' NOT connected (missing super)", cls->name);
}
return FALSE;
}
if (! connect_class(supercls)) {
// Superclass exists but is not yet connected.
// pendClassInstallation will handle duplicate pends of this class
pendClassInstallation(cls, supercls_name);
if (PrintConnecting) {
_objc_inform("CONNECT: class '%s' NOT connected (unconnected super)", cls->name);
}
return FALSE;
}
// Superclass exists and is connected.
// Connect this class to the superclass.
if (PrintConnecting) {
_objc_inform("CONNECT: class '%s' now connected", cls->name);
}
really_connect_class(cls, supercls);
return TRUE;
}
}
/***********************************************************************
* _objc_read_categories_from_image.
* Read all categories from the given image.