]>
Commit | Line | Data |
---|---|---|
7af964d1 A |
1 | /* |
2 | * Copyright (c) 2004-2007 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 | Implementation of the weak / associative references for non-GC mode. | |
25 | */ | |
26 | ||
27 | ||
28 | #include "objc-private.h" | |
29 | #include <objc/message.h> | |
fe06a513 | 30 | #include <map> |
1807f628 | 31 | #include "DenseMapExtras.h" |
7af964d1 | 32 | |
1807f628 | 33 | // expanded policy bits. |
cd5f04f5 | 34 | |
1807f628 A |
35 | enum { |
36 | OBJC_ASSOCIATION_SETTER_ASSIGN = 0, | |
37 | OBJC_ASSOCIATION_SETTER_RETAIN = 1, | |
38 | OBJC_ASSOCIATION_SETTER_COPY = 3, // NOTE: both bits are set, so we can simply test 1 bit in releaseValue below. | |
39 | OBJC_ASSOCIATION_GETTER_READ = (0 << 8), | |
40 | OBJC_ASSOCIATION_GETTER_RETAIN = (1 << 8), | |
34d5b5e8 A |
41 | OBJC_ASSOCIATION_GETTER_AUTORELEASE = (2 << 8), |
42 | OBJC_ASSOCIATION_SYSTEM_OBJECT = _OBJC_ASSOCIATION_SYSTEM_OBJECT, // 1 << 16 | |
1807f628 | 43 | }; |
7af964d1 | 44 | |
1807f628 | 45 | spinlock_t AssociationsManagerLock; |
7af964d1 | 46 | |
1807f628 | 47 | namespace objc { |
7af964d1 | 48 | |
1807f628 A |
49 | class ObjcAssociation { |
50 | uintptr_t _policy; | |
51 | id _value; | |
52 | public: | |
53 | ObjcAssociation(uintptr_t policy, id value) : _policy(policy), _value(value) {} | |
54 | ObjcAssociation() : _policy(0), _value(nil) {} | |
55 | ObjcAssociation(const ObjcAssociation &other) = default; | |
56 | ObjcAssociation &operator=(const ObjcAssociation &other) = default; | |
57 | ObjcAssociation(ObjcAssociation &&other) : ObjcAssociation() { | |
58 | swap(other); | |
59 | } | |
60 | ||
61 | inline void swap(ObjcAssociation &other) { | |
62 | std::swap(_policy, other._policy); | |
63 | std::swap(_value, other._value); | |
64 | } | |
7af964d1 | 65 | |
1807f628 A |
66 | inline uintptr_t policy() const { return _policy; } |
67 | inline id value() const { return _value; } | |
68 | ||
69 | inline void acquireValue() { | |
70 | if (_value) { | |
71 | switch (_policy & 0xFF) { | |
72 | case OBJC_ASSOCIATION_SETTER_RETAIN: | |
73 | _value = objc_retain(_value); | |
74 | break; | |
75 | case OBJC_ASSOCIATION_SETTER_COPY: | |
76 | _value = ((id(*)(id, SEL))objc_msgSend)(_value, @selector(copy)); | |
77 | break; | |
78 | } | |
79 | } | |
80 | } | |
7af964d1 | 81 | |
1807f628 A |
82 | inline void releaseHeldValue() { |
83 | if (_value && (_policy & OBJC_ASSOCIATION_SETTER_RETAIN)) { | |
84 | objc_release(_value); | |
7af964d1 | 85 | } |
1807f628 | 86 | } |
7af964d1 | 87 | |
1807f628 A |
88 | inline void retainReturnedValue() { |
89 | if (_value && (_policy & OBJC_ASSOCIATION_GETTER_RETAIN)) { | |
90 | objc_retain(_value); | |
7af964d1 | 91 | } |
1807f628 | 92 | } |
7af964d1 | 93 | |
1807f628 A |
94 | inline id autoreleaseReturnedValue() { |
95 | if (slowpath(_value && (_policy & OBJC_ASSOCIATION_GETTER_AUTORELEASE))) { | |
96 | return objc_autorelease(_value); | |
97 | } | |
98 | return _value; | |
99 | } | |
100 | }; | |
7af964d1 | 101 | |
1807f628 A |
102 | typedef DenseMap<const void *, ObjcAssociation> ObjectAssociationMap; |
103 | typedef DenseMap<DisguisedPtr<objc_object>, ObjectAssociationMap> AssociationsHashMap; | |
7af964d1 A |
104 | |
105 | // class AssociationsManager manages a lock / hash table singleton pair. | |
1807f628 | 106 | // Allocating an instance acquires the lock |
7af964d1 A |
107 | |
108 | class AssociationsManager { | |
1807f628 A |
109 | using Storage = ExplicitInitDenseMap<DisguisedPtr<objc_object>, ObjectAssociationMap>; |
110 | static Storage _mapStorage; | |
111 | ||
7af964d1 | 112 | public: |
bd8dfcfc A |
113 | AssociationsManager() { AssociationsManagerLock.lock(); } |
114 | ~AssociationsManager() { AssociationsManagerLock.unlock(); } | |
1807f628 A |
115 | |
116 | AssociationsHashMap &get() { | |
117 | return _mapStorage.get(); | |
118 | } | |
119 | ||
120 | static void init() { | |
121 | _mapStorage.init(); | |
7af964d1 A |
122 | } |
123 | }; | |
124 | ||
1807f628 | 125 | AssociationsManager::Storage AssociationsManager::_mapStorage; |
7af964d1 | 126 | |
1807f628 | 127 | } // namespace objc |
7af964d1 | 128 | |
1807f628 A |
129 | using namespace objc; |
130 | ||
131 | void | |
132 | _objc_associations_init() | |
133 | { | |
134 | AssociationsManager::init(); | |
135 | } | |
136 | ||
137 | id | |
138 | _object_get_associative_reference(id object, const void *key) | |
139 | { | |
140 | ObjcAssociation association{}; | |
7af964d1 | 141 | |
7af964d1 A |
142 | { |
143 | AssociationsManager manager; | |
1807f628 A |
144 | AssociationsHashMap &associations(manager.get()); |
145 | AssociationsHashMap::iterator i = associations.find((objc_object *)object); | |
7af964d1 | 146 | if (i != associations.end()) { |
1807f628 A |
147 | ObjectAssociationMap &refs = i->second; |
148 | ObjectAssociationMap::iterator j = refs.find(key); | |
149 | if (j != refs.end()) { | |
150 | association = j->second; | |
151 | association.retainReturnedValue(); | |
7af964d1 A |
152 | } |
153 | } | |
154 | } | |
7af964d1 | 155 | |
1807f628 | 156 | return association.autoreleaseReturnedValue(); |
7af964d1 A |
157 | } |
158 | ||
1807f628 A |
159 | void |
160 | _object_set_associative_reference(id object, const void *key, id value, uintptr_t policy) | |
161 | { | |
13ba007e A |
162 | // This code used to work when nil was passed for object and key. Some code |
163 | // probably relies on that to not crash. Check and handle it explicitly. | |
164 | // rdar://problem/44094390 | |
165 | if (!object && !value) return; | |
1807f628 | 166 | |
13ba007e A |
167 | if (object->getIsa()->forbidsAssociatedObjects()) |
168 | _objc_fatal("objc_setAssociatedObject called on instance (%p) of class %s which does not allow associated objects", object, object_getClassName(object)); | |
1807f628 A |
169 | |
170 | DisguisedPtr<objc_object> disguised{(objc_object *)object}; | |
171 | ObjcAssociation association{policy, value}; | |
172 | ||
7af964d1 | 173 | // retain the new value (if any) outside the lock. |
1807f628 A |
174 | association.acquireValue(); |
175 | ||
34d5b5e8 | 176 | bool isFirstAssociation = false; |
7af964d1 A |
177 | { |
178 | AssociationsManager manager; | |
1807f628 A |
179 | AssociationsHashMap &associations(manager.get()); |
180 | ||
181 | if (value) { | |
182 | auto refs_result = associations.try_emplace(disguised, ObjectAssociationMap{}); | |
183 | if (refs_result.second) { | |
184 | /* it's the first association we make */ | |
34d5b5e8 | 185 | isFirstAssociation = true; |
7af964d1 | 186 | } |
1807f628 A |
187 | |
188 | /* establish or replace the association */ | |
189 | auto &refs = refs_result.first->second; | |
190 | auto result = refs.try_emplace(key, std::move(association)); | |
191 | if (!result.second) { | |
192 | association.swap(result.first->second); | |
193 | } | |
7af964d1 | 194 | } else { |
1807f628 A |
195 | auto refs_it = associations.find(disguised); |
196 | if (refs_it != associations.end()) { | |
197 | auto &refs = refs_it->second; | |
198 | auto it = refs.find(key); | |
199 | if (it != refs.end()) { | |
200 | association.swap(it->second); | |
201 | refs.erase(it); | |
202 | if (refs.size() == 0) { | |
203 | associations.erase(refs_it); | |
204 | ||
205 | } | |
7af964d1 A |
206 | } |
207 | } | |
208 | } | |
209 | } | |
1807f628 | 210 | |
34d5b5e8 A |
211 | // Call setHasAssociatedObjects outside the lock, since this |
212 | // will call the object's _noteAssociatedObjects method if it | |
213 | // has one, and this may trigger +initialize which might do | |
214 | // arbitrary stuff, including setting more associated objects. | |
215 | if (isFirstAssociation) | |
216 | object->setHasAssociatedObjects(); | |
217 | ||
7af964d1 | 218 | // release the old value (outside of the lock). |
1807f628 | 219 | association.releaseHeldValue(); |
7af964d1 A |
220 | } |
221 | ||
1807f628 A |
222 | // Unlike setting/getting an associated reference, |
223 | // this function is performance sensitive because of | |
224 | // raw isa objects (such as OS Objects) that can't track | |
225 | // whether they have associated objects. | |
226 | void | |
34d5b5e8 | 227 | _object_remove_assocations(id object, bool deallocating) |
1807f628 A |
228 | { |
229 | ObjectAssociationMap refs{}; | |
230 | ||
7af964d1 A |
231 | { |
232 | AssociationsManager manager; | |
1807f628 A |
233 | AssociationsHashMap &associations(manager.get()); |
234 | AssociationsHashMap::iterator i = associations.find((objc_object *)object); | |
7af964d1 | 235 | if (i != associations.end()) { |
1807f628 | 236 | refs.swap(i->second); |
34d5b5e8 A |
237 | |
238 | // If we are not deallocating, then SYSTEM_OBJECT associations are preserved. | |
239 | bool didReInsert = false; | |
240 | if (!deallocating) { | |
241 | for (auto &ref: refs) { | |
242 | if (ref.second.policy() & OBJC_ASSOCIATION_SYSTEM_OBJECT) { | |
243 | i->second.insert(ref); | |
244 | didReInsert = true; | |
245 | } | |
246 | } | |
247 | } | |
248 | if (!didReInsert) | |
249 | associations.erase(i); | |
7af964d1 A |
250 | } |
251 | } | |
1807f628 | 252 | |
34d5b5e8 A |
253 | // Associations to be released after the normal ones. |
254 | SmallVector<ObjcAssociation *, 4> laterRefs; | |
255 | ||
1807f628 A |
256 | // release everything (outside of the lock). |
257 | for (auto &i: refs) { | |
34d5b5e8 A |
258 | if (i.second.policy() & OBJC_ASSOCIATION_SYSTEM_OBJECT) { |
259 | // If we are not deallocating, then RELEASE_LATER associations don't get released. | |
260 | if (deallocating) | |
261 | laterRefs.append(&i.second); | |
262 | } else { | |
263 | i.second.releaseHeldValue(); | |
264 | } | |
265 | } | |
266 | for (auto *later: laterRefs) { | |
267 | later->releaseHeldValue(); | |
1807f628 | 268 | } |
7af964d1 | 269 | } |