dte test coverage


Directory: ./
File: src/util/hashmap.c
Date: 2025-02-14 16:55:22
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 "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 461 static int hashmap_resize(HashMap *map, size_t size)
16 {
17 461 BUG_ON(size < MIN_SIZE);
18 461 BUG_ON(size <= map->count);
19 461 BUG_ON(!IS_POWER_OF_2(size));
20
21 461 HashMapEntry *newtab = calloc(size, sizeof(*newtab));
22
1/2
✓ Branch 0 (8→9) taken 461 times.
✗ Branch 1 (8→20) not taken.
461 if (unlikely(!newtab)) {
23 return ENOMEM;
24 }
25
26 461 HashMapEntry *oldtab = map->entries;
27 461 size_t oldlen = map->mask + 1;
28 461 map->entries = newtab;
29 461 map->mask = size - 1;
30 461 map->tombstones = 0;
31
32
2/2
✓ Branch 0 (9→10) taken 208 times.
✓ Branch 1 (9→20) taken 253 times.
461 if (!oldtab) {
33 return 0;
34 }
35
36 // Copy the entries to the new table
37
2/2
✓ Branch 0 (18→11) taken 19008 times.
✓ Branch 1 (18→19) taken 208 times.
19216 for (const HashMapEntry *e = oldtab, *end = e + oldlen; e < end; e++) {
38
2/2
✓ Branch 0 (11→12) taken 10951 times.
✓ Branch 1 (11→13) taken 8057 times.
19008 if (!e->key) {
39 10951 continue;
40 }
41 8057 HashMapEntry *newe;
42 9573 for (size_t i = e->hash, j = 1; ; i += j++) {
43 9573 newe = newtab + (i & map->mask);
44
2/2
✓ Branch 0 (14→15) taken 1516 times.
✓ Branch 1 (14→16) taken 8057 times.
9573 if (!newe->key) {
45 break;
46 }
47 }
48 8057 *newe = *e;
49 }
50
51 208 free(oldtab);
52 208 return 0;
53 }
54
55 WARN_UNUSED_RESULT
56 253 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 253 size += size / 3;
61
62
2/2
✓ Branch 0 (2→3) taken 226 times.
✓ Branch 1 (2→4) taken 27 times.
253 if (unlikely(size < MIN_SIZE)) {
63 226 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 (`i += j++`
69 // in the `for` loops below)
70 253 size = next_pow2(size);
71
1/2
✓ Branch 0 (5→6) taken 253 times.
✗ Branch 1 (5→7) not taken.
253 if (unlikely(size == 0)) {
72 return EOVERFLOW;
73 }
74
75 253 return hashmap_resize(map, size);
76 }
77
78 28 void hashmap_init(HashMap *map, size_t capacity, HashMapFlags flags)
79 {
80 28 *map = (HashMap){.flags = flags};
81 28 int err = hashmap_do_init(map, capacity);
82
1/2
✗ Branch 0 (3→4) not taken.
✓ Branch 1 (3→5) taken 28 times.
28 if (unlikely(err)) {
83 fatal_error(__func__, err);
84 }
85 28 }
86
87 41986 HashMapEntry *hashmap_find(const HashMap *map, const char *key)
88 {
89
2/2
✓ Branch 0 (2→3) taken 41666 times.
✓ Branch 1 (2→10) taken 320 times.
41986 if (unlikely(!map->entries)) {
90 return NULL;
91 }
92
93 41666 size_t hash = fnv_1a_hash(key, strlen(key));
94 41666 HashMapEntry *e;
95 76903 for (size_t i = hash, j = 1; ; i += j++) {
96 76903 e = map->entries + (i & map->mask);
97
2/2
✓ Branch 0 (4→5) taken 30999 times.
✓ Branch 1 (4→7) taken 45904 times.
76903 if (!e->key) {
98
2/2
✓ Branch 0 (5→6) taken 12182 times.
✓ Branch 1 (5→10) taken 18817 times.
30999 if (e->hash == TOMBSTONE) {
99 12182 continue;
100 }
101 return NULL;
102 }
103
3/4
✓ Branch 0 (7→8) taken 22849 times.
✓ Branch 1 (7→9) taken 23055 times.
✗ Branch 2 (8→9) not taken.
✓ Branch 3 (8→10) taken 22849 times.
45904 if (e->hash == hash && streq(e->key, key)) {
104 return e;
105 }
106 }
107
108 BUG("unexpected loop break");
109 }
110
111 15497 static void hashmap_free_key(const HashMap *map, char *key)
112 {
113
2/2
✓ Branch 0 (2→3) taken 15474 times.
✓ Branch 1 (2→4) taken 23 times.
15497 if (!(map->flags & HMAP_BORROWED_KEYS)) {
114 15474 free(key);
115 }
116 15497 }
117
118 11562 void *hashmap_remove(HashMap *map, const char *key)
119 {
120 11562 HashMapEntry *e = hashmap_find(map, key);
121
2/2
✓ Branch 0 (3→4) taken 11543 times.
✓ Branch 1 (3→6) taken 19 times.
11562 if (!e) {
122 return NULL;
123 }
124
125 11543 hashmap_free_key(map, e->key);
126 11543 e->key = NULL;
127 11543 e->hash = TOMBSTONE;
128 11543 map->count--;
129 11543 map->tombstones++;
130 11543 return e->value;
131 }
132
133 WARN_UNUSED_RESULT
134 15497 static int hashmap_do_insert(HashMap *map, char *key, void *value, void **old_value)
135 {
136 15497 int err = 0;
137
2/2
✓ Branch 0 (2→3) taken 225 times.
✓ Branch 1 (2→5) taken 15272 times.
15497 if (unlikely(!map->entries)) {
138 225 err = hashmap_do_init(map, 0);
139
1/2
✓ Branch 0 (4→5) taken 225 times.
✗ Branch 1 (4→31) not taken.
225 if (unlikely(err)) {
140 return err;
141 }
142 }
143
144 15497 size_t hash = fnv_1a_hash(key, strlen(key));
145 15497 bool replacing_tombstone_or_existing_value = false;
146 15497 HashMapEntry *e;
147 22801 for (size_t i = hash, j = 1; ; i += j++) {
148 22801 e = map->entries + (i & map->mask);
149
2/2
✓ Branch 0 (6→7) taken 15496 times.
✓ Branch 1 (6→11) taken 7305 times.
22801 if (!e->key) {
150
2/2
✓ Branch 0 (7→8) taken 4770 times.
✓ Branch 1 (7→20) taken 10726 times.
15496 if (e->hash == TOMBSTONE) {
151 4770 replacing_tombstone_or_existing_value = true;
152 4770 BUG_ON(map->tombstones == 0);
153 4770 map->tombstones--;
154 }
155 break;
156 }
157
3/4
✓ Branch 0 (11→12) taken 1 times.
✓ Branch 1 (11→19) taken 7304 times.
✓ Branch 2 (12→13) taken 1 times.
✗ Branch 3 (12→19) not taken.
7305 if (unlikely(e->hash == hash && streq(e->key, key))) {
158 1 replacing_tombstone_or_existing_value = true;
159 // When a caller passes NULL as the "old_value" return param,
160 // it implies there can be no existing entry with the same key
161 // as the one to be inserted.
162 1 BUG_ON(!old_value);
163 1 BUG_ON(!e->value);
164 1 *old_value = e->value;
165 1 hashmap_free_key(map, key);
166 1 key = e->key;
167 1 map->count--;
168 1 break;
169 }
170 }
171
172 15497 const size_t max_load = map->mask - (map->mask / 4);
173 15497 e->key = key;
174 15497 e->value = value;
175 15497 e->hash = hash;
176 15497 map->count++;
177
178
2/2
✓ Branch 0 (20→21) taken 208 times.
✓ Branch 1 (20→31) taken 15289 times.
15497 if (unlikely(map->count + map->tombstones > max_load)) {
179 208 BUG_ON(replacing_tombstone_or_existing_value);
180 208 size_t new_size = map->mask + 1;
181
3/4
✓ Branch 0 (23→24) taken 6 times.
✓ Branch 1 (23→25) taken 202 times.
✗ Branch 2 (24→25) not taken.
✓ Branch 3 (24→27) taken 6 times.
208 if (map->count > map->tombstones || new_size <= 256) {
182 // Only increase the size of the table when the number of
183 // real entries is higher than the number of tombstones.
184 // If the number of real entries is lower, the table is
185 // most likely being filled with tombstones from repeated
186 // insert/remove churn; so we just rehash at the same size
187 // to clean out the tombstones.
188 202 new_size <<= 1;
189
1/2
✗ Branch 0 (25→26) not taken.
✓ Branch 1 (25→27) taken 202 times.
202 if (unlikely(new_size == 0)) {
190 err = EOVERFLOW;
191 goto error;
192 }
193 }
194 208 err = hashmap_resize(map, new_size);
195
1/2
✗ Branch 0 (28→29) not taken.
✓ Branch 1 (28→31) taken 208 times.
208 if (unlikely(err)) {
196 goto error;
197 }
198 }
199
200 return 0;
201
202 error:
203 map->count--;
204 e->key = NULL;
205 e->hash = 0;
206 return err;
207 }
208
209 15276 void *hashmap_insert(HashMap *map, char *key, void *value)
210 {
211 15276 int err = hashmap_do_insert(map, key, value, NULL);
212
1/2
✗ Branch 0 (3→4) not taken.
✓ Branch 1 (3→5) taken 15276 times.
15276 if (unlikely(err)) {
213 fatal_error(__func__, err);
214 }
215 15276 return value;
216 }
217
218 221 void *hashmap_insert_or_replace(HashMap *map, char *key, void *value)
219 {
220 221 void *replaced_value = NULL;
221 221 int err = hashmap_do_insert(map, key, value, &replaced_value);
222
1/2
✗ Branch 0 (3→4) not taken.
✓ Branch 1 (3→5) taken 221 times.
221 if (unlikely(err)) {
223 fatal_error(__func__, err);
224 }
225 221 return replaced_value;
226 }
227
228 // Call a FreeFunction declared with an arbitrary pointer parameter type
229 // without -fsanitize=function pedantry
230 NO_SANITIZE("undefined")
231 3495 static void do_free_value(FreeFunction free_value, void *value)
232 {
233 3495 free_value(value);
234 3495 }
235
236 // Remove all entries without freeing the table
237 493 void hashmap_clear(HashMap *map, FreeFunction free_value)
238 {
239
2/2
✓ Branch 0 (2→3) taken 253 times.
✓ Branch 1 (2→13) taken 240 times.
493 if (unlikely(!map->entries)) {
240 return;
241 }
242
243 253 size_t count = 0;
244
2/2
✓ Branch 0 (9→4) taken 3953 times.
✓ Branch 1 (9→10) taken 253 times.
4206 for (HashMapIter it = hashmap_iter(map); hashmap_next(&it); count++) {
245 3953 hashmap_free_key(map, it.entry->key);
246
2/2
✓ Branch 0 (5→6) taken 3495 times.
✓ Branch 1 (5→7) taken 458 times.
3953 if (free_value) {
247 3495 do_free_value(free_value, it.entry->value);
248 }
249 }
250
251 253 BUG_ON(count != map->count);
252 253 size_t len = map->mask + 1;
253 253 map->count = 0;
254 253 memset(map->entries, 0, len * sizeof(*map->entries));
255 }
256
257 481 void hashmap_free(HashMap *map, FreeFunction free_value)
258 {
259 481 hashmap_clear(map, free_value);
260 481 free(map->entries);
261 481 *map = (HashMap){.flags = map->flags};
262 481 }
263