-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathobjc-initialize.mm
598 lines (522 loc) · 21.9 KB
/
objc-initialize.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
/*
* 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-initialize.m
* +initialize support
**********************************************************************/
/***********************************************************************
* Thread-safety during class initialization (GrP 2001-9-24)
*
* Initial state: CLS_INITIALIZING and CLS_INITIALIZED both clear.
* During initialization: CLS_INITIALIZING is set
* After initialization: CLS_INITIALIZING clear and CLS_INITIALIZED set.
* CLS_INITIALIZING and CLS_INITIALIZED are never set at the same time.
* CLS_INITIALIZED is never cleared once set.
*
* Only one thread is allowed to actually initialize a class and send
* +initialize. Enforced by allowing only one thread to set CLS_INITIALIZING.
*
* Additionally, threads trying to send messages to a class must wait for
* +initialize to finish. During initialization of a class, that class's
* method cache is kept empty. objc_msgSend will revert to
* class_lookupMethodAndLoadCache, which checks CLS_INITIALIZED before
* messaging. If CLS_INITIALIZED is clear but CLS_INITIALIZING is set,
* the thread must block, unless it is the thread that started
* initializing the class in the first place.
*
* Each thread keeps a list of classes it's initializing.
* The global classInitLock is used to synchronize changes to CLS_INITIALIZED
* and CLS_INITIALIZING: the transition to CLS_INITIALIZING must be
* an atomic test-and-set with respect to itself and the transition
* to CLS_INITIALIZED.
* The global classInitWaitCond is used to block threads waiting for an
* initialization to complete. The classInitLock synchronizes
* condition checking and the condition variable.
**********************************************************************/
/***********************************************************************
* +initialize deadlock case when a class is marked initializing while
* its superclass is initialized. Solved by completely initializing
* superclasses before beginning to initialize a class.
*
* OmniWeb class hierarchy:
* OBObject
* | ` OBPostLoader
* OFObject
* / \
* OWAddressEntry OWController
* |
* OWConsoleController
*
* Thread 1 (evil testing thread):
* initialize OWAddressEntry
* super init OFObject
* super init OBObject
* [OBObject initialize] runs OBPostLoader, which inits lots of classes...
* initialize OWConsoleController
* super init OWController - wait for Thread 2 to finish OWController init
*
* Thread 2 (normal OmniWeb thread):
* initialize OWController
* super init OFObject - wait for Thread 1 to finish OFObject init
*
* deadlock!
*
* Solution: fully initialize super classes before beginning to initialize
* a subclass. Then the initializing+initialized part of the class hierarchy
* will be a contiguous subtree starting at the root, so other threads
* can't jump into the middle between two initializing classes, and we won't
* get stuck while a superclass waits for its subclass which waits for the
* superclass.
**********************************************************************/
#include "objc-private.h"
#include "message.h"
#include "objc-initialize.h"
/* classInitLock protects CLS_INITIALIZED and CLS_INITIALIZING, and
* is signalled when any class is done initializing.
* Threads that are waiting for a class to finish initializing wait on this. */
monitor_t classInitLock;
/***********************************************************************
* struct _objc_initializing_classes
* Per-thread list of classes currently being initialized by that thread.
* During initialization, that thread is allowed to send messages to that
* class, but other threads have to wait.
* The list is a simple array of metaclasses (the metaclass stores
* the initialization state).
**********************************************************************/
typedef struct _objc_initializing_classes {
int classesAllocated;
Class *metaclasses;
} _objc_initializing_classes;
/***********************************************************************
* _fetchInitializingClassList
* Return the list of classes being initialized by this thread.
* If create == YES, create the list when no classes are being initialized by this thread.
* If create == NO, return nil when no classes are being initialized by this thread.
**********************************************************************/
static _objc_initializing_classes *_fetchInitializingClassList(bool create)
{
_objc_pthread_data *data;
_objc_initializing_classes *list;
Class *classes;
data = _objc_fetch_pthread_data(create);
if (data == nil) return nil;
list = data->initializingClasses;
if (list == nil) {
if (!create) {
return nil;
} else {
list = (_objc_initializing_classes *)
calloc(1, sizeof(_objc_initializing_classes));
data->initializingClasses = list;
}
}
classes = list->metaclasses;
if (classes == nil) {
// If _objc_initializing_classes exists, allocate metaclass array,
// even if create == NO.
// Allow 4 simultaneous class inits on this thread before realloc.
list->classesAllocated = 4;
classes = (Class *)
calloc(list->classesAllocated, sizeof(Class));
list->metaclasses = classes;
}
return list;
}
/***********************************************************************
* _destroyInitializingClassList
* Deallocate memory used by the given initialization list.
* Any part of the list may be nil.
* Called from _objc_pthread_destroyspecific().
**********************************************************************/
void _destroyInitializingClassList(struct _objc_initializing_classes *list)
{
if (list != nil) {
if (list->metaclasses != nil) {
free(list->metaclasses);
}
free(list);
}
}
/***********************************************************************
* _thisThreadIsInitializingClass
* Return TRUE if this thread is currently initializing the given class.
**********************************************************************/
bool _thisThreadIsInitializingClass(Class cls)
{
int i;
_objc_initializing_classes *list = _fetchInitializingClassList(NO);
if (list) {
cls = cls->getMeta();
for (i = 0; i < list->classesAllocated; i++) {
if (cls == list->metaclasses[i]) return YES;
}
}
// no list or not found in list
return NO;
}
/***********************************************************************
* _setThisThreadIsInitializingClass
* Record that this thread is currently initializing the given class.
* This thread will be allowed to send messages to the class, but
* other threads will have to wait.
**********************************************************************/
static void _setThisThreadIsInitializingClass(Class cls)
{
int i;
_objc_initializing_classes *list = _fetchInitializingClassList(YES);
cls = cls->getMeta();
// paranoia: explicitly disallow duplicates
for (i = 0; i < list->classesAllocated; i++) {
if (cls == list->metaclasses[i]) {
_objc_fatal("thread is already initializing this class!");
return; // already the initializer
}
}
for (i = 0; i < list->classesAllocated; i++) {
if (! list->metaclasses[i]) {
list->metaclasses[i] = cls;
return;
}
}
// class list is full - reallocate
list->classesAllocated = list->classesAllocated * 2 + 1;
list->metaclasses = (Class *)
realloc(list->metaclasses,
list->classesAllocated * sizeof(Class));
// zero out the new entries
list->metaclasses[i++] = cls;
for ( ; i < list->classesAllocated; i++) {
list->metaclasses[i] = nil;
}
}
/***********************************************************************
* _setThisThreadIsNotInitializingClass
* Record that this thread is no longer initializing the given class.
**********************************************************************/
static void _setThisThreadIsNotInitializingClass(Class cls)
{
int i;
_objc_initializing_classes *list = _fetchInitializingClassList(NO);
if (list) {
cls = cls->getMeta();
for (i = 0; i < list->classesAllocated; i++) {
if (cls == list->metaclasses[i]) {
list->metaclasses[i] = nil;
return;
}
}
}
// no list or not found in list
_objc_fatal("thread is not initializing this class!");
}
typedef struct PendingInitialize {
Class subclass;
struct PendingInitialize *next;
} PendingInitialize;
static NXMapTable *pendingInitializeMap;
/***********************************************************************
* _finishInitializing
* cls has completed its +initialize method, and so has its superclass.
* Mark cls as initialized as well, then mark any of cls's subclasses
* that have already finished their own +initialize methods.
**********************************************************************/
static void _finishInitializing(Class cls, Class supercls)
{
PendingInitialize *pending;
classInitLock.assertLocked();
assert(!supercls || supercls->isInitialized());
if (PrintInitializing) {
_objc_inform("INITIALIZE: thread %p: %s is fully +initialized",
pthread_self(), cls->nameForLogging());
}
// mark this class as fully +initialized
cls->setInitialized();
classInitLock.notifyAll();
_setThisThreadIsNotInitializingClass(cls);
// mark any subclasses that were merely waiting for this class
if (!pendingInitializeMap) return;
pending = (PendingInitialize *)NXMapGet(pendingInitializeMap, cls);
if (!pending) return;
NXMapRemove(pendingInitializeMap, cls);
// Destroy the pending table if it's now empty, to save memory.
if (NXCountMapTable(pendingInitializeMap) == 0) {
NXFreeMapTable(pendingInitializeMap);
pendingInitializeMap = nil;
}
while (pending) {
PendingInitialize *next = pending->next;
if (pending->subclass) _finishInitializing(pending->subclass, cls);
free(pending);
pending = next;
}
}
/***********************************************************************
* _finishInitializingAfter
* cls has completed its +initialize method, but its superclass has not.
* Wait until supercls finishes before marking cls as initialized.
**********************************************************************/
static void _finishInitializingAfter(Class cls, Class supercls)
{
PendingInitialize *pending;
classInitLock.assertLocked();
if (PrintInitializing) {
_objc_inform("INITIALIZE: thread %p: class %s will be marked as fully "
"+initialized after superclass +[%s initialize] completes",
pthread_self(), cls->nameForLogging(),
supercls->nameForLogging());
}
if (!pendingInitializeMap) {
pendingInitializeMap =
NXCreateMapTable(NXPtrValueMapPrototype, 10);
// fixme pre-size this table for CF/NSObject +initialize
}
pending = (PendingInitialize *)malloc(sizeof(*pending));
pending->subclass = cls;
pending->next = (PendingInitialize *)
NXMapGet(pendingInitializeMap, supercls);
NXMapInsert(pendingInitializeMap, supercls, pending);
}
// Provide helpful messages in stack traces.
OBJC_EXTERN __attribute__((noinline, used, visibility("hidden")))
void waitForInitializeToComplete(Class cls)
asm("_WAITING_FOR_ANOTHER_THREAD_TO_FINISH_CALLING_+initialize");
OBJC_EXTERN __attribute__((noinline, used, visibility("hidden")))
void callInitialize(Class cls)
asm("_CALLING_SOME_+initialize_METHOD");
void waitForInitializeToComplete(Class cls)
{
if (PrintInitializing) {
_objc_inform("INITIALIZE: thread %p: blocking until +[%s initialize] "
"completes", pthread_self(), cls->nameForLogging());
}
monitor_locker_t lock(classInitLock);
while (!cls->isInitialized()) {
classInitLock.wait();
}
asm("");
}
void callInitialize(Class cls)
{
((void(*)(Class, SEL))objc_msgSend)(cls, SEL_initialize);
asm("");
}
/***********************************************************************
* classHasTrivialInitialize
* Returns true if the class has no +initialize implementation or
* has a +initialize implementation that looks empty.
* Any root class +initialize implemetation is assumed to be trivial.
**********************************************************************/
static bool classHasTrivialInitialize(Class cls)
{
if (cls->isRootClass() || cls->isRootMetaclass()) return true;
Class rootCls = cls->ISA()->ISA()->superclass;
IMP rootImp = lookUpImpOrNil(rootCls->ISA(), SEL_initialize, rootCls,
NO/*initialize*/, YES/*cache*/, NO/*resolver*/);
IMP imp = lookUpImpOrNil(cls->ISA(), SEL_initialize, cls,
NO/*initialize*/, YES/*cache*/, NO/*resolver*/);
return (imp == nil || imp == (IMP)&objc_noop_imp || imp == rootImp);
}
/***********************************************************************
* lockAndFinishInitializing
* Mark a class as finished initializing and notify waiters, or queue for later.
* If the superclass is also done initializing, then update
* the info bits and notify waiting threads.
* If not, update them later. (This can happen if this +initialize
* was itself triggered from inside a superclass +initialize.)
**********************************************************************/
static void lockAndFinishInitializing(Class cls, Class supercls)
{
monitor_locker_t lock(classInitLock);
if (!supercls || supercls->isInitialized()) {
_finishInitializing(cls, supercls);
} else {
_finishInitializingAfter(cls, supercls);
}
}
/***********************************************************************
* performForkChildInitialize
* +initialize after fork() is problematic. It's possible for the
* fork child process to call some +initialize that would deadlock waiting
* for another +initialize in the parent process.
* We wouldn't know how much progress it made therein, so we can't
* act as if +initialize completed nor can we restart +initialize
* from scratch.
*
* Instead we proceed introspectively. If the class has some
* +initialize implementation, we halt. If the class has no
* +initialize implementation of its own, we continue. Root
* class +initialize is assumed to be empty if it exists.
*
* We apply this rule even if the child's +initialize does not appear
* to be blocked by anything. This prevents races wherein the +initialize
* deadlock only rarely hits. Instead we disallow it even when we "won"
* the race.
*
* Exception: processes that are single-threaded when fork() is called
* have no restrictions on +initialize in the child. Examples: sshd and httpd.
*
* Classes that wish to implement +initialize and be callable after
* fork() must use an atfork() handler to provoke +initialize in fork prepare.
**********************************************************************/
// Called before halting when some +initialize
// method can't be called after fork().
BREAKPOINT_FUNCTION(
void objc_initializeAfterForkError(Class cls)
);
void performForkChildInitialize(Class cls, Class supercls)
{
if (classHasTrivialInitialize(cls)) {
if (PrintInitializing) {
_objc_inform("INITIALIZE: thread %p: skipping trivial +[%s "
"initialize] in fork() child process",
pthread_self(), cls->nameForLogging());
}
lockAndFinishInitializing(cls, supercls);
}
else {
if (PrintInitializing) {
_objc_inform("INITIALIZE: thread %p: refusing to call +[%s "
"initialize] in fork() child process because "
"it may have been in progress when fork() was called",
pthread_self(), cls->nameForLogging());
}
_objc_inform_now_and_on_crash
("+[%s initialize] may have been in progress in another thread "
"when fork() was called.",
cls->nameForLogging());
objc_initializeAfterForkError(cls);
_objc_fatal
("+[%s initialize] may have been in progress in another thread "
"when fork() was called. We cannot safely call it or "
"ignore it in the fork() child process. Crashing instead. "
"Set a breakpoint on objc_initializeAfterForkError to debug.",
cls->nameForLogging());
}
}
/***********************************************************************
* class_initialize. Send the '+initialize' message on demand to any
* uninitialized class. Force initialization of superclasses first.
**********************************************************************/
void _class_initialize(Class cls)
{
assert(!cls->isMetaClass());
Class supercls;
bool reallyInitialize = NO;
// Make sure super is done initializing BEFORE beginning to initialize cls.
// See note about deadlock above.
// 保证父类先初始化
supercls = cls->superclass;
if (supercls && !supercls->isInitialized()) {
_class_initialize(supercls);
}
// Try to atomically set CLS_INITIALIZING.
{
monitor_locker_t lock(classInitLock);
if (!cls->isInitialized() && !cls->isInitializing()) {
cls->setInitializing();
reallyInitialize = YES;
}
}
if (reallyInitialize) {
// We successfully set the CLS_INITIALIZING bit. Initialize the class.
// Record that we're initializing this class so we can message it.
_setThisThreadIsInitializingClass(cls);
if (MultithreadedForkChild) {
// LOL JK we don't really call +initialize methods after fork().
performForkChildInitialize(cls, supercls);
return;
}
// Send the +initialize message.
// Note that +initialize is sent to the superclass (again) if
// this class doesn't implement +initialize. 2157218
if (PrintInitializing) {
_objc_inform("INITIALIZE: thread %p: calling +[%s initialize]",
pthread_self(), cls->nameForLogging());
}
// Exceptions: A +initialize call that throws an exception
// is deemed to be a complete and successful +initialize.
//
// Only __OBJC2__ adds these handlers. !__OBJC2__ has a
// bootstrapping problem of this versus CF's call to
// objc_exception_set_functions().
#if __OBJC2__
@try
#endif
{
callInitialize(cls);
if (PrintInitializing) {
_objc_inform("INITIALIZE: thread %p: finished +[%s initialize]",
pthread_self(), cls->nameForLogging());
}
}
#if __OBJC2__
@catch (...) {
if (PrintInitializing) {
_objc_inform("INITIALIZE: thread %p: +[%s initialize] "
"threw an exception",
pthread_self(), cls->nameForLogging());
}
@throw;
}
@finally
#endif
{
// Done initializing.
lockAndFinishInitializing(cls, supercls);
}
return;
}
else if (cls->isInitializing()) {
// We couldn't set INITIALIZING because INITIALIZING was already set.
// If this thread set it earlier, continue normally.
// If some other thread set it, block until initialize is done.
// It's ok if INITIALIZING changes to INITIALIZED while we're here,
// because we safely check for INITIALIZED inside the lock
// before blocking.
if (_thisThreadIsInitializingClass(cls)) {
return;
} else if (!MultithreadedForkChild) {
waitForInitializeToComplete(cls);
return;
} else {
// We're on the child side of fork(), facing a class that
// was initializing by some other thread when fork() was called.
_setThisThreadIsInitializingClass(cls);
performForkChildInitialize(cls, supercls);
}
}
else if (cls->isInitialized()) {
// Set CLS_INITIALIZING failed because someone else already
// initialized the class. Continue normally.
// NOTE this check must come AFTER the ISINITIALIZING case.
// Otherwise: Another thread is initializing this class. ISINITIALIZED
// is false. Skip this clause. Then the other thread finishes
// initialization and sets INITIALIZING=no and INITIALIZED=yes.
// Skip the ISINITIALIZING clause. Die horribly.
return;
}
else {
// We shouldn't be here.
_objc_fatal("thread-safe class init in objc runtime is buggy!");
}
}