/* * 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 #include "DenseMapExtras.h" // 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), OBJC_ASSOCIATION_SYSTEM_OBJECT = _OBJC_ASSOCIATION_SYSTEM_OBJECT, // 1 << 16 }; spinlock_t AssociationsManagerLock; namespace objc { class ObjcAssociation { uintptr_t _policy; id _value; public: ObjcAssociation(uintptr_t policy, id value) : _policy(policy), _value(value) {} ObjcAssociation() : _policy(0), _value(nil) {} ObjcAssociation(const ObjcAssociation &other) = default; ObjcAssociation &operator=(const ObjcAssociation &other) = default; ObjcAssociation(ObjcAssociation &&other) : ObjcAssociation() { swap(other); } inline void swap(ObjcAssociation &other) { std::swap(_policy, other._policy); std::swap(_value, other._value); } inline uintptr_t policy() const { return _policy; } inline id value() const { return _value; } inline void acquireValue() { if (_value) { switch (_policy & 0xFF) { case OBJC_ASSOCIATION_SETTER_RETAIN: _value = objc_retain(_value); break; case OBJC_ASSOCIATION_SETTER_COPY: _value = ((id(*)(id, SEL))objc_msgSend)(_value, @selector(copy)); break; } } } inline void releaseHeldValue() { if (_value && (_policy & OBJC_ASSOCIATION_SETTER_RETAIN)) { objc_release(_value); } } inline void retainReturnedValue() { if (_value && (_policy & OBJC_ASSOCIATION_GETTER_RETAIN)) { objc_retain(_value); } } inline id autoreleaseReturnedValue() { if (slowpath(_value && (_policy & OBJC_ASSOCIATION_GETTER_AUTORELEASE))) { return objc_autorelease(_value); } return _value; } }; typedef DenseMap ObjectAssociationMap; typedef DenseMap, ObjectAssociationMap> AssociationsHashMap; // class AssociationsManager manages a lock / hash table singleton pair. // Allocating an instance acquires the lock class AssociationsManager { using Storage = ExplicitInitDenseMap, ObjectAssociationMap>; static Storage _mapStorage; public: AssociationsManager() { AssociationsManagerLock.lock(); } ~AssociationsManager() { AssociationsManagerLock.unlock(); } AssociationsHashMap &get() { return _mapStorage.get(); } static void init() { _mapStorage.init(); } }; AssociationsManager::Storage AssociationsManager::_mapStorage; } // namespace objc using namespace objc; void _objc_associations_init() { AssociationsManager::init(); } id _object_get_associative_reference(id object, const void *key) { ObjcAssociation association{}; { AssociationsManager manager; AssociationsHashMap &associations(manager.get()); AssociationsHashMap::iterator i = associations.find((objc_object *)object); if (i != associations.end()) { ObjectAssociationMap &refs = i->second; ObjectAssociationMap::iterator j = refs.find(key); if (j != refs.end()) { association = j->second; association.retainReturnedValue(); } } } return association.autoreleaseReturnedValue(); } void _object_set_associative_reference(id object, const void *key, id value, uintptr_t policy) { // This code used to work when nil was passed for object and key. Some code // probably relies on that to not crash. Check and handle it explicitly. // rdar://problem/44094390 if (!object && !value) return; if (object->getIsa()->forbidsAssociatedObjects()) _objc_fatal("objc_setAssociatedObject called on instance (%p) of class %s which does not allow associated objects", object, object_getClassName(object)); DisguisedPtr disguised{(objc_object *)object}; ObjcAssociation association{policy, value}; // retain the new value (if any) outside the lock. association.acquireValue(); bool isFirstAssociation = false; { AssociationsManager manager; AssociationsHashMap &associations(manager.get()); if (value) { auto refs_result = associations.try_emplace(disguised, ObjectAssociationMap{}); if (refs_result.second) { /* it's the first association we make */ isFirstAssociation = true; } /* establish or replace the association */ auto &refs = refs_result.first->second; auto result = refs.try_emplace(key, std::move(association)); if (!result.second) { association.swap(result.first->second); } } else { auto refs_it = associations.find(disguised); if (refs_it != associations.end()) { auto &refs = refs_it->second; auto it = refs.find(key); if (it != refs.end()) { association.swap(it->second); refs.erase(it); if (refs.size() == 0) { associations.erase(refs_it); } } } } } // Call setHasAssociatedObjects outside the lock, since this // will call the object's _noteAssociatedObjects method if it // has one, and this may trigger +initialize which might do // arbitrary stuff, including setting more associated objects. if (isFirstAssociation) object->setHasAssociatedObjects(); // release the old value (outside of the lock). association.releaseHeldValue(); } // Unlike setting/getting an associated reference, // this function is performance sensitive because of // raw isa objects (such as OS Objects) that can't track // whether they have associated objects. void _object_remove_assocations(id object, bool deallocating) { ObjectAssociationMap refs{}; { AssociationsManager manager; AssociationsHashMap &associations(manager.get()); AssociationsHashMap::iterator i = associations.find((objc_object *)object); if (i != associations.end()) { refs.swap(i->second); // If we are not deallocating, then SYSTEM_OBJECT associations are preserved. bool didReInsert = false; if (!deallocating) { for (auto &ref: refs) { if (ref.second.policy() & OBJC_ASSOCIATION_SYSTEM_OBJECT) { i->second.insert(ref); didReInsert = true; } } } if (!didReInsert) associations.erase(i); } } // Associations to be released after the normal ones. SmallVector laterRefs; // release everything (outside of the lock). for (auto &i: refs) { if (i.second.policy() & OBJC_ASSOCIATION_SYSTEM_OBJECT) { // If we are not deallocating, then RELEASE_LATER associations don't get released. if (deallocating) laterRefs.append(&i.second); } else { i.second.releaseHeldValue(); } } for (auto *later: laterRefs) { later->releaseHeldValue(); } }