ucx/hash_map.c

changeset 101
7b3a3130be44
parent 49
2f71f4ee247a
--- 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;

mercurial