/* * 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 #include // 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 ObjectPointerLess { bool operator()(const void *p1, const void *p2) const { return p1 < p2; } }; 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 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 struct rebind { typedef ObjcAllocator other; }; template ObjcAllocator(const ObjcAllocator&) {} 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(::_malloc_internal(n * sizeof(T))); } void deallocate(pointer p, size_type) { ::_free_internal(p); } size_type max_size() const { return static_cast(-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 { typedef void value_type; typedef void* pointer; typedef const void *const_pointer; template struct rebind { typedef ObjcAllocator other; }; }; struct ObjcAssociation { uintptr_t policy; id value; ObjcAssociation(uintptr_t newPolicy, id newValue) : policy(newPolicy), value(newValue) { } ObjcAssociation() : policy(0), value(0) { } }; #if TARGET_OS_WIN32 typedef hash_map ObjectAssociationMap; typedef hash_map AssociationsHashMap; #else class ObjectAssociationMap : public std::map > > { public: void *operator new(size_t n) { return ::_malloc_internal(n); } void operator delete(void *ptr) { ::_free_internal(ptr); } }; typedef hash_map > AssociationsHashMap; #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 AssociationsHashMap *_map; // associative references: object pointer -> PtrPtrHashMap. public: AssociationsManager() { OSSpinLockLock(&_lock); } ~AssociationsManager() { OSSpinLockUnlock(&_lock); } AssociationsHashMap &associations() { if (_map == NULL) _map = new(::_malloc_internal(sizeof(AssociationsHashMap))) AssociationsHashMap(); return *_map; } }; OSSpinLock AssociationsManager::_lock = OS_SPINLOCK_INIT; AssociationsHashMap *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; AssociationsHashMap &associations(manager.associations()); AssociationsHashMap::iterator i = associations.find(object); if (i != associations.end()) { ObjectAssociationMap *refs = i->second; ObjectAssociationMap::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; AssociationsHashMap &associations(manager.associations()); if (new_value) { // break any existing association. AssociationsHashMap::iterator i = associations.find(object); if (i != associations.end()) { // secondary table exists ObjectAssociationMap *refs = i->second; ObjectAssociationMap::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). ObjectAssociationMap *refs = new ObjectAssociationMap; associations[object] = refs; (*refs)[key] = ObjcAssociation(policy, new_value); _class_assertInstancesHaveAssociatedObjects(object->isa); } } else { // setting the association to nil breaks the association. AssociationsHashMap::iterator i = associations.find(object); if (i != associations.end()) { ObjectAssociationMap *refs = i->second; ObjectAssociationMap::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 elements; { AssociationsManager manager; AssociationsHashMap &associations(manager.associations()); if (associations.size() == 0) return; AssociationsHashMap::iterator i = associations.find(object); if (i != associations.end()) { // copy all of the associations that need to be removed. ObjectAssociationMap *refs = i->second; for (ObjectAssociationMap::iterator j = refs->begin(), end = refs->end(); j != end; ++j) { elements.push_back(j->second); } // remove the secondary table. delete refs; associations.erase(i); } } // the calls to releaseValue() happen outside of the lock. for_each(elements.begin(), elements.end(), ReleaseValue()); }