dte test coverage


Directory: ./
File: src/util/hashmap.c
Date: 2025-05-08 15:05:54
Exec Total Coverage
Lines: 122 130 93.8%
Functions: 12 12 100.0%
Branches: 51 62 82.3%

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