]>
Commit | Line | Data |
---|---|---|
8972963c A |
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 | ||
cd5f04f5 | 24 | #include "objc-private.h" |
8972963c | 25 | |
7257e56c A |
26 | #include "objc-weak.h" |
27 | ||
cd5f04f5 A |
28 | #include <stdint.h> |
29 | #include <stdbool.h> | |
30 | #include <sys/types.h> | |
31 | #include <libkern/OSAtomic.h> | |
8972963c | 32 | |
7257e56c | 33 | #define TABLE_SIZE(entry) (entry->mask ? entry->mask + 1 : 0) |
8972963c | 34 | |
8070259c A |
35 | static void append_referrer(weak_entry_t *entry, objc_object **new_referrer); |
36 | ||
37 | BREAKPOINT_FUNCTION( | |
38 | void objc_weak_error(void) | |
39 | ); | |
8972963c | 40 | |
c1e772c4 A |
41 | static void bad_weak_table(weak_entry_t *entries) |
42 | { | |
43 | _objc_fatal("bad weak table at %p. This may be a runtime bug or a " | |
44 | "memory error somewhere else.", entries); | |
45 | } | |
46 | ||
7257e56c A |
47 | /** |
48 | * Unique hash function for object pointers only. | |
49 | * | |
50 | * @param key The object pointer | |
51 | * | |
52 | * @return Size unrestricted hash of pointer. | |
53 | */ | |
8070259c A |
54 | static inline uintptr_t hash_pointer(objc_object *key) { |
55 | return ptr_hash((uintptr_t)key); | |
7257e56c | 56 | } |
8972963c | 57 | |
7257e56c A |
58 | /** |
59 | * Unique hash function for weak object pointers only. | |
60 | * | |
61 | * @param key The weak object pointer. | |
62 | * | |
63 | * @return Size unrestricted hash of pointer. | |
64 | */ | |
8070259c A |
65 | static inline uintptr_t w_hash_pointer(objc_object **key) { |
66 | return ptr_hash((uintptr_t)key); | |
7257e56c | 67 | } |
8972963c | 68 | |
7257e56c A |
69 | /** |
70 | * Grow the entry's hash table of referrers. Rehashes each | |
71 | * of the referrers. | |
72 | * | |
73 | * @param entry Weak pointer hash set for a particular object. | |
74 | */ | |
75 | __attribute__((noinline, used)) | |
8070259c A |
76 | static void grow_refs_and_insert(weak_entry_t *entry, |
77 | objc_object **new_referrer) | |
7257e56c | 78 | { |
1807f628 | 79 | ASSERT(entry->out_of_line()); |
8972963c | 80 | |
7257e56c A |
81 | size_t old_size = TABLE_SIZE(entry); |
82 | size_t new_size = old_size ? old_size * 2 : 8; | |
8972963c | 83 | |
7257e56c A |
84 | size_t num_refs = entry->num_refs; |
85 | weak_referrer_t *old_refs = entry->referrers; | |
86 | entry->mask = new_size - 1; | |
87 | ||
88 | entry->referrers = (weak_referrer_t *) | |
31875a97 | 89 | calloc(TABLE_SIZE(entry), sizeof(weak_referrer_t)); |
7257e56c A |
90 | entry->num_refs = 0; |
91 | entry->max_hash_displacement = 0; | |
92 | ||
93 | for (size_t i = 0; i < old_size && num_refs > 0; i++) { | |
94 | if (old_refs[i] != nil) { | |
95 | append_referrer(entry, old_refs[i]); | |
96 | num_refs--; | |
97 | } | |
8972963c | 98 | } |
7257e56c A |
99 | // Insert |
100 | append_referrer(entry, new_referrer); | |
31875a97 | 101 | if (old_refs) free(old_refs); |
7257e56c | 102 | } |
8972963c | 103 | |
7257e56c A |
104 | /** |
105 | * Add the given referrer to set of weak pointers in this entry. | |
106 | * Does not perform duplicate checking (b/c weak pointers are never | |
107 | * added to a set twice). | |
108 | * | |
109 | * @param entry The entry holding the set of weak pointers. | |
110 | * @param new_referrer The new weak pointer to be added. | |
111 | */ | |
8070259c | 112 | static void append_referrer(weak_entry_t *entry, objc_object **new_referrer) |
7257e56c | 113 | { |
c1e772c4 | 114 | if (! entry->out_of_line()) { |
7257e56c A |
115 | // Try to insert inline. |
116 | for (size_t i = 0; i < WEAK_INLINE_COUNT; i++) { | |
117 | if (entry->inline_referrers[i] == nil) { | |
118 | entry->inline_referrers[i] = new_referrer; | |
119 | return; | |
120 | } | |
121 | } | |
8972963c | 122 | |
7257e56c A |
123 | // Couldn't insert inline. Allocate out of line. |
124 | weak_referrer_t *new_referrers = (weak_referrer_t *) | |
31875a97 | 125 | calloc(WEAK_INLINE_COUNT, sizeof(weak_referrer_t)); |
7257e56c A |
126 | // This constructed table is invalid, but grow_refs_and_insert |
127 | // will fix it and rehash it. | |
128 | for (size_t i = 0; i < WEAK_INLINE_COUNT; i++) { | |
129 | new_referrers[i] = entry->inline_referrers[i]; | |
130 | } | |
131 | entry->referrers = new_referrers; | |
132 | entry->num_refs = WEAK_INLINE_COUNT; | |
c1e772c4 | 133 | entry->out_of_line_ness = REFERRERS_OUT_OF_LINE; |
7257e56c A |
134 | entry->mask = WEAK_INLINE_COUNT-1; |
135 | entry->max_hash_displacement = 0; | |
136 | } | |
8972963c | 137 | |
1807f628 | 138 | ASSERT(entry->out_of_line()); |
8972963c | 139 | |
7257e56c A |
140 | if (entry->num_refs >= TABLE_SIZE(entry) * 3/4) { |
141 | return grow_refs_and_insert(entry, new_referrer); | |
8972963c | 142 | } |
c1e772c4 A |
143 | size_t begin = w_hash_pointer(new_referrer) & (entry->mask); |
144 | size_t index = begin; | |
7257e56c | 145 | size_t hash_displacement = 0; |
c1e772c4 | 146 | while (entry->referrers[index] != nil) { |
7257e56c | 147 | hash_displacement++; |
c1e772c4 A |
148 | index = (index+1) & entry->mask; |
149 | if (index == begin) bad_weak_table(entry); | |
8972963c | 150 | } |
7257e56c A |
151 | if (hash_displacement > entry->max_hash_displacement) { |
152 | entry->max_hash_displacement = hash_displacement; | |
8972963c | 153 | } |
7257e56c A |
154 | weak_referrer_t &ref = entry->referrers[index]; |
155 | ref = new_referrer; | |
156 | entry->num_refs++; | |
157 | } | |
8972963c | 158 | |
7257e56c A |
159 | /** |
160 | * Remove old_referrer from set of referrers, if it's present. | |
161 | * Does not remove duplicates, because duplicates should not exist. | |
162 | * | |
8070259c | 163 | * @todo this is slow if old_referrer is not present. Is this ever the case? |
7257e56c A |
164 | * |
165 | * @param entry The entry holding the referrers. | |
166 | * @param old_referrer The referrer to remove. | |
167 | */ | |
8070259c | 168 | static void remove_referrer(weak_entry_t *entry, objc_object **old_referrer) |
7257e56c | 169 | { |
c1e772c4 | 170 | if (! entry->out_of_line()) { |
7257e56c A |
171 | for (size_t i = 0; i < WEAK_INLINE_COUNT; i++) { |
172 | if (entry->inline_referrers[i] == old_referrer) { | |
173 | entry->inline_referrers[i] = nil; | |
174 | return; | |
175 | } | |
176 | } | |
8070259c A |
177 | _objc_inform("Attempted to unregister unknown __weak variable " |
178 | "at %p. This is probably incorrect use of " | |
179 | "objc_storeWeak() and objc_loadWeak(). " | |
180 | "Break on objc_weak_error to debug.\n", | |
7257e56c | 181 | old_referrer); |
8070259c A |
182 | objc_weak_error(); |
183 | return; | |
7257e56c | 184 | } |
8972963c | 185 | |
c1e772c4 A |
186 | size_t begin = w_hash_pointer(old_referrer) & (entry->mask); |
187 | size_t index = begin; | |
7257e56c A |
188 | size_t hash_displacement = 0; |
189 | while (entry->referrers[index] != old_referrer) { | |
190 | index = (index+1) & entry->mask; | |
c1e772c4 | 191 | if (index == begin) bad_weak_table(entry); |
7257e56c A |
192 | hash_displacement++; |
193 | if (hash_displacement > entry->max_hash_displacement) { | |
8070259c A |
194 | _objc_inform("Attempted to unregister unknown __weak variable " |
195 | "at %p. This is probably incorrect use of " | |
196 | "objc_storeWeak() and objc_loadWeak(). " | |
197 | "Break on objc_weak_error to debug.\n", | |
7257e56c | 198 | old_referrer); |
8070259c | 199 | objc_weak_error(); |
7257e56c A |
200 | return; |
201 | } | |
202 | } | |
203 | entry->referrers[index] = nil; | |
204 | entry->num_refs--; | |
205 | } | |
8972963c | 206 | |
7257e56c A |
207 | /** |
208 | * Add new_entry to the object's table of weak references. | |
209 | * Does not check whether the referent is already in the table. | |
210 | */ | |
211 | static void weak_entry_insert(weak_table_t *weak_table, weak_entry_t *new_entry) | |
212 | { | |
213 | weak_entry_t *weak_entries = weak_table->weak_entries; | |
1807f628 | 214 | ASSERT(weak_entries != nil); |
8972963c | 215 | |
c1e772c4 A |
216 | size_t begin = hash_pointer(new_entry->referent) & (weak_table->mask); |
217 | size_t index = begin; | |
7257e56c A |
218 | size_t hash_displacement = 0; |
219 | while (weak_entries[index].referent != nil) { | |
220 | index = (index+1) & weak_table->mask; | |
c1e772c4 | 221 | if (index == begin) bad_weak_table(weak_entries); |
7257e56c A |
222 | hash_displacement++; |
223 | } | |
8972963c | 224 | |
7257e56c A |
225 | weak_entries[index] = *new_entry; |
226 | weak_table->num_entries++; | |
8972963c | 227 | |
7257e56c A |
228 | if (hash_displacement > weak_table->max_hash_displacement) { |
229 | weak_table->max_hash_displacement = hash_displacement; | |
230 | } | |
8972963c A |
231 | } |
232 | ||
8972963c | 233 | |
7257e56c | 234 | static void weak_resize(weak_table_t *weak_table, size_t new_size) |
8972963c | 235 | { |
7257e56c A |
236 | size_t old_size = TABLE_SIZE(weak_table); |
237 | ||
238 | weak_entry_t *old_entries = weak_table->weak_entries; | |
239 | weak_entry_t *new_entries = (weak_entry_t *) | |
31875a97 | 240 | calloc(new_size, sizeof(weak_entry_t)); |
7257e56c A |
241 | |
242 | weak_table->mask = new_size - 1; | |
243 | weak_table->weak_entries = new_entries; | |
244 | weak_table->max_hash_displacement = 0; | |
245 | weak_table->num_entries = 0; // restored by weak_entry_insert below | |
8972963c | 246 | |
7257e56c A |
247 | if (old_entries) { |
248 | weak_entry_t *entry; | |
249 | weak_entry_t *end = old_entries + old_size; | |
250 | for (entry = old_entries; entry < end; entry++) { | |
251 | if (entry->referent) { | |
252 | weak_entry_insert(weak_table, entry); | |
253 | } | |
8972963c | 254 | } |
31875a97 | 255 | free(old_entries); |
8972963c | 256 | } |
8972963c A |
257 | } |
258 | ||
7257e56c A |
259 | // Grow the given zone's table of weak references if it is full. |
260 | static void weak_grow_maybe(weak_table_t *weak_table) | |
8972963c | 261 | { |
7257e56c A |
262 | size_t old_size = TABLE_SIZE(weak_table); |
263 | ||
264 | // Grow if at least 3/4 full. | |
265 | if (weak_table->num_entries >= old_size * 3 / 4) { | |
266 | weak_resize(weak_table, old_size ? old_size*2 : 64); | |
8972963c | 267 | } |
8972963c A |
268 | } |
269 | ||
7257e56c A |
270 | // Shrink the table if it is mostly empty. |
271 | static void weak_compact_maybe(weak_table_t *weak_table) | |
8972963c | 272 | { |
7257e56c A |
273 | size_t old_size = TABLE_SIZE(weak_table); |
274 | ||
275 | // Shrink if larger than 1024 buckets and at most 1/16 full. | |
276 | if (old_size >= 1024 && old_size / 16 >= weak_table->num_entries) { | |
277 | weak_resize(weak_table, old_size / 8); | |
278 | // leaves new table no more than 1/2 full | |
8972963c | 279 | } |
8972963c A |
280 | } |
281 | ||
282 | ||
7257e56c A |
283 | /** |
284 | * Remove entry from the zone's table of weak references. | |
285 | */ | |
286 | static void weak_entry_remove(weak_table_t *weak_table, weak_entry_t *entry) | |
8972963c | 287 | { |
7257e56c | 288 | // remove entry |
c1e772c4 | 289 | if (entry->out_of_line()) free(entry->referrers); |
7257e56c | 290 | bzero(entry, sizeof(*entry)); |
8972963c | 291 | |
7257e56c | 292 | weak_table->num_entries--; |
8972963c | 293 | |
7257e56c | 294 | weak_compact_maybe(weak_table); |
8972963c A |
295 | } |
296 | ||
297 | ||
7257e56c A |
298 | /** |
299 | * Return the weak reference table entry for the given referent. | |
300 | * If there is no entry for referent, return NULL. | |
301 | * Performs a lookup. | |
302 | * | |
303 | * @param weak_table | |
304 | * @param referent The object. Must not be nil. | |
305 | * | |
306 | * @return The table of weak referrers to this object. | |
307 | */ | |
8070259c A |
308 | static weak_entry_t * |
309 | weak_entry_for_referent(weak_table_t *weak_table, objc_object *referent) | |
8972963c | 310 | { |
1807f628 | 311 | ASSERT(referent); |
8972963c | 312 | |
8972963c | 313 | weak_entry_t *weak_entries = weak_table->weak_entries; |
8972963c | 314 | |
7257e56c | 315 | if (!weak_entries) return nil; |
8972963c | 316 | |
c1e772c4 A |
317 | size_t begin = hash_pointer(referent) & weak_table->mask; |
318 | size_t index = begin; | |
7257e56c A |
319 | size_t hash_displacement = 0; |
320 | while (weak_table->weak_entries[index].referent != referent) { | |
321 | index = (index+1) & weak_table->mask; | |
c1e772c4 | 322 | if (index == begin) bad_weak_table(weak_table->weak_entries); |
7257e56c A |
323 | hash_displacement++; |
324 | if (hash_displacement > weak_table->max_hash_displacement) { | |
325 | return nil; | |
8972963c A |
326 | } |
327 | } | |
8972963c | 328 | |
7257e56c | 329 | return &weak_table->weak_entries[index]; |
8972963c A |
330 | } |
331 | ||
7257e56c A |
332 | /** |
333 | * Unregister an already-registered weak reference. | |
334 | * This is used when referrer's storage is about to go away, but referent | |
335 | * isn't dead yet. (Otherwise, zeroing referrer later would be a | |
336 | * bad memory access.) | |
337 | * Does nothing if referent/referrer is not a currently active weak reference. | |
338 | * Does not zero referrer. | |
339 | * | |
340 | * FIXME currently requires old referent value to be passed in (lame) | |
341 | * FIXME unregistration should be automatic if referrer is collected | |
342 | * | |
343 | * @param weak_table The global weak table. | |
344 | * @param referent The object. | |
345 | * @param referrer The weak reference. | |
346 | */ | |
8070259c A |
347 | void |
348 | weak_unregister_no_lock(weak_table_t *weak_table, id referent_id, | |
349 | id *referrer_id) | |
8972963c | 350 | { |
8070259c A |
351 | objc_object *referent = (objc_object *)referent_id; |
352 | objc_object **referrer = (objc_object **)referrer_id; | |
353 | ||
8972963c A |
354 | weak_entry_t *entry; |
355 | ||
7257e56c | 356 | if (!referent) return; |
8972963c | 357 | |
7257e56c A |
358 | if ((entry = weak_entry_for_referent(weak_table, referent))) { |
359 | remove_referrer(entry, referrer); | |
360 | bool empty = true; | |
c1e772c4 | 361 | if (entry->out_of_line() && entry->num_refs != 0) { |
7257e56c | 362 | empty = false; |
8972963c | 363 | } |
7257e56c A |
364 | else { |
365 | for (size_t i = 0; i < WEAK_INLINE_COUNT; i++) { | |
366 | if (entry->inline_referrers[i]) { | |
367 | empty = false; | |
368 | break; | |
8972963c A |
369 | } |
370 | } | |
371 | } | |
7257e56c A |
372 | |
373 | if (empty) { | |
374 | weak_entry_remove(weak_table, entry); | |
375 | } | |
8972963c | 376 | } |
8972963c | 377 | |
7257e56c A |
378 | // Do not set *referrer = nil. objc_storeWeak() requires that the |
379 | // value not change. | |
380 | } | |
8972963c | 381 | |
7257e56c A |
382 | /** |
383 | * Registers a new (object, weak pointer) pair. Creates a new weak | |
384 | * object entry if it does not exist. | |
385 | * | |
386 | * @param weak_table The global weak table. | |
387 | * @param referent The object pointed to by the weak reference. | |
388 | * @param referrer The weak pointer address. | |
389 | */ | |
390 | id | |
31875a97 | 391 | weak_register_no_lock(weak_table_t *weak_table, id referent_id, |
34d5b5e8 | 392 | id *referrer_id, WeakRegisterDeallocatingOptions deallocatingOptions) |
7257e56c | 393 | { |
8070259c A |
394 | objc_object *referent = (objc_object *)referent_id; |
395 | objc_object **referrer = (objc_object **)referrer_id; | |
396 | ||
34d5b5e8 | 397 | if (referent->isTaggedPointerOrNil()) return referent_id; |
8070259c A |
398 | |
399 | // ensure that the referenced object is viable | |
34d5b5e8 A |
400 | if (deallocatingOptions == ReturnNilIfDeallocating || |
401 | deallocatingOptions == CrashIfDeallocating) { | |
402 | bool deallocating; | |
403 | if (!referent->ISA()->hasCustomRR()) { | |
404 | deallocating = referent->rootIsDeallocating(); | |
8972963c | 405 | } |
34d5b5e8 A |
406 | else { |
407 | // Use lookUpImpOrForward so we can avoid the assert in | |
408 | // class_getInstanceMethod, since we intentionally make this | |
409 | // callout with the lock held. | |
410 | auto allowsWeakReference = (BOOL(*)(objc_object *, SEL)) | |
411 | lookUpImpOrForwardTryCache((id)referent, @selector(allowsWeakReference), | |
412 | referent->getIsa()); | |
413 | if ((IMP)allowsWeakReference == _objc_msgForward) { | |
414 | return nil; | |
415 | } | |
416 | deallocating = | |
1807f628 | 417 | ! (*allowsWeakReference)(referent, @selector(allowsWeakReference)); |
34d5b5e8 | 418 | } |
7257e56c | 419 | |
34d5b5e8 A |
420 | if (deallocating) { |
421 | if (deallocatingOptions == CrashIfDeallocating) { | |
422 | _objc_fatal("Cannot form weak reference to instance (%p) of " | |
423 | "class %s. It is possible that this object was " | |
424 | "over-released, or is in the process of deallocation.", | |
425 | (void*)referent, object_getClassName((id)referent)); | |
426 | } else { | |
427 | return nil; | |
428 | } | |
31875a97 | 429 | } |
8070259c A |
430 | } |
431 | ||
432 | // now remember it and where it is being stored | |
433 | weak_entry_t *entry; | |
434 | if ((entry = weak_entry_for_referent(weak_table, referent))) { | |
435 | append_referrer(entry, referrer); | |
436 | } | |
437 | else { | |
c1e772c4 | 438 | weak_entry_t new_entry(referent, referrer); |
8070259c A |
439 | weak_grow_maybe(weak_table); |
440 | weak_entry_insert(weak_table, &new_entry); | |
8972963c A |
441 | } |
442 | ||
443 | // Do not set *referrer. objc_storeWeak() requires that the | |
444 | // value not change. | |
445 | ||
8070259c A |
446 | return referent_id; |
447 | } | |
448 | ||
449 | ||
31875a97 | 450 | #if DEBUG |
8070259c A |
451 | bool |
452 | weak_is_registered_no_lock(weak_table_t *weak_table, id referent_id) | |
453 | { | |
454 | return weak_entry_for_referent(weak_table, (objc_object *)referent_id); | |
8972963c | 455 | } |
8070259c A |
456 | #endif |
457 | ||
8972963c | 458 | |
7257e56c A |
459 | /** |
460 | * Called by dealloc; nils out all weak pointers that point to the | |
461 | * provided object so that they can no longer be used. | |
462 | * | |
463 | * @param weak_table | |
464 | * @param referent The object being deallocated. | |
465 | */ | |
466 | void | |
8070259c | 467 | weak_clear_no_lock(weak_table_t *weak_table, id referent_id) |
7257e56c | 468 | { |
8070259c A |
469 | objc_object *referent = (objc_object *)referent_id; |
470 | ||
7257e56c A |
471 | weak_entry_t *entry = weak_entry_for_referent(weak_table, referent); |
472 | if (entry == nil) { | |
473 | /// XXX shouldn't happen, but does with mismatched CF/objc | |
474 | //printf("XXX no entry for clear deallocating %p\n", referent); | |
475 | return; | |
476 | } | |
8972963c | 477 | |
7257e56c A |
478 | // zero out references |
479 | weak_referrer_t *referrers; | |
480 | size_t count; | |
481 | ||
c1e772c4 | 482 | if (entry->out_of_line()) { |
7257e56c A |
483 | referrers = entry->referrers; |
484 | count = TABLE_SIZE(entry); | |
485 | } | |
486 | else { | |
487 | referrers = entry->inline_referrers; | |
488 | count = WEAK_INLINE_COUNT; | |
489 | } | |
490 | ||
491 | for (size_t i = 0; i < count; ++i) { | |
8070259c | 492 | objc_object **referrer = referrers[i]; |
7257e56c A |
493 | if (referrer) { |
494 | if (*referrer == referent) { | |
495 | *referrer = nil; | |
496 | } | |
497 | else if (*referrer) { | |
8070259c A |
498 | _objc_inform("__weak variable at %p holds %p instead of %p. " |
499 | "This is probably incorrect use of " | |
500 | "objc_storeWeak() and objc_loadWeak(). " | |
501 | "Break on objc_weak_error to debug.\n", | |
502 | referrer, (void*)*referrer, (void*)referent); | |
503 | objc_weak_error(); | |
8972963c | 504 | } |
8972963c A |
505 | } |
506 | } | |
7257e56c A |
507 | |
508 | weak_entry_remove(weak_table, entry); | |
509 | } | |
510 |