GCC Code Coverage Report


Directory: ./
File: hash_map.c
Date: 2025-11-30 14:18:36
Exec Total Coverage
Lines: 216 216 100.0%
Functions: 15 15 100.0%
Branches: 118 122 96.7%

Line Branch Exec Source
1 /*
2 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
3 *
4 * Copyright 2021 Mike Becker, Olaf Wintermann All rights reserved.
5 *
6 * Redistribution and use in source and binary forms, with or without
7 * modification, are permitted provided that the following conditions are met:
8 *
9 * 1. Redistributions of source code must retain the above copyright
10 * notice, this list of conditions and the following disclaimer.
11 *
12 * 2. Redistributions in binary form must reproduce the above copyright
13 * notice, this list of conditions and the following disclaimer in the
14 * documentation and/or other materials provided with the distribution.
15 *
16 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
17 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
18 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
19 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
20 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
21 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
22 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
23 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
24 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
25 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
26 * POSSIBILITY OF SUCH DAMAGE.
27 */
28
29 #include "cx/hash_map.h"
30
31 #include <string.h>
32 #include <assert.h>
33 #include <errno.h>
34
35 struct cx_hash_map_element_s {
36 /** A pointer to the next element in the current bucket. */
37 struct cx_hash_map_element_s *next;
38
39 /** The corresponding key. */
40 CxHashKey key;
41
42 /** The value data. */
43 char data[];
44 };
45
46 206 static void cx_hash_map_clear(struct cx_map_s *map) {
47 206 struct cx_hash_map_s *hash_map = (struct cx_hash_map_s *) map;
48
2/2
✓ Branch 0 (19→3) taken 3244 times.
✓ Branch 1 (19→20) taken 206 times.
3450 for (size_t i = 0; i < hash_map->bucket_count; i++) {
49 3244 struct cx_hash_map_element_s *elem = hash_map->buckets[i];
50
2/2
✓ Branch 0 (3→4) taken 274 times.
✓ Branch 1 (3→18) taken 2970 times.
3244 if (elem != NULL) {
51 do {
52 281 struct cx_hash_map_element_s *next = elem->next;
53 // invoke the destructor
54
8/8
✓ Branch 0 (4→5) taken 4 times.
✓ Branch 1 (4→9) taken 277 times.
✓ Branch 2 (5→6) taken 2 times.
✓ Branch 3 (5→7) taken 2 times.
✓ Branch 4 (9→10) taken 44 times.
✓ Branch 5 (9→14) taken 237 times.
✓ Branch 6 (10→11) taken 42 times.
✓ Branch 7 (10→12) taken 2 times.
281 cx_invoke_destructor(map, elem->data);
55 // free the key data
56 281 cxFree(map->collection.allocator, (void *) elem->key.data);
57 // free the node
58 281 cxFree(map->collection.allocator, elem);
59 // proceed
60 281 elem = next;
61
2/2
✓ Branch 0 (16→4) taken 7 times.
✓ Branch 1 (16→17) taken 274 times.
281 } while (elem != NULL);
62
63 // do not leave a dangling pointer
64 274 hash_map->buckets[i] = NULL;
65 }
66 }
67 206 map->collection.size = 0;
68 206 }
69
70 187 static void cx_hash_map_destructor(struct cx_map_s *map) {
71 187 struct cx_hash_map_s *hash_map = (struct cx_hash_map_s *) map;
72
73 // free the buckets
74 187 cx_hash_map_clear(map);
75 187 cxFree(map->collection.allocator, hash_map->buckets);
76
77 // free the map structure
78 187 cxFree(map->collection.allocator, map);
79 187 }
80
81 365 static void *cx_hash_map_put(
82 CxMap *map,
83 CxHashKey key,
84 void *value
85 ) {
86 365 struct cx_hash_map_s *hash_map = (struct cx_hash_map_s *) map;
87 365 const CxAllocator *allocator = map->collection.allocator;
88
89 365 uint64_t hash = key.hash;
90
2/2
✓ Branch 0 (2→3) taken 13 times.
✓ Branch 1 (2→5) taken 352 times.
365 if (hash == 0) {
91 13 cx_hash_murmur(&key);
92 13 hash = key.hash;
93 }
94
95 365 size_t slot = hash % hash_map->bucket_count;
96 365 struct cx_hash_map_element_s *elm = hash_map->buckets[slot];
97 365 struct cx_hash_map_element_s *prev = NULL;
98
99
4/4
✓ Branch 0 (7→8) taken 31 times.
✓ Branch 1 (7→9) taken 342 times.
✓ Branch 2 (8→6) taken 8 times.
✓ Branch 3 (8→9) taken 23 times.
373 while (elm != NULL && elm->key.hash < hash) {
100 8 prev = elm;
101 8 elm = elm->next;
102 }
103
104
4/4
✓ Branch 0 (9→10) taken 23 times.
✓ Branch 1 (9→28) taken 342 times.
✓ Branch 2 (11→12) taken 13 times.
✓ Branch 3 (11→28) taken 10 times.
365 if (elm != NULL && cx_hash_key_cmp(&elm->key, &key) == 0) {
105 // overwrite existing element, but call destructors first
106
8/8
✓ Branch 0 (12→13) taken 2 times.
✓ Branch 1 (12→17) taken 11 times.
✓ Branch 2 (13→14) taken 1 times.
✓ Branch 3 (13→15) taken 1 times.
✓ Branch 4 (17→18) taken 4 times.
✓ Branch 5 (17→22) taken 9 times.
✓ Branch 6 (18→19) taken 3 times.
✓ Branch 7 (18→20) taken 1 times.
13 cx_invoke_destructor(map, elm->data);
107
2/2
✓ Branch 0 (22→23) taken 5 times.
✓ Branch 1 (22→24) taken 8 times.
13 if (value == NULL) {
108 5 memset(elm->data, 0, map->collection.elem_size);
109
2/2
✓ Branch 0 (24→25) taken 5 times.
✓ Branch 1 (24→26) taken 3 times.
8 } else if (map->collection.store_pointer) {
110 5 memcpy(elm->data, &value, sizeof(void *));
111 } else {
112 3 memcpy(elm->data, value, map->collection.elem_size);
113 }
114 } else {
115 // allocate new element
116 352 struct cx_hash_map_element_s *e = cxMalloc(
117 allocator,
118 352 sizeof(struct cx_hash_map_element_s) + map->collection.elem_size
119 );
120 if (e == NULL) return NULL; // LCOV_EXCL_LINE
121
122 // write the value
123
2/2
✓ Branch 0 (31→32) taken 119 times.
✓ Branch 1 (31→33) taken 233 times.
352 if (value == NULL) {
124 119 memset(e->data, 0, map->collection.elem_size);
125
2/2
✓ Branch 0 (33→34) taken 134 times.
✓ Branch 1 (33→35) taken 99 times.
233 } else if (map->collection.store_pointer) {
126 134 memcpy(e->data, &value, sizeof(void *));
127 } else {
128 99 memcpy(e->data, value, map->collection.elem_size);
129 }
130
131 // copy the key
132 352 void *kd = cxMalloc(allocator, key.len);
133 if (kd == NULL) { // LCOV_EXCL_START
134 cxFree(allocator, e);
135 return NULL;
136 } // LCOV_EXCL_STOP
137 352 memcpy(kd, key.data, key.len);
138 352 e->key.data = kd;
139 352 e->key.len = key.len;
140 352 e->key.hash = hash;
141
142 // insert the element into the linked list
143
2/2
✓ Branch 0 (40→41) taken 346 times.
✓ Branch 1 (40→42) taken 6 times.
352 if (prev == NULL) {
144 346 hash_map->buckets[slot] = e;
145 } else {
146 6 prev->next = e;
147 }
148 352 e->next = elm;
149 352 elm = e;
150
151 // increase the size
152 352 map->collection.size++;
153 }
154
155 // return pointer to the element
156 365 return elm->data;
157 }
158
159 71 static void cx_hash_map_unlink(
160 struct cx_hash_map_s *hash_map,
161 size_t slot,
162 struct cx_hash_map_element_s *prev,
163 struct cx_hash_map_element_s *elm
164 ) {
165 // unlink
166
2/2
✓ Branch 0 (2→3) taken 69 times.
✓ Branch 1 (2→4) taken 2 times.
71 if (prev == NULL) {
167 69 hash_map->buckets[slot] = elm->next;
168 } else {
169 2 prev->next = elm->next;
170 }
171 // free element
172 71 cxFree(hash_map->base.collection.allocator, (void *) elm->key.data);
173 71 cxFree(hash_map->base.collection.allocator, elm);
174 // decrease size
175 71 hash_map->base.collection.size--;
176 71 }
177
178 /**
179 * Helper function to avoid code duplication.
180 *
181 * If @p remove is true, and @p targetbuf is @c NULL, the element
182 * will be destroyed when found.
183 *
184 * If @p remove is true, and @p targetbuf is set, the element will
185 * be copied to that buffer and no destructor function is called.
186 *
187 * If @p remove is false, @p targetbuf must not be non-null and
188 * either the pointer, when the map is storing pointers, is copied
189 * to the target buffer, or a pointer to the stored object will
190 * be copied to the target buffer.
191 *
192 * @param map the map
193 * @param key the key to look up
194 * @param targetbuf see description
195 * @param remove flag indicating whether the looked up entry shall be removed
196 * @return zero, if the key was found, non-zero otherwise
197 */
198 373 static int cx_hash_map_get_remove(
199 CxMap *map,
200 CxHashKey key,
201 void *targetbuf,
202 bool remove
203 ) {
204 373 struct cx_hash_map_s *hash_map = (struct cx_hash_map_s *) map;
205
206 373 uint64_t hash = key.hash;
207
2/2
✓ Branch 0 (2→3) taken 17 times.
✓ Branch 1 (2→5) taken 356 times.
373 if (hash == 0) {
208 17 cx_hash_murmur(&key);
209 17 hash = key.hash;
210 }
211
212 373 size_t slot = hash % hash_map->bucket_count;
213 373 struct cx_hash_map_element_s *elm = hash_map->buckets[slot];
214 373 struct cx_hash_map_element_s *prev = NULL;
215
4/4
✓ Branch 0 (28→29) taken 278 times.
✓ Branch 1 (28→30) taken 105 times.
✓ Branch 2 (29→6) taken 273 times.
✓ Branch 3 (29→30) taken 5 times.
383 while (elm && elm->key.hash <= hash) {
216
2/2
✓ Branch 0 (7→8) taken 263 times.
✓ Branch 1 (7→27) taken 10 times.
273 if (cx_hash_key_cmp(&elm->key, &key) == 0) {
217
2/2
✓ Branch 0 (8→9) taken 58 times.
✓ Branch 1 (8→22) taken 205 times.
263 if (remove) {
218
2/2
✓ Branch 0 (9→10) taken 36 times.
✓ Branch 1 (9→20) taken 22 times.
58 if (targetbuf == NULL) {
219
8/8
✓ Branch 0 (10→11) taken 2 times.
✓ Branch 1 (10→15) taken 34 times.
✓ Branch 2 (11→12) taken 1 times.
✓ Branch 3 (11→13) taken 1 times.
✓ Branch 4 (15→16) taken 2 times.
✓ Branch 5 (15→21) taken 34 times.
✓ Branch 6 (16→17) taken 1 times.
✓ Branch 7 (16→18) taken 1 times.
36 cx_invoke_destructor(map, elm->data);
220 } else {
221 22 memcpy(targetbuf, elm->data, map->collection.elem_size);
222 }
223 58 cx_hash_map_unlink(hash_map, slot, prev, elm);
224 } else {
225 assert(targetbuf != NULL);
226 205 void *data = NULL;
227
2/2
✓ Branch 0 (22→23) taken 136 times.
✓ Branch 1 (22→24) taken 69 times.
205 if (map->collection.store_pointer) {
228 136 data = *(void **) elm->data;
229 } else {
230 69 data = elm->data;
231 }
232 205 memcpy(targetbuf, &data, sizeof(void *));
233 }
234 263 return 0;
235 }
236 10 prev = elm;
237 10 elm = prev->next;
238 }
239
240 110 return 1;
241 }
242
243 311 static void *cx_hash_map_get(
244 const CxMap *map,
245 CxHashKey key
246 ) {
247 // we can safely cast, because we know the map stays untouched
248 311 void *ptr = NULL;
249 311 int found = cx_hash_map_get_remove((CxMap *) map, key, &ptr, false);
250
2/2
✓ Branch 0 (3→4) taken 205 times.
✓ Branch 1 (3→5) taken 106 times.
311 return found == 0 ? ptr : NULL;
251 }
252
253 62 static int cx_hash_map_remove(
254 CxMap *map,
255 CxHashKey key,
256 void *targetbuf
257 ) {
258 62 return cx_hash_map_get_remove(map, key, targetbuf, true);
259 }
260
261 193 static void *cx_hash_map_iter_current_entry(const void *it) {
262 193 const CxMapIterator *iter = it;
263 // we have to cast away const, because of the signature
264 193 return (void*) &iter->entry;
265 }
266
267 109 static void *cx_hash_map_iter_current_key(const void *it) {
268 109 const CxMapIterator *iter = it;
269 109 return (void*) iter->entry.key;
270 }
271
272 108 static void *cx_hash_map_iter_current_value(const void *it) {
273 108 const CxMapIterator *iter = it;
274 108 return iter->entry.value;
275 }
276
277 474 static bool cx_hash_map_iter_valid(const void *it) {
278 474 const CxMapIterator *iter = it;
279 474 return iter->elem != NULL;
280 }
281
282 404 static void cx_hash_map_iter_next(void *it) {
283 404 CxMapIterator *iter = it;
284 404 CxMap *map = iter->map;
285 404 struct cx_hash_map_s *hmap = (struct cx_hash_map_s *) map;
286 404 struct cx_hash_map_element_s *elm = iter->elem;
287
288 // remove current element, if asked
289
2/2
✓ Branch 0 (2→3) taken 13 times.
✓ Branch 1 (2→19) taken 391 times.
404 if (iter->base.remove) {
290
291 // clear the flag
292 13 iter->base.remove = false;
293
294 // determine the next element
295 13 struct cx_hash_map_element_s *next = elm->next;
296
297 // search the previous element
298 13 struct cx_hash_map_element_s *prev = NULL;
299
2/2
✓ Branch 0 (3→4) taken 2 times.
✓ Branch 1 (3→7) taken 11 times.
13 if (hmap->buckets[iter->slot] != elm) {
300 2 prev = hmap->buckets[iter->slot];
301
2/2
✓ Branch 0 (6→5) taken 1 times.
✓ Branch 1 (6→7) taken 2 times.
3 while (prev->next != elm) {
302 1 prev = prev->next;
303 }
304 }
305
306 // destroy
307
8/8
✓ Branch 0 (7→8) taken 4 times.
✓ Branch 1 (7→12) taken 9 times.
✓ Branch 2 (8→9) taken 2 times.
✓ Branch 3 (8→10) taken 2 times.
✓ Branch 4 (12→13) taken 4 times.
✓ Branch 5 (12→17) taken 9 times.
✓ Branch 6 (13→14) taken 2 times.
✓ Branch 7 (13→15) taken 2 times.
13 cx_invoke_destructor(map, elm->data);
308
309 // unlink
310 13 cx_hash_map_unlink(hmap, iter->slot, prev, elm);
311 13 iter->elem_count--;
312
313 // advance
314 13 elm = next;
315 } else {
316 // just advance
317 391 elm = elm->next;
318 391 iter->index++;
319 }
320
321 // search the next bucket, if required
322
4/4
✓ Branch 0 (22→23) taken 888 times.
✓ Branch 1 (22→24) taken 322 times.
✓ Branch 2 (23→21) taken 806 times.
✓ Branch 3 (23→24) taken 82 times.
1210 while (elm == NULL && ++iter->slot < hmap->bucket_count) {
323 806 elm = hmap->buckets[iter->slot];
324 }
325 404 iter->elem = elm;
326
327 // copy data to a location where the iterator can point to
328 // we need to do it here, because the iterator function call
329 // must not modify the iterator (the parameter is const)
330
2/2
✓ Branch 0 (24→25) taken 322 times.
✓ Branch 1 (24→28) taken 82 times.
404 if (elm != NULL) {
331 322 iter->entry.key = &elm->key;
332
2/2
✓ Branch 0 (25→26) taken 262 times.
✓ Branch 1 (25→27) taken 60 times.
322 if (map->collection.store_pointer) {
333 262 iter->entry.value = *(void **) elm->data;
334 } else {
335 60 iter->entry.value = elm->data;
336 }
337 }
338 404 }
339
340 91 static CxMapIterator cx_hash_map_iterator(
341 const CxMap *map,
342 enum cx_map_iterator_type type
343 ) {
344 CxMapIterator iter;
345
346 91 iter.map = (CxMap*) map;
347 91 iter.elem_count = map->collection.size;
348
349
3/4
✓ Branch 0 (2→3) taken 46 times.
✓ Branch 1 (2→4) taken 22 times.
✓ Branch 2 (2→5) taken 23 times.
✗ Branch 3 (2→6) not taken.
91 switch (type) {
350 46 case CX_MAP_ITERATOR_PAIRS:
351 46 iter.elem_size = sizeof(CxMapEntry);
352 46 iter.base.current = cx_hash_map_iter_current_entry;
353 46 break;
354 22 case CX_MAP_ITERATOR_KEYS:
355 22 iter.elem_size = sizeof(CxHashKey);
356 22 iter.base.current = cx_hash_map_iter_current_key;
357 22 break;
358 23 case CX_MAP_ITERATOR_VALUES:
359 23 iter.elem_size = map->collection.elem_size;
360 23 iter.base.current = cx_hash_map_iter_current_value;
361 23 break;
362 91 default:
363 assert(false); // LCOV_EXCL_LINE
364 }
365
366 91 iter.base.valid = cx_hash_map_iter_valid;
367 91 iter.base.next = cx_hash_map_iter_next;
368 91 iter.base.remove = false;
369 91 iter.base.allow_remove = true;
370
371 91 iter.slot = 0;
372 91 iter.index = 0;
373
374
2/2
✓ Branch 0 (6→7) taken 88 times.
✓ Branch 1 (6→13) taken 3 times.
91 if (map->collection.size > 0) {
375 88 struct cx_hash_map_s *hash_map = (struct cx_hash_map_s *) map;
376 88 struct cx_hash_map_element_s *elm = hash_map->buckets[0];
377
2/2
✓ Branch 0 (9→8) taken 60 times.
✓ Branch 1 (9→10) taken 88 times.
148 while (elm == NULL) {
378 60 elm = hash_map->buckets[++iter.slot];
379 }
380 88 iter.elem = elm;
381 88 iter.entry.key = &elm->key;
382
2/2
✓ Branch 0 (10→11) taken 63 times.
✓ Branch 1 (10→12) taken 25 times.
88 if (map->collection.store_pointer) {
383 63 iter.entry.value = *(void **) elm->data;
384 } else {
385 25 iter.entry.value = elm->data;
386 }
387 } else {
388 3 iter.elem = NULL;
389 }
390
391 91 return iter;
392 }
393
394 static cx_map_class cx_hash_map_class = {
395 cx_hash_map_destructor,
396 cx_hash_map_clear,
397 cx_hash_map_put,
398 cx_hash_map_get,
399 cx_hash_map_remove,
400 cx_hash_map_iterator,
401 };
402
403 187 CxMap *cxHashMapCreate(
404 const CxAllocator *allocator,
405 size_t itemsize,
406 size_t buckets
407 ) {
408
2/2
✓ Branch 0 (2→3) taken 58 times.
✓ Branch 1 (2→4) taken 129 times.
187 if (allocator == NULL) {
409 58 allocator = cxDefaultAllocator;
410 }
411
412
2/2
✓ Branch 0 (4→5) taken 180 times.
✓ Branch 1 (4→6) taken 7 times.
187 if (buckets == 0) {
413 // implementation defined default
414 180 buckets = 16;
415 }
416
417 187 struct cx_hash_map_s *map = cxCalloc(allocator, 1,
418 sizeof(struct cx_hash_map_s));
419
1/2
✗ Branch 0 (7→8) not taken.
✓ Branch 1 (7→9) taken 187 times.
187 if (map == NULL) return NULL;
420
421 // initialize hash map members
422 187 map->bucket_count = buckets;
423 187 map->buckets = cxCalloc(allocator, buckets,
424 sizeof(struct cx_hash_map_element_s *));
425 if (map->buckets == NULL) { // LCOV_EXCL_START
426 cxFree(allocator, map);
427 return NULL;
428 } // LCOV_EXCL_STOP
429
430 // initialize base members
431 187 map->base.cl = &cx_hash_map_class;
432 187 map->base.collection.allocator = allocator;
433
434
2/2
✓ Branch 0 (13→14) taken 39 times.
✓ Branch 1 (13→15) taken 148 times.
187 if (itemsize > 0) {
435 39 map->base.collection.elem_size = itemsize;
436 } else {
437 148 map->base.collection.elem_size = sizeof(void *);
438 148 map->base.collection.store_pointer = true;
439 }
440
441 187 return (CxMap *) map;
442 }
443
444 6 int cxMapRehash(CxMap *map) {
445 6 struct cx_hash_map_s *hash_map = (struct cx_hash_map_s *) map;
446
2/2
✓ Branch 0 (2→3) taken 1 times.
✓ Branch 1 (2→23) taken 5 times.
6 if (map->collection.size > ((hash_map->bucket_count * 3) >> 2)) {
447
448 1 size_t new_bucket_count = (map->collection.size * 5) >> 1;
449
1/2
✗ Branch 0 (3→4) not taken.
✓ Branch 1 (3→5) taken 1 times.
1 if (new_bucket_count < hash_map->bucket_count) {
450 // LCOV_EXCL_START
451 errno = EOVERFLOW;
452 return 1;
453 } // LCOV_EXCL_STOP
454 1 struct cx_hash_map_element_s **new_buckets = cxCalloc(
455 map->collection.allocator,
456 new_bucket_count, sizeof(struct cx_hash_map_element_s *)
457 );
458
459 if (new_buckets == NULL) return 1; // LCOV_EXCL_LINE
460
461 // iterate through the elements and assign them to their new slots
462
2/2
✓ Branch 0 (20→9) taken 7 times.
✓ Branch 1 (20→21) taken 1 times.
8 for (size_t slot = 0; slot < hash_map->bucket_count; slot++) {
463 7 struct cx_hash_map_element_s *elm = hash_map->buckets[slot];
464
2/2
✓ Branch 0 (18→10) taken 10 times.
✓ Branch 1 (18→19) taken 7 times.
17 while (elm != NULL) {
465 10 struct cx_hash_map_element_s *next = elm->next;
466 10 size_t new_slot = elm->key.hash % new_bucket_count;
467
468 // find position where to insert
469 10 struct cx_hash_map_element_s *bucket_next = new_buckets[new_slot];
470 10 struct cx_hash_map_element_s *bucket_prev = NULL;
471
2/2
✓ Branch 0 (12→13) taken 1 times.
✓ Branch 1 (12→14) taken 10 times.
11 while (bucket_next != NULL &&
472
1/2
✓ Branch 0 (13→11) taken 1 times.
✗ Branch 1 (13→14) not taken.
1 bucket_next->key.hash < elm->key.hash) {
473 1 bucket_prev = bucket_next;
474 1 bucket_next = bucket_next->next;
475 }
476
477 // insert
478
2/2
✓ Branch 0 (14→15) taken 9 times.
✓ Branch 1 (14→16) taken 1 times.
10 if (bucket_prev == NULL) {
479 9 elm->next = new_buckets[new_slot];
480 9 new_buckets[new_slot] = elm;
481 } else {
482 1 bucket_prev->next = elm;
483 1 elm->next = bucket_next;
484 }
485
486 // advance
487 10 elm = next;
488 }
489 }
490
491 // assign result to the map
492 1 hash_map->bucket_count = new_bucket_count;
493 1 cxFree(map->collection.allocator, hash_map->buckets);
494 1 hash_map->buckets = new_buckets;
495 }
496 6 return 0;
497 }
498