/* * Copyright (c) 2008-2024 Jonathan Schleifer <js@nil.im> * * All rights reserved. * * This file is part of ObjFW. It may be distributed under the terms of the * Q Public License 1.0, which can be found in the file LICENSE.QPL included in * the packaging of this file. * * Alternatively, it may be distributed under the terms of the GNU General * Public License, either version 2 or 3, which can be found in the file * LICENSE.GPLv2 or LICENSE.GPLv3 respectively included in the packaging of this * file. */ #include "config.h" #ifdef OF_OBJFW_RUNTIME # import "ObjFWRT.h" # import "private.h" #else # import "OFObject.h" # import "OFMapTable.h" #endif struct Association { id object; objc_associationPolicy policy; }; #ifdef OF_OBJFW_RUNTIME typedef struct objc_hashtable objc_hashtable; #else typedef OFMapTable objc_hashtable; static const OFMapTableFunctions defaultFunctions = { NULL }; static objc_hashtable * objc_hashtable_new(uint32_t (*hash)(const void *key), bool (*equal)(const void *key1, const void *key2), uint32_t size) { return [[OFMapTable alloc] initWithKeyFunctions: defaultFunctions objectFunctions: defaultFunctions]; } static void objc_hashtable_set(objc_hashtable *hashtable, const void *key, const void *object) { return [hashtable setObject: (void *)object forKey: (void *)key]; } static void * objc_hashtable_get(objc_hashtable *hashtable, const void *key) { return [hashtable objectForKey: (void *)key]; } static void objc_hashtable_delete(objc_hashtable *hashtable, const void *key) { [hashtable removeObjectForKey: (void *)key]; } static void objc_hashtable_free(objc_hashtable *hashtable) { [hashtable release]; } # define OBJC_ERROR(...) abort() #endif #ifdef OF_HAVE_THREADS # define numSlots 8 /* needs to be a power of 2 */ # import "OFPlainMutex.h" static OFSpinlock spinlocks[numSlots]; #else # define numSlots 1 #endif static objc_hashtable *hashtables[numSlots]; static OF_INLINE size_t slotForObject(id object) { return ((size_t)((uintptr_t)object >> 4) & (numSlots - 1)); } static uint32_t hash(const void *object) { return (uint32_t)(uintptr_t)object; } static bool equal(const void *object1, const void *object2) { return (object1 == object2); } OF_CONSTRUCTOR() { for (size_t i = 0; i < numSlots; i++) { hashtables[i] = objc_hashtable_new(hash, equal, 2); #ifdef OF_HAVE_THREADS if (OFSpinlockNew(&spinlocks[i]) != 0) OBJC_ERROR("Failed to create spinlocks!"); #endif } } void objc_setAssociatedObject(id object, const void *key, id value, objc_associationPolicy policy) { size_t slot; switch (policy) { case OBJC_ASSOCIATION_ASSIGN: break; case OBJC_ASSOCIATION_RETAIN: case OBJC_ASSOCIATION_RETAIN_NONATOMIC: value = [value retain]; break; case OBJC_ASSOCIATION_COPY: case OBJC_ASSOCIATION_COPY_NONATOMIC: value = [value copy]; break; default: /* Don't know what to do, so do nothing. */ return; } slot = slotForObject(object); #ifdef OF_HAVE_THREADS if (OFSpinlockLock(&spinlocks[slot]) != 0) OBJC_ERROR("Failed to lock spinlock!"); @try { #endif objc_hashtable *objectHashtable; struct Association *association; objectHashtable = objc_hashtable_get(hashtables[slot], object); if (objectHashtable == NULL) { objectHashtable = objc_hashtable_new(hash, equal, 2); objc_hashtable_set(hashtables[slot], object, objectHashtable); } association = objc_hashtable_get(objectHashtable, key); if (association != NULL) { switch (association->policy) { case OBJC_ASSOCIATION_RETAIN: case OBJC_ASSOCIATION_RETAIN_NONATOMIC: case OBJC_ASSOCIATION_COPY: case OBJC_ASSOCIATION_COPY_NONATOMIC: [association->object release]; break; default: break; } } else { association = malloc(sizeof(*association)); if (association == NULL) OBJC_ERROR("Failed to allocate association!"); objc_hashtable_set(objectHashtable, key, association); } association->policy = policy; association->object = value; #ifdef OF_HAVE_THREADS } @finally { if (OFSpinlockUnlock(&spinlocks[slot]) != 0) OBJC_ERROR("Failed to unlock spinlock!"); } #endif } id objc_getAssociatedObject(id object, const void *key) { size_t slot = slotForObject(object); id ret; #ifdef OF_HAVE_THREADS if (OFSpinlockLock(&spinlocks[slot]) != 0) OBJC_ERROR("Failed to lock spinlock!"); @try { #endif objc_hashtable *objectHashtable; struct Association *association; objectHashtable = objc_hashtable_get(hashtables[slot], object); if (objectHashtable == NULL) return nil; association = objc_hashtable_get(objectHashtable, key); if (association == NULL) return nil; switch (association->policy) { case OBJC_ASSOCIATION_RETAIN: case OBJC_ASSOCIATION_COPY: ret = [[association->object retain] autorelease]; break; default: ret = association->object; break; } #ifdef OF_HAVE_THREADS } @finally { if (OFSpinlockUnlock(&spinlocks[slot]) != 0) OBJC_ERROR("Failed to unlock spinlock!"); } #endif return ret; } void objc_removeAssociatedObjects(id object) { size_t slot = slotForObject(object); #ifdef OF_HAVE_THREADS if (OFSpinlockLock(&spinlocks[slot]) != 0) OBJC_ERROR("Failed to lock spinlock!"); @try { #endif objc_hashtable *objectHashtable; objectHashtable = objc_hashtable_get(hashtables[slot], object); if (objectHashtable == NULL) return; #ifdef OF_OBJFW_RUNTIME for (uint32_t i = 0; i < objectHashtable->size; i++) { struct Association *association; if (objectHashtable->data[i] == NULL || objectHashtable->data[i] == &objc_deletedBucket) continue; association = (struct Association *) objectHashtable->data[i]->object; #else OFMapTableEnumerator *enumerator = [objectHashtable objectEnumerator]; void **iter; while ((iter = [enumerator nextObject]) != NULL) { struct Association *association = *iter; #endif switch (association->policy) { case OBJC_ASSOCIATION_RETAIN: case OBJC_ASSOCIATION_RETAIN_NONATOMIC: case OBJC_ASSOCIATION_COPY: case OBJC_ASSOCIATION_COPY_NONATOMIC: [association->object release]; break; default: break; } free(association); } objc_hashtable_delete(hashtables[slot], object); objc_hashtable_free(objectHashtable); #ifdef OF_HAVE_THREADS } @finally { if (OFSpinlockUnlock(&spinlocks[slot]) != 0) OBJC_ERROR("Failed to unlock spinlock!"); } #endif }