]> git.saurik.com Git - apple/objc4.git/blob - runtime/objc-weak.mm
c822953247e7f6913c6e6ceff104151f014ebe0a
[apple/objc4.git] / runtime / objc-weak.mm
1 /*
2 * Copyright (c) 2010-2011 Apple Inc. All rights reserved.
3 *
4 * @APPLE_LICENSE_HEADER_START@
5 *
6 * This file contains Original Code and/or Modifications of Original Code
7 * as defined in and that are subject to the Apple Public Source License
8 * Version 2.0 (the 'License'). You may not use this file except in
9 * compliance with the License. Please obtain a copy of the License at
10 * http://www.opensource.apple.com/apsl/ and read it before using this
11 * file.
12 *
13 * The Original Code and all software distributed under the License are
14 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
15 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
16 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
17 * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
18 * Please see the License for the specific language governing rights and
19 * limitations under the License.
20 *
21 * @APPLE_LICENSE_HEADER_END@
22 */
23
24 #include "objc-private.h"
25
26 #include "objc-weak.h"
27
28 #include <stdint.h>
29 #include <stdbool.h>
30 #include <sys/types.h>
31 #include <libkern/OSAtomic.h>
32
33 #define TABLE_SIZE(entry) (entry->mask ? entry->mask + 1 : 0)
34
35 static void append_referrer(weak_entry_t *entry, objc_object **new_referrer);
36
37 BREAKPOINT_FUNCTION(
38 void objc_weak_error(void)
39 );
40
41 /**
42 * Unique hash function for object pointers only.
43 *
44 * @param key The object pointer
45 *
46 * @return Size unrestricted hash of pointer.
47 */
48 static inline uintptr_t hash_pointer(objc_object *key) {
49 return ptr_hash((uintptr_t)key);
50 }
51
52 /**
53 * Unique hash function for weak object pointers only.
54 *
55 * @param key The weak object pointer.
56 *
57 * @return Size unrestricted hash of pointer.
58 */
59 static inline uintptr_t w_hash_pointer(objc_object **key) {
60 return ptr_hash((uintptr_t)key);
61 }
62
63 /**
64 * Grow the entry's hash table of referrers. Rehashes each
65 * of the referrers.
66 *
67 * @param entry Weak pointer hash set for a particular object.
68 */
69 __attribute__((noinline, used))
70 static void grow_refs_and_insert(weak_entry_t *entry,
71 objc_object **new_referrer)
72 {
73 assert(entry->out_of_line);
74
75 size_t old_size = TABLE_SIZE(entry);
76 size_t new_size = old_size ? old_size * 2 : 8;
77
78 size_t num_refs = entry->num_refs;
79 weak_referrer_t *old_refs = entry->referrers;
80 entry->mask = new_size - 1;
81
82 entry->referrers = (weak_referrer_t *)
83 calloc(TABLE_SIZE(entry), sizeof(weak_referrer_t));
84 entry->num_refs = 0;
85 entry->max_hash_displacement = 0;
86
87 for (size_t i = 0; i < old_size && num_refs > 0; i++) {
88 if (old_refs[i] != nil) {
89 append_referrer(entry, old_refs[i]);
90 num_refs--;
91 }
92 }
93 // Insert
94 append_referrer(entry, new_referrer);
95 if (old_refs) free(old_refs);
96 }
97
98 /**
99 * Add the given referrer to set of weak pointers in this entry.
100 * Does not perform duplicate checking (b/c weak pointers are never
101 * added to a set twice).
102 *
103 * @param entry The entry holding the set of weak pointers.
104 * @param new_referrer The new weak pointer to be added.
105 */
106 static void append_referrer(weak_entry_t *entry, objc_object **new_referrer)
107 {
108 if (! entry->out_of_line) {
109 // Try to insert inline.
110 for (size_t i = 0; i < WEAK_INLINE_COUNT; i++) {
111 if (entry->inline_referrers[i] == nil) {
112 entry->inline_referrers[i] = new_referrer;
113 return;
114 }
115 }
116
117 // Couldn't insert inline. Allocate out of line.
118 weak_referrer_t *new_referrers = (weak_referrer_t *)
119 calloc(WEAK_INLINE_COUNT, sizeof(weak_referrer_t));
120 // This constructed table is invalid, but grow_refs_and_insert
121 // will fix it and rehash it.
122 for (size_t i = 0; i < WEAK_INLINE_COUNT; i++) {
123 new_referrers[i] = entry->inline_referrers[i];
124 }
125 entry->referrers = new_referrers;
126 entry->num_refs = WEAK_INLINE_COUNT;
127 entry->out_of_line = 1;
128 entry->mask = WEAK_INLINE_COUNT-1;
129 entry->max_hash_displacement = 0;
130 }
131
132 assert(entry->out_of_line);
133
134 if (entry->num_refs >= TABLE_SIZE(entry) * 3/4) {
135 return grow_refs_and_insert(entry, new_referrer);
136 }
137 size_t index = w_hash_pointer(new_referrer) & (entry->mask);
138 size_t hash_displacement = 0;
139 while (entry->referrers[index] != NULL) {
140 index = (index+1) & entry->mask;
141 hash_displacement++;
142 }
143 if (hash_displacement > entry->max_hash_displacement) {
144 entry->max_hash_displacement = hash_displacement;
145 }
146 weak_referrer_t &ref = entry->referrers[index];
147 ref = new_referrer;
148 entry->num_refs++;
149 }
150
151 /**
152 * Remove old_referrer from set of referrers, if it's present.
153 * Does not remove duplicates, because duplicates should not exist.
154 *
155 * @todo this is slow if old_referrer is not present. Is this ever the case?
156 *
157 * @param entry The entry holding the referrers.
158 * @param old_referrer The referrer to remove.
159 */
160 static void remove_referrer(weak_entry_t *entry, objc_object **old_referrer)
161 {
162 if (! entry->out_of_line) {
163 for (size_t i = 0; i < WEAK_INLINE_COUNT; i++) {
164 if (entry->inline_referrers[i] == old_referrer) {
165 entry->inline_referrers[i] = nil;
166 return;
167 }
168 }
169 _objc_inform("Attempted to unregister unknown __weak variable "
170 "at %p. This is probably incorrect use of "
171 "objc_storeWeak() and objc_loadWeak(). "
172 "Break on objc_weak_error to debug.\n",
173 old_referrer);
174 objc_weak_error();
175 return;
176 }
177
178 size_t index = w_hash_pointer(old_referrer) & (entry->mask);
179 size_t hash_displacement = 0;
180 while (entry->referrers[index] != old_referrer) {
181 index = (index+1) & entry->mask;
182 hash_displacement++;
183 if (hash_displacement > entry->max_hash_displacement) {
184 _objc_inform("Attempted to unregister unknown __weak variable "
185 "at %p. This is probably incorrect use of "
186 "objc_storeWeak() and objc_loadWeak(). "
187 "Break on objc_weak_error to debug.\n",
188 old_referrer);
189 objc_weak_error();
190 return;
191 }
192 }
193 entry->referrers[index] = nil;
194 entry->num_refs--;
195 }
196
197 /**
198 * Add new_entry to the object's table of weak references.
199 * Does not check whether the referent is already in the table.
200 */
201 static void weak_entry_insert(weak_table_t *weak_table, weak_entry_t *new_entry)
202 {
203 weak_entry_t *weak_entries = weak_table->weak_entries;
204 assert(weak_entries != nil);
205
206 size_t index = hash_pointer(new_entry->referent) & (weak_table->mask);
207 size_t hash_displacement = 0;
208 while (weak_entries[index].referent != nil) {
209 index = (index+1) & weak_table->mask;
210 hash_displacement++;
211 }
212
213 weak_entries[index] = *new_entry;
214 weak_table->num_entries++;
215
216 if (hash_displacement > weak_table->max_hash_displacement) {
217 weak_table->max_hash_displacement = hash_displacement;
218 }
219 }
220
221
222 static void weak_resize(weak_table_t *weak_table, size_t new_size)
223 {
224 size_t old_size = TABLE_SIZE(weak_table);
225
226 weak_entry_t *old_entries = weak_table->weak_entries;
227 weak_entry_t *new_entries = (weak_entry_t *)
228 calloc(new_size, sizeof(weak_entry_t));
229
230 weak_table->mask = new_size - 1;
231 weak_table->weak_entries = new_entries;
232 weak_table->max_hash_displacement = 0;
233 weak_table->num_entries = 0; // restored by weak_entry_insert below
234
235 if (old_entries) {
236 weak_entry_t *entry;
237 weak_entry_t *end = old_entries + old_size;
238 for (entry = old_entries; entry < end; entry++) {
239 if (entry->referent) {
240 weak_entry_insert(weak_table, entry);
241 }
242 }
243 free(old_entries);
244 }
245 }
246
247 // Grow the given zone's table of weak references if it is full.
248 static void weak_grow_maybe(weak_table_t *weak_table)
249 {
250 size_t old_size = TABLE_SIZE(weak_table);
251
252 // Grow if at least 3/4 full.
253 if (weak_table->num_entries >= old_size * 3 / 4) {
254 weak_resize(weak_table, old_size ? old_size*2 : 64);
255 }
256 }
257
258 // Shrink the table if it is mostly empty.
259 static void weak_compact_maybe(weak_table_t *weak_table)
260 {
261 size_t old_size = TABLE_SIZE(weak_table);
262
263 // Shrink if larger than 1024 buckets and at most 1/16 full.
264 if (old_size >= 1024 && old_size / 16 >= weak_table->num_entries) {
265 weak_resize(weak_table, old_size / 8);
266 // leaves new table no more than 1/2 full
267 }
268 }
269
270
271 /**
272 * Remove entry from the zone's table of weak references.
273 */
274 static void weak_entry_remove(weak_table_t *weak_table, weak_entry_t *entry)
275 {
276 // remove entry
277 if (entry->out_of_line) free(entry->referrers);
278 bzero(entry, sizeof(*entry));
279
280 weak_table->num_entries--;
281
282 weak_compact_maybe(weak_table);
283 }
284
285
286 /**
287 * Return the weak reference table entry for the given referent.
288 * If there is no entry for referent, return NULL.
289 * Performs a lookup.
290 *
291 * @param weak_table
292 * @param referent The object. Must not be nil.
293 *
294 * @return The table of weak referrers to this object.
295 */
296 static weak_entry_t *
297 weak_entry_for_referent(weak_table_t *weak_table, objc_object *referent)
298 {
299 assert(referent);
300
301 weak_entry_t *weak_entries = weak_table->weak_entries;
302
303 if (!weak_entries) return nil;
304
305 size_t index = hash_pointer(referent) & weak_table->mask;
306 size_t hash_displacement = 0;
307 while (weak_table->weak_entries[index].referent != referent) {
308 index = (index+1) & weak_table->mask;
309 hash_displacement++;
310 if (hash_displacement > weak_table->max_hash_displacement) {
311 return nil;
312 }
313 }
314
315 return &weak_table->weak_entries[index];
316 }
317
318 /**
319 * Unregister an already-registered weak reference.
320 * This is used when referrer's storage is about to go away, but referent
321 * isn't dead yet. (Otherwise, zeroing referrer later would be a
322 * bad memory access.)
323 * Does nothing if referent/referrer is not a currently active weak reference.
324 * Does not zero referrer.
325 *
326 * FIXME currently requires old referent value to be passed in (lame)
327 * FIXME unregistration should be automatic if referrer is collected
328 *
329 * @param weak_table The global weak table.
330 * @param referent The object.
331 * @param referrer The weak reference.
332 */
333 void
334 weak_unregister_no_lock(weak_table_t *weak_table, id referent_id,
335 id *referrer_id)
336 {
337 objc_object *referent = (objc_object *)referent_id;
338 objc_object **referrer = (objc_object **)referrer_id;
339
340 weak_entry_t *entry;
341
342 if (!referent) return;
343
344 if ((entry = weak_entry_for_referent(weak_table, referent))) {
345 remove_referrer(entry, referrer);
346 bool empty = true;
347 if (entry->out_of_line && entry->num_refs != 0) {
348 empty = false;
349 }
350 else {
351 for (size_t i = 0; i < WEAK_INLINE_COUNT; i++) {
352 if (entry->inline_referrers[i]) {
353 empty = false;
354 break;
355 }
356 }
357 }
358
359 if (empty) {
360 weak_entry_remove(weak_table, entry);
361 }
362 }
363
364 // Do not set *referrer = nil. objc_storeWeak() requires that the
365 // value not change.
366 }
367
368 /**
369 * Registers a new (object, weak pointer) pair. Creates a new weak
370 * object entry if it does not exist.
371 *
372 * @param weak_table The global weak table.
373 * @param referent The object pointed to by the weak reference.
374 * @param referrer The weak pointer address.
375 */
376 id
377 weak_register_no_lock(weak_table_t *weak_table, id referent_id,
378 id *referrer_id, bool crashIfDeallocating)
379 {
380 objc_object *referent = (objc_object *)referent_id;
381 objc_object **referrer = (objc_object **)referrer_id;
382
383 if (!referent || referent->isTaggedPointer()) return referent_id;
384
385 // ensure that the referenced object is viable
386 bool deallocating;
387 if (!referent->ISA()->hasCustomRR()) {
388 deallocating = referent->rootIsDeallocating();
389 }
390 else {
391 BOOL (*allowsWeakReference)(objc_object *, SEL) =
392 (BOOL(*)(objc_object *, SEL))
393 object_getMethodImplementation((id)referent,
394 SEL_allowsWeakReference);
395 if ((IMP)allowsWeakReference == _objc_msgForward) {
396 return nil;
397 }
398 deallocating =
399 ! (*allowsWeakReference)(referent, SEL_allowsWeakReference);
400 }
401
402 if (deallocating) {
403 if (crashIfDeallocating) {
404 _objc_fatal("Cannot form weak reference to instance (%p) of "
405 "class %s. It is possible that this object was "
406 "over-released, or is in the process of deallocation.",
407 (void*)referent, object_getClassName((id)referent));
408 } else {
409 return nil;
410 }
411 }
412
413 // now remember it and where it is being stored
414 weak_entry_t *entry;
415 if ((entry = weak_entry_for_referent(weak_table, referent))) {
416 append_referrer(entry, referrer);
417 }
418 else {
419 weak_entry_t new_entry;
420 new_entry.referent = referent;
421 new_entry.out_of_line = 0;
422 new_entry.inline_referrers[0] = referrer;
423 for (size_t i = 1; i < WEAK_INLINE_COUNT; i++) {
424 new_entry.inline_referrers[i] = nil;
425 }
426
427 weak_grow_maybe(weak_table);
428 weak_entry_insert(weak_table, &new_entry);
429 }
430
431 // Do not set *referrer. objc_storeWeak() requires that the
432 // value not change.
433
434 return referent_id;
435 }
436
437
438 #if DEBUG
439 bool
440 weak_is_registered_no_lock(weak_table_t *weak_table, id referent_id)
441 {
442 return weak_entry_for_referent(weak_table, (objc_object *)referent_id);
443 }
444 #endif
445
446
447 /**
448 * Called by dealloc; nils out all weak pointers that point to the
449 * provided object so that they can no longer be used.
450 *
451 * @param weak_table
452 * @param referent The object being deallocated.
453 */
454 void
455 weak_clear_no_lock(weak_table_t *weak_table, id referent_id)
456 {
457 objc_object *referent = (objc_object *)referent_id;
458
459 weak_entry_t *entry = weak_entry_for_referent(weak_table, referent);
460 if (entry == nil) {
461 /// XXX shouldn't happen, but does with mismatched CF/objc
462 //printf("XXX no entry for clear deallocating %p\n", referent);
463 return;
464 }
465
466 // zero out references
467 weak_referrer_t *referrers;
468 size_t count;
469
470 if (entry->out_of_line) {
471 referrers = entry->referrers;
472 count = TABLE_SIZE(entry);
473 }
474 else {
475 referrers = entry->inline_referrers;
476 count = WEAK_INLINE_COUNT;
477 }
478
479 for (size_t i = 0; i < count; ++i) {
480 objc_object **referrer = referrers[i];
481 if (referrer) {
482 if (*referrer == referent) {
483 *referrer = nil;
484 }
485 else if (*referrer) {
486 _objc_inform("__weak variable at %p holds %p instead of %p. "
487 "This is probably incorrect use of "
488 "objc_storeWeak() and objc_loadWeak(). "
489 "Break on objc_weak_error to debug.\n",
490 referrer, (void*)*referrer, (void*)referent);
491 objc_weak_error();
492 }
493 }
494 }
495
496 weak_entry_remove(weak_table, entry);
497 }
498
499
500 /**
501 * This function gets called when the value of a weak pointer is being
502 * used in an expression. Called by objc_loadWeakRetained() which is
503 * ultimately called by objc_loadWeak(). The objective is to assert that
504 * there is in fact a weak pointer(s) entry for this particular object being
505 * stored in the weak-table, and to retain that object so it is not deallocated
506 * during the weak pointer's usage.
507 *
508 * @param weak_table
509 * @param referrer The weak pointer address.
510 */
511 /*
512 Once upon a time we eagerly cleared *referrer if we saw the referent
513 was deallocating. This confuses code like NSPointerFunctions which
514 tries to pre-flight the raw storage and assumes if the storage is
515 zero then the weak system is done interfering. That is false: the
516 weak system is still going to check and clear the storage later.
517 This can cause objc_weak_error complaints and crashes.
518 So we now don't touch the storage until deallocation completes.
519 */
520 id
521 weak_read_no_lock(weak_table_t *weak_table, id *referrer_id)
522 {
523 objc_object **referrer = (objc_object **)referrer_id;
524 objc_object *referent = *referrer;
525 if (referent->isTaggedPointer()) return (id)referent;
526
527 weak_entry_t *entry;
528 if (referent == nil ||
529 !(entry = weak_entry_for_referent(weak_table, referent)))
530 {
531 return nil;
532 }
533
534 if (! referent->ISA()->hasCustomRR()) {
535 if (! referent->rootTryRetain()) {
536 return nil;
537 }
538 }
539 else {
540 BOOL (*tryRetain)(objc_object *, SEL) = (BOOL(*)(objc_object *, SEL))
541 object_getMethodImplementation((id)referent,
542 SEL_retainWeakReference);
543 if ((IMP)tryRetain == _objc_msgForward) {
544 return nil;
545 }
546 if (! (*tryRetain)(referent, SEL_retainWeakReference)) {
547 return nil;
548 }
549 }
550
551 return (id)referent;
552 }
553