--- a/ucx/hash_map.c Thu Dec 12 20:01:43 2024 +0100 +++ b/ucx/hash_map.c Mon Jan 06 22:22:55 2025 +0100 @@ -27,10 +27,10 @@ */ #include "cx/hash_map.h" -#include "cx/utils.h" #include <string.h> #include <assert.h> +#include <errno.h> struct cx_hash_map_element_s { /** A pointer to the next element in the current bucket. */ @@ -45,7 +45,7 @@ static void cx_hash_map_clear(struct cx_map_s *map) { struct cx_hash_map_s *hash_map = (struct cx_hash_map_s *) map; - cx_for_n(i, hash_map->bucket_count) { + for (size_t i = 0; i < hash_map->bucket_count; i++) { struct cx_hash_map_element_s *elem = hash_map->buckets[i]; if (elem != NULL) { do { @@ -115,9 +115,7 @@ allocator, sizeof(struct cx_hash_map_element_s) + map->collection.elem_size ); - if (e == NULL) { - return -1; - } + if (e == NULL) return -1; // write the value if (map->collection.store_pointer) { @@ -128,9 +126,7 @@ // copy the key void *kd = cxMalloc(allocator, key.len); - if (kd == NULL) { - return -1; - } + if (kd == NULL) return -1; memcpy(kd, key.data, key.len); e->key.data = kd; e->key.len = key.len; @@ -173,17 +169,28 @@ /** * Helper function to avoid code duplication. * + * If @p remove is true, and @p targetbuf is @c NULL, the element + * will be destroyed when found. + * + * If @p remove is true, and @p targetbuf is set, the element will + * be copied to that buffer and no destructor function is called. + * + * If @p remove is false, @p targetbuf must not be non-null and + * either the pointer, when the map is storing pointers, is copied + * to the target buffer, or a pointer to the stored object will + * be copied to the target buffer. + * * @param map the map * @param key the key to look up + * @param targetbuf see description * @param remove flag indicating whether the looked up entry shall be removed - * @param destroy flag indicating whether the destructor shall be invoked - * @return a pointer to the value corresponding to the key or \c NULL + * @return zero, if the key was found, non-zero otherwise */ -static void *cx_hash_map_get_remove( +static int cx_hash_map_get_remove( CxMap *map, CxHashKey key, - bool remove, - bool destroy + void *targetbuf, + bool remove ) { struct cx_hash_map_s *hash_map = (struct cx_hash_map_s *) map; @@ -199,27 +206,31 @@ while (elm && elm->key.hash <= hash) { if (elm->key.hash == hash && elm->key.len == key.len) { if (memcmp(elm->key.data, key.data, key.len) == 0) { - void *data = NULL; - if (destroy) { - cx_invoke_destructor(map, elm->data); + if (remove) { + if (targetbuf == NULL) { + cx_invoke_destructor(map, elm->data); + } else { + memcpy(targetbuf, elm->data, map->collection.elem_size); + } + cx_hash_map_unlink(hash_map, slot, prev, elm); } else { + assert(targetbuf != NULL); + void *data = NULL; if (map->collection.store_pointer) { data = *(void **) elm->data; } else { data = elm->data; } + memcpy(targetbuf, &data, sizeof(void *)); } - if (remove) { - cx_hash_map_unlink(hash_map, slot, prev, elm); - } - return data; + return 0; } } prev = elm; elm = prev->next; } - return NULL; + return 1; } static void *cx_hash_map_get( @@ -227,15 +238,17 @@ CxHashKey key ) { // we can safely cast, because we know the map stays untouched - return cx_hash_map_get_remove((CxMap *) map, key, false, false); + void *ptr = NULL; + int found = cx_hash_map_get_remove((CxMap *) map, key, &ptr, false); + return found == 0 ? ptr : NULL; } -static void *cx_hash_map_remove( +static int cx_hash_map_remove( CxMap *map, CxHashKey key, - bool destroy + void *targetbuf ) { - return cx_hash_map_get_remove(map, key, true, destroy); + return cx_hash_map_get_remove(map, key, targetbuf, true); } static void *cx_hash_map_iter_current_entry(const void *it) { @@ -346,7 +359,7 @@ iter.base.current = cx_hash_map_iter_current_value; break; default: - assert(false); + assert(false); // LCOV_EXCL_LINE } iter.base.valid = cx_hash_map_iter_valid; @@ -393,6 +406,10 @@ size_t itemsize, size_t buckets ) { + if (allocator == NULL) { + allocator = cxDefaultAllocator; + } + if (buckets == 0) { // implementation defined default buckets = 16; @@ -406,10 +423,10 @@ map->bucket_count = buckets; map->buckets = cxCalloc(allocator, buckets, sizeof(struct cx_hash_map_element_s *)); - if (map->buckets == NULL) { + if (map->buckets == NULL) { // LCOV_EXCL_START cxFree(allocator, map); return NULL; - } + } // LCOV_EXCL_STOP // initialize base members map->base.cl = &cx_hash_map_class; @@ -431,17 +448,19 @@ if (map->collection.size > ((hash_map->bucket_count * 3) >> 2)) { size_t new_bucket_count = (map->collection.size * 5) >> 1; + if (new_bucket_count < hash_map->bucket_count) { + errno = EOVERFLOW; + return 1; + } struct cx_hash_map_element_s **new_buckets = cxCalloc( map->collection.allocator, new_bucket_count, sizeof(struct cx_hash_map_element_s *) ); - if (new_buckets == NULL) { - return 1; - } + if (new_buckets == NULL) return 1; // iterate through the elements and assign them to their new slots - cx_for_n(slot, hash_map->bucket_count) { + for (size_t slot = 0; slot < hash_map->bucket_count; slot++) { struct cx_hash_map_element_s *elm = hash_map->buckets[slot]; while (elm != NULL) { struct cx_hash_map_element_s *next = elm->next;