dte test coverage


Directory: ./
File: src/util/hashmap.c
Date: 2024-12-21 16:03:22
Exec Total Coverage
Lines: 118 126 93.7%
Functions: 11 11 100.0%
Branches: 49 60 81.7%

Line Branch Exec Source
1 #include <errno.h>
2 #include <stdlib.h>
3 #include <string.h>
4 #include "hashmap.h"
5 #include "debug.h"
6 #include "hash.h"
7 #include "xstring.h"
8
9 enum {
10 MIN_SIZE = 8,
11 TOMBSTONE = 0xdead,
12 };
13
14 WARN_UNUSED_RESULT
15 455 static int hashmap_resize(HashMap *map, size_t size)
16 {
17 455 BUG_ON(size < MIN_SIZE);
18 455 BUG_ON(size <= map->count);
19 455 BUG_ON(!IS_POWER_OF_2(size));
20
21 455 HashMapEntry *newtab = calloc(size, sizeof(*newtab));
22
1/2
✓ Branch 0 taken 455 times.
✗ Branch 1 not taken.
455 if (unlikely(!newtab)) {
23 return ENOMEM;
24 }
25
26 455 HashMapEntry *oldtab = map->entries;
27 455 size_t oldlen = map->mask + 1;
28 455 map->entries = newtab;
29 455 map->mask = size - 1;
30 455 map->tombstones = 0;
31
32
2/2
✓ Branch 0 taken 208 times.
✓ Branch 1 taken 247 times.
455 if (!oldtab) {
33 return 0;
34 }
35
36 // Copy the entries to the new table
37
2/2
✓ Branch 0 taken 19016 times.
✓ Branch 1 taken 208 times.
19224 for (const HashMapEntry *e = oldtab, *end = e + oldlen; e < end; e++) {
38
2/2
✓ Branch 0 taken 10953 times.
✓ Branch 1 taken 8063 times.
19016 if (!e->key) {
39 10953 continue;
40 }
41 8063 HashMapEntry *newe;
42 9580 for (size_t i = e->hash, j = 1; ; i += j++) {
43 9580 newe = newtab + (i & map->mask);
44
2/2
✓ Branch 0 taken 1517 times.
✓ Branch 1 taken 8063 times.
9580 if (!newe->key) {
45 break;
46 }
47 }
48 8063 *newe = *e;
49 }
50
51 208 free(oldtab);
52 208 return 0;
53 }
54
55 WARN_UNUSED_RESULT
56 247 static int hashmap_do_init(HashMap *map, size_t size)
57 {
58 // Accommodate the 75% load factor in the table size, to allow
59 // filling to the requested size without needing to resize()
60 247 size += size / 3;
61
62
2/2
✓ Branch 0 taken 220 times.
✓ Branch 1 taken 27 times.
247 if (unlikely(size < MIN_SIZE)) {
63 220 size = MIN_SIZE;
64 }
65
66 // Round up the size to the next power of 2, to allow using simple
67 // bitwise ops (instead of modulo) to wrap the hash value and also
68 // to allow quadratic probing with triangular numbers
69 247 size = round_size_to_next_power_of_2(size);
70
1/2
✓ Branch 0 taken 247 times.
✗ Branch 1 not taken.
247 if (unlikely(size == 0)) {
71 return EOVERFLOW;
72 }
73
74 247 *map = (HashMap)HASHMAP_INIT;
75 247 return hashmap_resize(map, size);
76 }
77
78 28 void hashmap_init(HashMap *map, size_t capacity)
79 {
80 28 int err = hashmap_do_init(map, capacity);
81
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 28 times.
28 if (unlikely(err)) {
82 fatal_error(__func__, err);
83 }
84 28 }
85
86 41860 HashMapEntry *hashmap_find(const HashMap *map, const char *key)
87 {
88
2/2
✓ Branch 0 taken 41543 times.
✓ Branch 1 taken 317 times.
41860 if (unlikely(!map->entries)) {
89 return NULL;
90 }
91
92 41543 size_t hash = fnv_1a_hash(key, strlen(key));
93 41543 HashMapEntry *e;
94 76626 for (size_t i = hash, j = 1; ; i += j++) {
95 76626 e = map->entries + (i & map->mask);
96
2/2
✓ Branch 0 taken 30939 times.
✓ Branch 1 taken 45687 times.
76626 if (!e->key) {
97
2/2
✓ Branch 0 taken 12182 times.
✓ Branch 1 taken 18757 times.
30939 if (e->hash == TOMBSTONE) {
98 12182 continue;
99 }
100 return NULL;
101 }
102
3/4
✓ Branch 0 taken 22786 times.
✓ Branch 1 taken 22901 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 22786 times.
45687 if (e->hash == hash && streq(e->key, key)) {
103 return e;
104 }
105 }
106
107 BUG("unexpected loop break");
108 }
109
110 11560 void *hashmap_remove(HashMap *map, const char *key)
111 {
112 11560 HashMapEntry *e = hashmap_find(map, key);
113
2/2
✓ Branch 0 taken 11541 times.
✓ Branch 1 taken 19 times.
11560 if (!e) {
114 return NULL;
115 }
116
117 11541 free(e->key);
118 11541 e->key = NULL;
119 11541 e->hash = TOMBSTONE;
120 11541 map->count--;
121 11541 map->tombstones++;
122 11541 return e->value;
123 }
124
125 WARN_UNUSED_RESULT
126 15474 static int hashmap_do_insert(HashMap *map, char *key, void *value, void **old_value)
127 {
128 15474 int err = 0;
129
2/2
✓ Branch 0 taken 219 times.
✓ Branch 1 taken 15255 times.
15474 if (unlikely(!map->entries)) {
130 219 err = hashmap_do_init(map, 0);
131
1/2
✓ Branch 0 taken 219 times.
✗ Branch 1 not taken.
219 if (unlikely(err)) {
132 return err;
133 }
134 }
135
136 15474 size_t hash = fnv_1a_hash(key, strlen(key));
137 15474 bool replacing_tombstone_or_existing_value = false;
138 15474 HashMapEntry *e;
139 22754 for (size_t i = hash, j = 1; ; i += j++) {
140 22754 e = map->entries + (i & map->mask);
141
2/2
✓ Branch 0 taken 15473 times.
✓ Branch 1 taken 7281 times.
22754 if (!e->key) {
142
2/2
✓ Branch 0 taken 4770 times.
✓ Branch 1 taken 10703 times.
15473 if (e->hash == TOMBSTONE) {
143 4770 replacing_tombstone_or_existing_value = true;
144 4770 BUG_ON(map->tombstones == 0);
145 4770 map->tombstones--;
146 }
147 break;
148 }
149
3/4
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 7280 times.
✓ Branch 2 taken 1 times.
✗ Branch 3 not taken.
7281 if (unlikely(e->hash == hash && streq(e->key, key))) {
150 1 replacing_tombstone_or_existing_value = true;
151 // When a caller passes NULL as the "old_value" return param,
152 // it implies there can be no existing entry with the same key
153 // as the one to be inserted.
154 1 BUG_ON(!old_value);
155 1 BUG_ON(!e->value);
156 1 *old_value = e->value;
157 1 free(key);
158 1 key = e->key;
159 1 map->count--;
160 1 break;
161 }
162 }
163
164 15474 const size_t max_load = map->mask - (map->mask / 4);
165 15474 e->key = key;
166 15474 e->value = value;
167 15474 e->hash = hash;
168 15474 map->count++;
169
170
2/2
✓ Branch 0 taken 208 times.
✓ Branch 1 taken 15266 times.
15474 if (unlikely(map->count + map->tombstones > max_load)) {
171 208 BUG_ON(replacing_tombstone_or_existing_value);
172 208 size_t new_size = map->mask + 1;
173
3/4
✓ Branch 0 taken 6 times.
✓ Branch 1 taken 202 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 6 times.
208 if (map->count > map->tombstones || new_size <= 256) {
174 // Only increase the size of the table when the number of
175 // real entries is higher than the number of tombstones.
176 // If the number of real entries is lower, the table is
177 // most likely being filled with tombstones from repeated
178 // insert/remove churn; so we just rehash at the same size
179 // to clean out the tombstones.
180 202 new_size <<= 1;
181
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 202 times.
202 if (unlikely(new_size == 0)) {
182 err = EOVERFLOW;
183 goto error;
184 }
185 }
186 208 err = hashmap_resize(map, new_size);
187
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 208 times.
208 if (unlikely(err)) {
188 goto error;
189 }
190 }
191
192 return 0;
193
194 error:
195 map->count--;
196 e->key = NULL;
197 e->hash = 0;
198 return err;
199 }
200
201 15259 void *hashmap_insert(HashMap *map, char *key, void *value)
202 {
203 15259 int err = hashmap_do_insert(map, key, value, NULL);
204
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 15259 times.
15259 if (unlikely(err)) {
205 fatal_error(__func__, err);
206 }
207 15259 return value;
208 }
209
210 215 void *hashmap_insert_or_replace(HashMap *map, char *key, void *value)
211 {
212 215 void *replaced_value = NULL;
213 215 int err = hashmap_do_insert(map, key, value, &replaced_value);
214
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 215 times.
215 if (unlikely(err)) {
215 fatal_error(__func__, err);
216 }
217 215 return replaced_value;
218 }
219
220 // Call a FreeFunction declared with an arbitrary pointer parameter type
221 // without -fsanitize=function pedantry
222 NO_SANITIZE("undefined")
223 3478 static void do_free_value(FreeFunction free_value, void *value)
224 {
225 3478 free_value(value);
226 3478 }
227
228 // Remove all entries without freeing the table
229 484 void hashmap_clear(HashMap *map, FreeFunction free_value)
230 {
231
2/2
✓ Branch 0 taken 247 times.
✓ Branch 1 taken 237 times.
484 if (unlikely(!map->entries)) {
232 return;
233 }
234
235 247 size_t count = 0;
236
2/2
✓ Branch 0 taken 3932 times.
✓ Branch 1 taken 247 times.
4179 for (HashMapIter it = hashmap_iter(map); hashmap_next(&it); count++) {
237 3932 free(it.entry->key);
238
2/2
✓ Branch 0 taken 3478 times.
✓ Branch 1 taken 454 times.
3932 if (free_value) {
239 3478 do_free_value(free_value, it.entry->value);
240 }
241 }
242
243 247 BUG_ON(count != map->count);
244 247 size_t len = map->mask + 1;
245 247 map->count = 0;
246 247 memset(map->entries, 0, len * sizeof(*map->entries));
247 }
248
249 472 void hashmap_free(HashMap *map, FreeFunction free_value)
250 {
251 472 hashmap_clear(map, free_value);
252 472 free(map->entries);
253 472 *map = (HashMap)HASHMAP_INIT;
254 472 }
255