]>
Commit | Line | Data |
---|---|---|
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 | 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 | ||
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 | */ | |
54 | static inline uintptr_t hash_pointer(objc_object *key) { | |
55 | return ptr_hash((uintptr_t)key); | |
56 | } | |
57 | ||
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 | */ | |
65 | static inline uintptr_t w_hash_pointer(objc_object **key) { | |
66 | return ptr_hash((uintptr_t)key); | |
67 | } | |
68 | ||
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)) | |
76 | static void grow_refs_and_insert(weak_entry_t *entry, | |
77 | objc_object **new_referrer) | |
78 | { | |
79 | ASSERT(entry->out_of_line()); | |
80 | ||
81 | size_t old_size = TABLE_SIZE(entry); | |
82 | size_t new_size = old_size ? old_size * 2 : 8; | |
83 | ||
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 *) | |
89 | calloc(TABLE_SIZE(entry), sizeof(weak_referrer_t)); | |
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 | } | |
98 | } | |
99 | // Insert | |
100 | append_referrer(entry, new_referrer); | |
101 | if (old_refs) free(old_refs); | |
102 | } | |
103 | ||
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 | */ | |
112 | static void append_referrer(weak_entry_t *entry, objc_object **new_referrer) | |
113 | { | |
114 | if (! entry->out_of_line()) { | |
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 | } | |
122 | ||
123 | // Couldn't insert inline. Allocate out of line. | |
124 | weak_referrer_t *new_referrers = (weak_referrer_t *) | |
125 | calloc(WEAK_INLINE_COUNT, sizeof(weak_referrer_t)); | |
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; | |
133 | entry->out_of_line_ness = REFERRERS_OUT_OF_LINE; | |
134 | entry->mask = WEAK_INLINE_COUNT-1; | |
135 | entry->max_hash_displacement = 0; | |
136 | } | |
137 | ||
138 | ASSERT(entry->out_of_line()); | |
139 | ||
140 | if (entry->num_refs >= TABLE_SIZE(entry) * 3/4) { | |
141 | return grow_refs_and_insert(entry, new_referrer); | |
142 | } | |
143 | size_t begin = w_hash_pointer(new_referrer) & (entry->mask); | |
144 | size_t index = begin; | |
145 | size_t hash_displacement = 0; | |
146 | while (entry->referrers[index] != nil) { | |
147 | hash_displacement++; | |
148 | index = (index+1) & entry->mask; | |
149 | if (index == begin) bad_weak_table(entry); | |
150 | } | |
151 | if (hash_displacement > entry->max_hash_displacement) { | |
152 | entry->max_hash_displacement = hash_displacement; | |
153 | } | |
154 | weak_referrer_t &ref = entry->referrers[index]; | |
155 | ref = new_referrer; | |
156 | entry->num_refs++; | |
157 | } | |
158 | ||
159 | /** | |
160 | * Remove old_referrer from set of referrers, if it's present. | |
161 | * Does not remove duplicates, because duplicates should not exist. | |
162 | * | |
163 | * @todo this is slow if old_referrer is not present. Is this ever the case? | |
164 | * | |
165 | * @param entry The entry holding the referrers. | |
166 | * @param old_referrer The referrer to remove. | |
167 | */ | |
168 | static void remove_referrer(weak_entry_t *entry, objc_object **old_referrer) | |
169 | { | |
170 | if (! entry->out_of_line()) { | |
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 | } | |
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", | |
181 | old_referrer); | |
182 | objc_weak_error(); | |
183 | return; | |
184 | } | |
185 | ||
186 | size_t begin = w_hash_pointer(old_referrer) & (entry->mask); | |
187 | size_t index = begin; | |
188 | size_t hash_displacement = 0; | |
189 | while (entry->referrers[index] != old_referrer) { | |
190 | index = (index+1) & entry->mask; | |
191 | if (index == begin) bad_weak_table(entry); | |
192 | hash_displacement++; | |
193 | if (hash_displacement > entry->max_hash_displacement) { | |
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", | |
198 | old_referrer); | |
199 | objc_weak_error(); | |
200 | return; | |
201 | } | |
202 | } | |
203 | entry->referrers[index] = nil; | |
204 | entry->num_refs--; | |
205 | } | |
206 | ||
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; | |
214 | ASSERT(weak_entries != nil); | |
215 | ||
216 | size_t begin = hash_pointer(new_entry->referent) & (weak_table->mask); | |
217 | size_t index = begin; | |
218 | size_t hash_displacement = 0; | |
219 | while (weak_entries[index].referent != nil) { | |
220 | index = (index+1) & weak_table->mask; | |
221 | if (index == begin) bad_weak_table(weak_entries); | |
222 | hash_displacement++; | |
223 | } | |
224 | ||
225 | weak_entries[index] = *new_entry; | |
226 | weak_table->num_entries++; | |
227 | ||
228 | if (hash_displacement > weak_table->max_hash_displacement) { | |
229 | weak_table->max_hash_displacement = hash_displacement; | |
230 | } | |
231 | } | |
232 | ||
233 | ||
234 | static void weak_resize(weak_table_t *weak_table, size_t new_size) | |
235 | { | |
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 *) | |
240 | calloc(new_size, sizeof(weak_entry_t)); | |
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 | |
246 | ||
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 | } | |
254 | } | |
255 | free(old_entries); | |
256 | } | |
257 | } | |
258 | ||
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) | |
261 | { | |
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); | |
267 | } | |
268 | } | |
269 | ||
270 | // Shrink the table if it is mostly empty. | |
271 | static void weak_compact_maybe(weak_table_t *weak_table) | |
272 | { | |
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 | |
279 | } | |
280 | } | |
281 | ||
282 | ||
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) | |
287 | { | |
288 | // remove entry | |
289 | if (entry->out_of_line()) free(entry->referrers); | |
290 | bzero(entry, sizeof(*entry)); | |
291 | ||
292 | weak_table->num_entries--; | |
293 | ||
294 | weak_compact_maybe(weak_table); | |
295 | } | |
296 | ||
297 | ||
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 | */ | |
308 | static weak_entry_t * | |
309 | weak_entry_for_referent(weak_table_t *weak_table, objc_object *referent) | |
310 | { | |
311 | ASSERT(referent); | |
312 | ||
313 | weak_entry_t *weak_entries = weak_table->weak_entries; | |
314 | ||
315 | if (!weak_entries) return nil; | |
316 | ||
317 | size_t begin = hash_pointer(referent) & weak_table->mask; | |
318 | size_t index = begin; | |
319 | size_t hash_displacement = 0; | |
320 | while (weak_table->weak_entries[index].referent != referent) { | |
321 | index = (index+1) & weak_table->mask; | |
322 | if (index == begin) bad_weak_table(weak_table->weak_entries); | |
323 | hash_displacement++; | |
324 | if (hash_displacement > weak_table->max_hash_displacement) { | |
325 | return nil; | |
326 | } | |
327 | } | |
328 | ||
329 | return &weak_table->weak_entries[index]; | |
330 | } | |
331 | ||
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 | */ | |
347 | void | |
348 | weak_unregister_no_lock(weak_table_t *weak_table, id referent_id, | |
349 | id *referrer_id) | |
350 | { | |
351 | objc_object *referent = (objc_object *)referent_id; | |
352 | objc_object **referrer = (objc_object **)referrer_id; | |
353 | ||
354 | weak_entry_t *entry; | |
355 | ||
356 | if (!referent) return; | |
357 | ||
358 | if ((entry = weak_entry_for_referent(weak_table, referent))) { | |
359 | remove_referrer(entry, referrer); | |
360 | bool empty = true; | |
361 | if (entry->out_of_line() && entry->num_refs != 0) { | |
362 | empty = false; | |
363 | } | |
364 | else { | |
365 | for (size_t i = 0; i < WEAK_INLINE_COUNT; i++) { | |
366 | if (entry->inline_referrers[i]) { | |
367 | empty = false; | |
368 | break; | |
369 | } | |
370 | } | |
371 | } | |
372 | ||
373 | if (empty) { | |
374 | weak_entry_remove(weak_table, entry); | |
375 | } | |
376 | } | |
377 | ||
378 | // Do not set *referrer = nil. objc_storeWeak() requires that the | |
379 | // value not change. | |
380 | } | |
381 | ||
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 | |
391 | weak_register_no_lock(weak_table_t *weak_table, id referent_id, | |
392 | id *referrer_id, WeakRegisterDeallocatingOptions deallocatingOptions) | |
393 | { | |
394 | objc_object *referent = (objc_object *)referent_id; | |
395 | objc_object **referrer = (objc_object **)referrer_id; | |
396 | ||
397 | if (referent->isTaggedPointerOrNil()) return referent_id; | |
398 | ||
399 | // ensure that the referenced object is viable | |
400 | if (deallocatingOptions == ReturnNilIfDeallocating || | |
401 | deallocatingOptions == CrashIfDeallocating) { | |
402 | bool deallocating; | |
403 | if (!referent->ISA()->hasCustomRR()) { | |
404 | deallocating = referent->rootIsDeallocating(); | |
405 | } | |
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 = | |
417 | ! (*allowsWeakReference)(referent, @selector(allowsWeakReference)); | |
418 | } | |
419 | ||
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 | } | |
429 | } | |
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 { | |
438 | weak_entry_t new_entry(referent, referrer); | |
439 | weak_grow_maybe(weak_table); | |
440 | weak_entry_insert(weak_table, &new_entry); | |
441 | } | |
442 | ||
443 | // Do not set *referrer. objc_storeWeak() requires that the | |
444 | // value not change. | |
445 | ||
446 | return referent_id; | |
447 | } | |
448 | ||
449 | ||
450 | #if DEBUG | |
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); | |
455 | } | |
456 | #endif | |
457 | ||
458 | ||
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 | |
467 | weak_clear_no_lock(weak_table_t *weak_table, id referent_id) | |
468 | { | |
469 | objc_object *referent = (objc_object *)referent_id; | |
470 | ||
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 | } | |
477 | ||
478 | // zero out references | |
479 | weak_referrer_t *referrers; | |
480 | size_t count; | |
481 | ||
482 | if (entry->out_of_line()) { | |
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) { | |
492 | objc_object **referrer = referrers[i]; | |
493 | if (referrer) { | |
494 | if (*referrer == referent) { | |
495 | *referrer = nil; | |
496 | } | |
497 | else if (*referrer) { | |
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(); | |
504 | } | |
505 | } | |
506 | } | |
507 | ||
508 | weak_entry_remove(weak_table, entry); | |
509 | } | |
510 |