/* * Copyright (c) 2008-2024 Jonathan Schleifer <js@nil.im> * * All rights reserved. * * This program is free software: you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License version 3.0 only, * as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License * version 3.0 for more details. * * You should have received a copy of the GNU Lesser General Public License * version 3.0 along with this program. If not, see * <https://www.gnu.org/licenses/>. */ #include "config.h" #include <limits.h> #include <stdint.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #import "ObjFWRT.h" #import "private.h" struct objc_hashtable_bucket objc_deletedBucket; uint32_t objc_string_hash(const void *str_) { const char *str = str_; uint32_t hash = 0; while (*str != 0) { hash += *str; hash += (hash << 10); hash ^= (hash >> 6); str++; } hash += (hash << 3); hash ^= (hash >> 11); hash += (hash << 15); return hash; } bool objc_string_equal(const void *ptr1, const void *ptr2) { return (strcmp(ptr1, ptr2) == 0); } struct objc_hashtable * objc_hashtable_new(uint32_t (*hash)(const void *), bool (*equal)(const void *, const void *), uint32_t size) { struct objc_hashtable *table; if ((table = malloc(sizeof(*table))) == NULL) OBJC_ERROR("Not enough memory to allocate hash table!"); table->hash = hash; table->equal = equal; table->count = 0; table->size = size; table->data = calloc(size, sizeof(struct objc_hashtable_bucket *)); if (table->data == NULL) OBJC_ERROR("Not enough memory to allocate hash table!"); return table; } static void resize(struct objc_hashtable *table, uint32_t count) { uint32_t fullness, newSize; struct objc_hashtable_bucket **newData; if (count > UINT32_MAX / sizeof(*table->data) || count > UINT32_MAX / 8) OBJC_ERROR("Integer overflow!"); fullness = count * 8 / table->size; if (fullness >= 6) { if (table->size > UINT32_MAX / 2) return; newSize = table->size * 2; } else if (fullness <= 1) newSize = table->size / 2; else return; if (count < table->count && newSize < 16) return; if ((newData = calloc(newSize, sizeof(*newData))) == NULL) OBJC_ERROR("Not enough memory to resize hash table!"); for (uint32_t i = 0; i < table->size; i++) { if (table->data[i] != NULL && table->data[i] != &objc_deletedBucket) { uint32_t j, last; last = newSize; for (j = table->data[i]->hash & (newSize - 1); j < last && newData[j] != NULL; j++); if (j >= last) { last = table->data[i]->hash & (newSize - 1); for (j = 0; j < last && newData[j] != NULL; j++); } if (j >= last) OBJC_ERROR("No free bucket in hash table!"); newData[j] = table->data[i]; } } free(table->data); table->data = newData; table->size = newSize; } static inline bool indexForKey(struct objc_hashtable *table, const void *key, uint32_t *idx) { uint32_t i, hash; hash = table->hash(key) & (table->size - 1); for (i = hash; i < table->size && table->data[i] != NULL; i++) { if (table->data[i] == &objc_deletedBucket) continue; if (table->equal(table->data[i]->key, key)) { *idx = i; return true; } } if (i < table->size) return false; for (i = 0; i < hash && table->data[i] != NULL; i++) { if (table->data[i] == &objc_deletedBucket) continue; if (table->equal(table->data[i]->key, key)) { *idx = i; return true; } } return false; } void objc_hashtable_set(struct objc_hashtable *table, const void *key, const void *object) { uint32_t i, hash, last; struct objc_hashtable_bucket *bucket; if (indexForKey(table, key, &i)) { table->data[i]->object = object; return; } resize(table, table->count + 1); hash = table->hash(key); last = table->size; for (i = hash & (table->size - 1); i < last && table->data[i] != NULL && table->data[i] != &objc_deletedBucket; i++); if (i >= last) { last = hash & (table->size - 1); for (i = 0; i < last && table->data[i] != NULL && table->data[i] != &objc_deletedBucket; i++); } if (i >= last) OBJC_ERROR("No free bucket in hash table!"); if ((bucket = malloc(sizeof(*bucket))) == NULL) OBJC_ERROR("Not enough memory to allocate hash table bucket!"); bucket->key = key; bucket->hash = hash; bucket->object = object; table->data[i] = bucket; table->count++; } void * objc_hashtable_get(struct objc_hashtable *table, const void *key) { uint32_t idx; if (!indexForKey(table, key, &idx)) return NULL; return (void *)table->data[idx]->object; } void objc_hashtable_delete(struct objc_hashtable *table, const void *key) { uint32_t idx; if (!indexForKey(table, key, &idx)) return; free(table->data[idx]); table->data[idx] = &objc_deletedBucket; table->count--; resize(table, table->count); } void objc_hashtable_free(struct objc_hashtable *table) { for (uint32_t i = 0; i < table->size; i++) if (table->data[i] != NULL && table->data[i] != &objc_deletedBucket) free(table->data[i]); free(table->data); free(table); }