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