--- /dev/null
+/*
+ * Copyright (c) 2004-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@
+ */
+/*
+ Implementation of the weak / associative references for non-GC mode.
+*/
+
+
+#include "objc-private.h"
+#include <objc/message.h>
+
+
+// wrap all the murky C++ details in a namespace to get them out of the way.
+
+namespace objc_references_support {
+ struct ObjcPointerEqual {
+ bool operator()(void *p1, void *p2) const {
+ return p1 == p2;
+ }
+ };
+
+ struct ObjcPointerClear {
+ void operator() (void *pointer) {
+ void **location = (void **)pointer;
+ *location = NULL;
+ }
+ };
+
+ struct ObjcPointerHash {
+ uintptr_t operator()(void *p) const {
+ uintptr_t k = (uintptr_t)p;
+
+ // borrowed from CFSet.c
+ #if __LP64__
+ uintptr_t a = 0x4368726973746F70ULL;
+ uintptr_t b = 0x686572204B616E65ULL;
+ #else
+ uintptr_t a = 0x4B616E65UL;
+ uintptr_t b = 0x4B616E65UL;
+ #endif
+ uintptr_t c = 1;
+ a += k;
+ #if __LP64__
+ a -= b; a -= c; a ^= (c >> 43);
+ b -= c; b -= a; b ^= (a << 9);
+ c -= a; c -= b; c ^= (b >> 8);
+ a -= b; a -= c; a ^= (c >> 38);
+ b -= c; b -= a; b ^= (a << 23);
+ c -= a; c -= b; c ^= (b >> 5);
+ a -= b; a -= c; a ^= (c >> 35);
+ b -= c; b -= a; b ^= (a << 49);
+ c -= a; c -= b; c ^= (b >> 11);
+ a -= b; a -= c; a ^= (c >> 12);
+ b -= c; b -= a; b ^= (a << 18);
+ c -= a; c -= b; c ^= (b >> 22);
+ #else
+ a -= b; a -= c; a ^= (c >> 13);
+ b -= c; b -= a; b ^= (a << 8);
+ c -= a; c -= b; c ^= (b >> 13);
+ a -= b; a -= c; a ^= (c >> 12);
+ b -= c; b -= a; b ^= (a << 16);
+ c -= a; c -= b; c ^= (b >> 5);
+ a -= b; a -= c; a ^= (c >> 3);
+ b -= c; b -= a; b ^= (a << 10);
+ c -= a; c -= b; c ^= (b >> 15);
+ #endif
+ return c;
+ }
+ };
+
+ // STL allocator that uses the runtime's internal allocator.
+
+ template <typename T> struct ObjcAllocator {
+ typedef T value_type;
+ typedef value_type* pointer;
+ typedef const value_type *const_pointer;
+ typedef value_type& reference;
+ typedef const value_type& const_reference;
+ typedef size_t size_type;
+ typedef ptrdiff_t difference_type;
+
+ template <typename U> struct rebind { typedef ObjcAllocator<U> other; };
+
+ template <typename U> ObjcAllocator(const ObjcAllocator<U>&) {}
+ ObjcAllocator() {}
+ ObjcAllocator(const ObjcAllocator&) {}
+ ~ObjcAllocator() {}
+
+ pointer address(reference x) const { return &x; }
+ const_pointer address(const_reference x) const {
+ return x;
+ }
+
+ pointer allocate(size_type n, const_pointer = 0) {
+ return static_cast<pointer>(::_malloc_internal(n * sizeof(T)));
+ }
+
+ void deallocate(pointer p, size_type) { ::_free_internal(p); }
+
+ size_type max_size() const {
+ return static_cast<size_type>(-1) / sizeof(T);
+ }
+
+ void construct(pointer p, const value_type& x) {
+ new(p) value_type(x);
+ }
+
+ void destroy(pointer p) { p->~value_type(); }
+
+ void operator=(const ObjcAllocator&);
+
+ };
+
+ template<> struct ObjcAllocator<void> {
+ typedef void value_type;
+ typedef void* pointer;
+ typedef const void *const_pointer;
+ template <typename U> struct rebind { typedef ObjcAllocator<U> other; };
+ };
+
+ struct ObjcAssociation {
+ uintptr_t policy;
+ id value;
+ ObjcAssociation(uintptr_t newPolicy, id newValue) : policy(newPolicy), value(newValue) { }
+ ObjcAssociation() : policy(0), value(0) { }
+ };
+
+ // typedef vector<void *, ObjcAllocator<void *> > PtrVector;
+ // typedef hash_set<void *, ObjcPointerHash, ObjcPointerEqual, ObjcAllocator<void *> > PtrHashSet;
+ // typedef hash_map<void *, void *, ObjcPointerHash, ObjcPointerEqual, ObjcAllocator<void *> > PtrPtrHashMap;
+#if TARGET_OS_WIN32
+ typedef hash_map<void *, ObjcAssociation> ObjectAssocationHashMap;
+ typedef hash_map<void *, ObjectAssocationHashMap> AssocationsHashMap;
+#else
+ typedef hash_map<void *, ObjcAssociation, ObjcPointerHash, ObjcPointerEqual, ObjcAllocator<void *> > ObjectAssocationHashMap;
+ typedef hash_map<void *, ObjectAssocationHashMap, ObjcPointerHash, ObjcPointerEqual, ObjcAllocator<void *> > AssocationsHashMap;
+#endif
+}
+
+using namespace objc_references_support;
+
+// class AssociationsManager manages a lock / hash table singleton pair.
+// Allocating an instance acquires the lock, and calling its assocations() method
+// lazily allocates it.
+
+class AssociationsManager {
+ static OSSpinLock _lock;
+ static AssocationsHashMap *_map; // associative references: object pointer -> PtrPtrHashMap.
+public:
+ AssociationsManager() { OSSpinLockLock(&_lock); }
+ ~AssociationsManager() { OSSpinLockUnlock(&_lock); }
+
+ AssocationsHashMap &associations() {
+ if (_map == NULL)
+ _map = new(::_malloc_internal(sizeof(AssocationsHashMap))) AssocationsHashMap();
+ return *_map;
+ }
+};
+
+OSSpinLock AssociationsManager::_lock = OS_SPINLOCK_INIT;
+AssocationsHashMap *AssociationsManager::_map = NULL;
+
+// expanded policy bits.
+
+enum {
+ OBJC_ASSOCIATION_SETTER_ASSIGN = 0,
+ OBJC_ASSOCIATION_SETTER_RETAIN = 1,
+ OBJC_ASSOCIATION_SETTER_COPY = 3, // NOTE: both bits are set, so we can simply test 1 bit in releaseValue below.
+ OBJC_ASSOCIATION_GETTER_READ = (0 << 8),
+ OBJC_ASSOCIATION_GETTER_RETAIN = (1 << 8),
+ OBJC_ASSOCIATION_GETTER_AUTORELEASE = (2 << 8)
+};
+
+__private_extern__ id _object_get_associative_reference(id object, void *key) {
+ id value = nil;
+ uintptr_t policy = OBJC_ASSOCIATION_ASSIGN;
+ {
+ AssociationsManager manager;
+ AssocationsHashMap &associations(manager.associations());
+ AssocationsHashMap::iterator i = associations.find(object);
+ if (i != associations.end()) {
+ ObjectAssocationHashMap &refs = i->second;
+ ObjectAssocationHashMap::iterator j = refs.find(key);
+ if (j != refs.end()) {
+ ObjcAssociation &entry = j->second;
+ value = (id)entry.value;
+ policy = entry.policy;
+ if (policy & OBJC_ASSOCIATION_GETTER_RETAIN) objc_msgSend(value, SEL_retain);
+ }
+ }
+ }
+ if (value && (policy & OBJC_ASSOCIATION_GETTER_AUTORELEASE)) {
+ objc_msgSend(value, SEL_autorelease);
+ }
+ return value;
+}
+
+static id acquireValue(id value, uintptr_t policy) {
+ switch (policy & 0xFF) {
+ case OBJC_ASSOCIATION_SETTER_RETAIN:
+ return objc_msgSend(value, SEL_retain);
+ case OBJC_ASSOCIATION_SETTER_COPY:
+ return objc_msgSend(value, SEL_copy);
+ }
+ return value;
+}
+
+static void releaseValue(id value, uintptr_t policy) {
+ if (policy & OBJC_ASSOCIATION_SETTER_RETAIN) {
+ objc_msgSend(value, SEL_release);
+ }
+}
+
+struct ReleaseValue {
+ void operator() (ObjcAssociation &association) {
+ releaseValue(association.value, association.policy);
+ }
+};
+
+__private_extern__ void _object_set_associative_reference(id object, void *key, id value, uintptr_t policy) {
+ // retain the new value (if any) outside the lock.
+ uintptr_t old_policy = 0; // NOTE: old_policy is always assigned to when old_value is non-nil.
+ id new_value = value ? acquireValue(value, policy) : nil, old_value = nil;
+ {
+ AssociationsManager manager;
+ AssocationsHashMap &associations(manager.associations());
+ if (new_value) {
+ // break any existing association.
+ AssocationsHashMap::iterator i = associations.find(object);
+ if (i != associations.end()) {
+ // secondary table exists
+ ObjectAssocationHashMap &refs = i->second;
+ ObjectAssocationHashMap::iterator j = refs.find(key);
+ if (j != refs.end()) {
+ ObjcAssociation &old_entry = j->second;
+ old_policy = old_entry.policy;
+ old_value = old_entry.value;
+ old_entry.policy = policy;
+ old_entry.value = new_value;
+ } else {
+ refs[key] = ObjcAssociation(policy, new_value);
+ }
+ } else {
+ // create the new association (first time).
+ associations[object][key] = ObjcAssociation(policy, new_value);
+ _class_assertInstancesHaveAssociatedObjects(object->isa);
+ }
+ } else {
+ // setting the association to nil breaks the association.
+ AssocationsHashMap::iterator i = associations.find(object);
+ if (i != associations.end()) {
+ ObjectAssocationHashMap &refs = i->second;
+ ObjectAssocationHashMap::iterator j = refs.find(key);
+ if (j != refs.end()) {
+ ObjcAssociation &old_entry = j->second;
+ old_policy = old_entry.policy;
+ old_value = (id) old_entry.value;
+ refs.erase(j);
+ }
+ }
+ }
+ }
+ // release the old value (outside of the lock).
+ if (old_value) releaseValue(old_value, old_policy);
+}
+
+__private_extern__ void _object_remove_assocations(id object) {
+ vector<ObjcAssociation> elements;
+ {
+ AssociationsManager manager;
+ AssocationsHashMap &associations(manager.associations());
+ if (associations.size() == 0) return;
+ AssocationsHashMap::iterator i = associations.find(object);
+ if (i != associations.end()) {
+ // copy all of the associations that need to be removed.
+ ObjectAssocationHashMap &refs = i->second;
+ for (ObjectAssocationHashMap::iterator j = refs.begin(); j != refs.end(); ++j) {
+ elements.push_back(j->second);
+ }
+ // remove the secondary table.
+ associations.erase(i);
+ }
+ }
+ // the calls to releaseValue() happen outside of the lock.
+ for_each(elements.begin(), elements.end(), ReleaseValue());
+}