GCC Code Coverage Report


Directory: ./
File: array_list.c
Date: 2025-04-06 13:22:55
Exec Total Coverage
Lines: 378 403 93.8%
Functions: 30 30 100.0%
Branches: 208 244 85.2%

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/array_list.h"
30 #include "cx/compare.h"
31 #include <assert.h>
32 #include <string.h>
33 #include <errno.h>
34
35 // Default array reallocator
36
37 5 static void *cx_array_default_realloc(
38 void *array,
39 size_t capacity,
40 size_t elem_size,
41 cx_attr_unused CxArrayReallocator *alloc
42 ) {
43 size_t n;
44
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 5 times.
5 if (cx_szmul(capacity, elem_size, &n)) {
45 errno = EOVERFLOW;
46 return NULL;
47 }
48 5 return realloc(array, n);
49 }
50
51 CxArrayReallocator cx_array_default_reallocator_impl = {
52 cx_array_default_realloc, NULL, NULL, 0, 0
53 };
54
55 CxArrayReallocator *cx_array_default_reallocator = &cx_array_default_reallocator_impl;
56
57 // Stack-aware array reallocator
58
59 239 static void *cx_array_advanced_realloc(
60 void *array,
61 size_t capacity,
62 size_t elem_size,
63 cx_attr_unused CxArrayReallocator *alloc
64 ) {
65 // check for overflow
66 size_t n;
67
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 239 times.
239 if (cx_szmul(capacity, elem_size, &n)) {
68 errno = EOVERFLOW;
69 return NULL;
70 }
71
72 // retrieve the pointer to the actual allocator
73 239 const CxAllocator *al = alloc->ptr1;
74
75 // check if the array is still located on the stack
76 void *newmem;
77
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 237 times.
239 if (array == alloc->ptr2) {
78 2 newmem = cxMalloc(al, n);
79
2/4
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 2 times.
✗ Branch 3 not taken.
2 if (newmem != NULL && array != NULL) {
80 2 memcpy(newmem, array, n);
81 }
82 } else {
83 237 newmem = cxRealloc(al, array, n);
84 }
85 239 return newmem;
86 }
87
88 499 struct cx_array_reallocator_s cx_array_reallocator(
89 const struct cx_allocator_s *allocator,
90 const void *stackmem
91 ) {
92
2/2
✓ Branch 0 taken 265 times.
✓ Branch 1 taken 234 times.
499 if (allocator == NULL) {
93 265 allocator = cxDefaultAllocator;
94 }
95 499 return (struct cx_array_reallocator_s) {
96 cx_array_advanced_realloc,
97 (void*) allocator, (void*) stackmem,
98 0, 0
99 };
100 }
101
102 // LOW LEVEL ARRAY LIST FUNCTIONS
103
104 244 static size_t cx_array_align_capacity(
105 size_t cap,
106 size_t alignment,
107 size_t max
108 ) {
109
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 244 times.
244 if (cap > max - alignment) {
110 return cap;
111 } else {
112 244 return cap - (cap % alignment) + alignment;
113 }
114 }
115
116 327 int cx_array_reserve(
117 void **array,
118 void *size,
119 void *capacity,
120 unsigned width,
121 size_t elem_size,
122 size_t elem_count,
123 CxArrayReallocator *reallocator
124 ) {
125 // assert pointers
126 assert(array != NULL);
127 assert(size != NULL);
128 assert(capacity != NULL);
129
130 // default reallocator
131
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 325 times.
327 if (reallocator == NULL) {
132 2 reallocator = cx_array_default_reallocator;
133 }
134
135 // determine size and capacity
136 size_t oldcap;
137 size_t oldsize;
138 size_t max_size;
139
3/4
✓ Branch 0 taken 327 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 92 times.
✓ Branch 3 taken 235 times.
327 if (width == 0 || width == sizeof(size_t)) {
140 92 oldcap = *(size_t*) capacity;
141 92 oldsize = *(size_t*) size;
142 92 max_size = SIZE_MAX;
143
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 233 times.
235 } else if (width == sizeof(uint16_t)) {
144 2 oldcap = *(uint16_t*) capacity;
145 2 oldsize = *(uint16_t*) size;
146 2 max_size = UINT16_MAX;
147
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 233 times.
233 } else if (width == sizeof(uint8_t)) {
148 oldcap = *(uint8_t*) capacity;
149 oldsize = *(uint8_t*) size;
150 max_size = UINT8_MAX;
151 }
152 #if CX_WORDSIZE == 64
153
1/2
✓ Branch 0 taken 233 times.
✗ Branch 1 not taken.
233 else if (width == sizeof(uint32_t)) {
154 233 oldcap = *(uint32_t*) capacity;
155 233 oldsize = *(uint32_t*) size;
156 233 max_size = UINT32_MAX;
157 }
158 #endif
159 else {
160 errno = EINVAL;
161 return 1;
162 }
163
164 // assert that the array is allocated when it has capacity
165 assert(*array != NULL || oldcap == 0);
166
167 // check for overflow
168
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 327 times.
327 if (elem_count > max_size - oldsize) {
169 errno = EOVERFLOW;
170 return 1;
171 }
172
173 // determine new capacity
174 327 size_t newcap = oldsize + elem_count;
175
176 // reallocate if possible
177
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 325 times.
327 if (newcap > oldcap) {
178 // calculate new capacity (next number divisible by 16)
179 2 newcap = cx_array_align_capacity(newcap, 16, max_size);
180
181 // perform reallocation
182 2 void *newmem = reallocator->realloc(
183 *array, newcap, elem_size, reallocator
184 );
185
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 2 times.
2 if (newmem == NULL) {
186 return 1; // LCOV_EXCL_LINE
187 }
188
189 // store new pointer
190 2 *array = newmem;
191
192 // store new capacity
193
2/4
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 2 times.
2 if (width == 0 || width == sizeof(size_t)) {
194 *(size_t*) capacity = newcap;
195
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 1 times.
2 } else if (width == sizeof(uint16_t)) {
196 1 *(uint16_t*) capacity = (uint16_t) newcap;
197
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
1 } else if (width == sizeof(uint8_t)) {
198 *(uint8_t*) capacity = (uint8_t) newcap;
199 }
200 #if CX_WORDSIZE == 64
201
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 else if (width == sizeof(uint32_t)) {
202 1 *(uint32_t*) capacity = (uint32_t) newcap;
203 }
204 #endif
205 }
206
207 327 return 0;
208 }
209
210 4348 int cx_array_copy(
211 void **target,
212 void *size,
213 void *capacity,
214 unsigned width,
215 size_t index,
216 const void *src,
217 size_t elem_size,
218 size_t elem_count,
219 CxArrayReallocator *reallocator
220 ) {
221 // assert pointers
222 assert(target != NULL);
223 assert(size != NULL);
224 assert(capacity != NULL);
225 assert(src != NULL);
226
227 // default reallocator
228
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 4347 times.
4348 if (reallocator == NULL) {
229 1 reallocator = cx_array_default_reallocator;
230 }
231
232 // determine size and capacity
233 size_t oldcap;
234 size_t oldsize;
235 size_t max_size;
236
4/4
✓ Branch 0 taken 333 times.
✓ Branch 1 taken 4015 times.
✓ Branch 2 taken 297 times.
✓ Branch 3 taken 36 times.
4348 if (width == 0 || width == sizeof(size_t)) {
237 4312 oldcap = *(size_t*) capacity;
238 4312 oldsize = *(size_t*) size;
239 4312 max_size = SIZE_MAX;
240
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 36 times.
36 } else if (width == sizeof(uint16_t)) {
241 oldcap = *(uint16_t*) capacity;
242 oldsize = *(uint16_t*) size;
243 max_size = UINT16_MAX;
244
2/2
✓ Branch 0 taken 3 times.
✓ Branch 1 taken 33 times.
36 } else if (width == sizeof(uint8_t)) {
245 3 oldcap = *(uint8_t*) capacity;
246 3 oldsize = *(uint8_t*) size;
247 3 max_size = UINT8_MAX;
248 }
249 #if CX_WORDSIZE == 64
250
2/2
✓ Branch 0 taken 32 times.
✓ Branch 1 taken 1 times.
33 else if (width == sizeof(uint32_t)) {
251 32 oldcap = *(uint32_t*) capacity;
252 32 oldsize = *(uint32_t*) size;
253 32 max_size = UINT32_MAX;
254 }
255 #endif
256 else {
257 1 errno = EINVAL;
258 1 return 1;
259 }
260
261 // assert that the array is allocated when it has capacity
262 assert(*target != NULL || oldcap == 0);
263
264 // check for overflow
265
3/4
✓ Branch 0 taken 4346 times.
✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 4346 times.
4347 if (index > max_size || elem_count > max_size - index) {
266 1 errno = EOVERFLOW;
267 1 return 1;
268 }
269
270 // check if resize is required
271 4346 size_t minsize = index + elem_count;
272 4346 size_t newsize = oldsize < minsize ? minsize : oldsize;
273
274 // reallocate if possible
275 4346 size_t newcap = oldcap;
276
2/2
✓ Branch 0 taken 236 times.
✓ Branch 1 taken 4110 times.
4346 if (newsize > oldcap) {
277 // check, if we need to repair the src pointer
278 236 uintptr_t targetaddr = (uintptr_t) *target;
279 236 uintptr_t srcaddr = (uintptr_t) src;
280 236 bool repairsrc = targetaddr <= srcaddr
281
4/4
✓ Branch 0 taken 204 times.
✓ Branch 1 taken 32 times.
✓ Branch 2 taken 16 times.
✓ Branch 3 taken 188 times.
236 && srcaddr < targetaddr + oldcap * elem_size;
282
283 // calculate new capacity (next number divisible by 16)
284 236 newcap = cx_array_align_capacity(newsize, 16, max_size);
285 assert(newcap > newsize);
286
287 // perform reallocation
288 236 void *newmem = reallocator->realloc(
289 *target, newcap, elem_size, reallocator
290 );
291
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 236 times.
236 if (newmem == NULL) {
292 return 1;
293 }
294
295 // repair src pointer, if necessary
296
2/2
✓ Branch 0 taken 16 times.
✓ Branch 1 taken 220 times.
236 if (repairsrc) {
297 16 src = ((char *) newmem) + (srcaddr - targetaddr);
298 }
299
300 // store new pointer
301 236 *target = newmem;
302 }
303
304 // determine target pointer
305 4346 char *start = *target;
306 4346 start += index * elem_size;
307
308 // copy elements and set new size
309 // note: no overflow check here, b/c we cannot get here w/o allocation
310 4346 memmove(start, src, elem_count * elem_size);
311
312 // if any of size or capacity changed, store them back
313
3/4
✓ Branch 0 taken 158 times.
✓ Branch 1 taken 4188 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 158 times.
4346 if (newsize != oldsize || newcap != oldcap) {
314
4/4
✓ Branch 0 taken 331 times.
✓ Branch 1 taken 3857 times.
✓ Branch 2 taken 297 times.
✓ Branch 3 taken 34 times.
4188 if (width == 0 || width == sizeof(size_t)) {
315 4154 *(size_t*) capacity = newcap;
316 4154 *(size_t*) size = newsize;
317
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 34 times.
34 } else if (width == sizeof(uint16_t)) {
318 *(uint16_t*) capacity = (uint16_t) newcap;
319 *(uint16_t*) size = (uint16_t) newsize;
320
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 32 times.
34 } else if (width == sizeof(uint8_t)) {
321 2 *(uint8_t*) capacity = (uint8_t) newcap;
322 2 *(uint8_t*) size = (uint8_t) newsize;
323 }
324 #if CX_WORDSIZE == 64
325
1/2
✓ Branch 0 taken 32 times.
✗ Branch 1 not taken.
32 else if (width == sizeof(uint32_t)) {
326 32 *(uint32_t*) capacity = (uint32_t) newcap;
327 32 *(uint32_t*) size = (uint32_t) newsize;
328 }
329 #endif
330 }
331
332 // return successfully
333 4346 return 0;
334 }
335
336 24 int cx_array_insert_sorted(
337 void **target,
338 size_t *size,
339 size_t *capacity,
340 cx_compare_func cmp_func,
341 const void *sorted_data,
342 size_t elem_size,
343 size_t elem_count,
344 CxArrayReallocator *reallocator
345 ) {
346 // assert pointers
347 assert(target != NULL);
348 assert(size != NULL);
349 assert(capacity != NULL);
350 assert(cmp_func != NULL);
351 assert(sorted_data != NULL);
352
353 // default reallocator
354
2/2
✓ Branch 0 taken 8 times.
✓ Branch 1 taken 16 times.
24 if (reallocator == NULL) {
355 8 reallocator = cx_array_default_reallocator;
356 }
357
358 // corner case
359
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 24 times.
24 if (elem_count == 0) return 0;
360
361 // overflow check
362
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 24 times.
24 if (elem_count > SIZE_MAX - *size) {
363 errno = EOVERFLOW;
364 return 1;
365 }
366
367 // store some counts
368 24 size_t old_size = *size;
369 24 size_t needed_capacity = old_size + elem_count;
370
371 // if we need more than we have, try a reallocation
372
2/2
✓ Branch 0 taken 6 times.
✓ Branch 1 taken 18 times.
24 if (needed_capacity > *capacity) {
373 6 size_t new_capacity = cx_array_align_capacity(needed_capacity, 16, SIZE_MAX);
374 6 void *new_mem = reallocator->realloc(
375 *target, new_capacity, elem_size, reallocator
376 );
377
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 6 times.
6 if (new_mem == NULL) {
378 // give it up right away, there is no contract
379 // that requires us to insert as much as we can
380 return 1; // LCOV_EXCL_LINE
381 }
382 6 *target = new_mem;
383 6 *capacity = new_capacity;
384 }
385
386 // now we have guaranteed that we can insert everything
387 24 size_t new_size = old_size + elem_count;
388 24 *size = new_size;
389
390 // declare the source and destination indices/pointers
391 24 size_t si = 0, di = 0;
392 24 const char *src = sorted_data;
393 24 char *dest = *target;
394
395 // find the first insertion point
396 24 di = cx_array_binary_search_sup(dest, old_size, elem_size, src, cmp_func);
397 24 dest += di * elem_size;
398
399 // move the remaining elements in the array completely to the right
400 // we will call it the "buffer" for parked elements
401 24 size_t buf_size = old_size - di;
402 24 size_t bi = new_size - buf_size;
403 24 char *bptr = ((char *) *target) + bi * elem_size;
404 24 memmove(bptr, dest, buf_size * elem_size);
405
406 // while there are both source and buffered elements left,
407 // copy them interleaving
408
3/4
✓ Branch 0 taken 39 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 30 times.
✓ Branch 3 taken 9 times.
39 while (si < elem_count && bi < new_size) {
409 // determine how many source elements can be inserted
410 size_t copy_len, bytes_copied;
411 30 copy_len = cx_array_binary_search_sup(
412 src,
413 elem_count - si,
414 elem_size,
415 bptr,
416 cmp_func
417 );
418
419 // copy the source elements
420 30 bytes_copied = copy_len * elem_size;
421 30 memcpy(dest, src, bytes_copied);
422 30 dest += bytes_copied;
423 30 src += bytes_copied;
424 30 si += copy_len;
425
426 // when all source elements are in place, we are done
427
2/2
✓ Branch 0 taken 15 times.
✓ Branch 1 taken 15 times.
30 if (si >= elem_count) break;
428
429 // determine how many buffered elements need to be restored
430 15 copy_len = cx_array_binary_search_sup(
431 bptr,
432 new_size - bi,
433 elem_size,
434 src,
435 cmp_func
436 );
437
438 // restore the buffered elements
439 15 bytes_copied = copy_len * elem_size;
440 15 memmove(dest, bptr, bytes_copied);
441 15 dest += bytes_copied;
442 15 bptr += bytes_copied;
443 15 bi += copy_len;
444 }
445
446 // still source elements left? simply append them
447
2/2
✓ Branch 0 taken 9 times.
✓ Branch 1 taken 15 times.
24 if (si < elem_count) {
448 9 memcpy(dest, src, elem_size * (elem_count - si));
449 }
450
451 // still buffer elements left?
452 // don't worry, we already moved them to the correct place
453
454 24 return 0;
455 }
456
457 253 size_t cx_array_binary_search_inf(
458 const void *arr,
459 size_t size,
460 size_t elem_size,
461 const void *elem,
462 cx_compare_func cmp_func
463 ) {
464 // special case: empty array
465
2/2
✓ Branch 0 taken 44 times.
✓ Branch 1 taken 209 times.
253 if (size == 0) return 0;
466
467 // declare a variable that will contain the compare results
468 int result;
469
470 // cast the array pointer to something we can use offsets with
471 209 const char *array = arr;
472
473 // check the first array element
474 209 result = cmp_func(elem, array);
475
2/2
✓ Branch 0 taken 27 times.
✓ Branch 1 taken 182 times.
209 if (result < 0) {
476 27 return size;
477
2/2
✓ Branch 0 taken 17 times.
✓ Branch 1 taken 165 times.
182 } else if (result == 0) {
478 17 return 0;
479 }
480
481 // special case: there is only one element and that is smaller
482
2/2
✓ Branch 0 taken 30 times.
✓ Branch 1 taken 135 times.
165 if (size == 1) return 0;
483
484 // check the last array element
485 135 result = cmp_func(elem, array + elem_size * (size - 1));
486
2/2
✓ Branch 0 taken 25 times.
✓ Branch 1 taken 110 times.
135 if (result >= 0) {
487 25 return size - 1;
488 }
489
490 // the element is now guaranteed to be somewhere in the list
491 // so start the binary search
492 110 size_t left_index = 1;
493 110 size_t right_index = size - 1;
494 size_t pivot_index;
495
496
2/2
✓ Branch 0 taken 300 times.
✓ Branch 1 taken 75 times.
375 while (left_index <= right_index) {
497 300 pivot_index = left_index + (right_index - left_index) / 2;
498 300 const char *arr_elem = array + pivot_index * elem_size;
499 300 result = cmp_func(elem, arr_elem);
500
2/2
✓ Branch 0 taken 35 times.
✓ Branch 1 taken 265 times.
300 if (result == 0) {
501 // found it!
502 35 return pivot_index;
503
2/2
✓ Branch 0 taken 134 times.
✓ Branch 1 taken 131 times.
265 } else if (result < 0) {
504 // element is smaller than pivot, continue search left
505 134 right_index = pivot_index - 1;
506 } else {
507 // element is larger than pivot, continue search right
508 131 left_index = pivot_index + 1;
509 }
510 }
511
512 // report the largest upper bound
513
2/2
✓ Branch 0 taken 58 times.
✓ Branch 1 taken 17 times.
75 return result < 0 ? (pivot_index - 1) : pivot_index;
514 }
515
516 68 size_t cx_array_binary_search(
517 const void *arr,
518 size_t size,
519 size_t elem_size,
520 const void *elem,
521 cx_compare_func cmp_func
522 ) {
523 68 size_t index = cx_array_binary_search_inf(
524 arr, size, elem_size, elem, cmp_func
525 );
526
4/4
✓ Branch 0 taken 63 times.
✓ Branch 1 taken 5 times.
✓ Branch 2 taken 55 times.
✓ Branch 3 taken 8 times.
131 if (index < size &&
527 63 cmp_func(((const char *) arr) + index * elem_size, elem) == 0) {
528 55 return index;
529 } else {
530 13 return size;
531 }
532 }
533
534 173 size_t cx_array_binary_search_sup(
535 const void *arr,
536 size_t size,
537 size_t elem_size,
538 const void *elem,
539 cx_compare_func cmp_func
540 ) {
541 173 size_t inf = cx_array_binary_search_inf(
542 arr, size, elem_size, elem, cmp_func
543 );
544
2/2
✓ Branch 0 taken 63 times.
✓ Branch 1 taken 110 times.
173 if (inf == size) {
545 // no infimum means, first element is supremum
546 63 return 0;
547
2/2
✓ Branch 1 taken 3 times.
✓ Branch 2 taken 107 times.
110 } else if (cmp_func(((const char *) arr) + inf * elem_size, elem) == 0) {
548 3 return inf;
549 } else {
550 107 return inf + 1;
551 }
552 }
553
554 #ifndef CX_ARRAY_SWAP_SBO_SIZE
555 #define CX_ARRAY_SWAP_SBO_SIZE 128
556 #endif
557 const unsigned cx_array_swap_sbo_size = CX_ARRAY_SWAP_SBO_SIZE;
558
559 74 void cx_array_swap(
560 void *arr,
561 size_t elem_size,
562 size_t idx1,
563 size_t idx2
564 ) {
565 assert(arr != NULL);
566
567 // short circuit
568
2/2
✓ Branch 0 taken 3 times.
✓ Branch 1 taken 71 times.
74 if (idx1 == idx2) return;
569
570 char sbo_mem[CX_ARRAY_SWAP_SBO_SIZE];
571 void *tmp;
572
573 // decide if we can use the local buffer
574
2/2
✓ Branch 0 taken 7 times.
✓ Branch 1 taken 64 times.
71 if (elem_size > CX_ARRAY_SWAP_SBO_SIZE) {
575 7 tmp = malloc(elem_size);
576 // we don't want to enforce error handling
577
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 7 times.
7 if (tmp == NULL) abort();
578 } else {
579 64 tmp = sbo_mem;
580 }
581
582 // calculate memory locations
583 71 char *left = arr, *right = arr;
584 71 left += idx1 * elem_size;
585 71 right += idx2 * elem_size;
586
587 // three-way swap
588 71 memcpy(tmp, left, elem_size);
589 71 memcpy(left, right, elem_size);
590 71 memcpy(right, tmp, elem_size);
591
592 // free dynamic memory, if it was needed
593
2/2
✓ Branch 0 taken 7 times.
✓ Branch 1 taken 64 times.
71 if (tmp != sbo_mem) {
594 7 free(tmp);
595 }
596 }
597
598 // HIGH LEVEL ARRAY LIST FUNCTIONS
599
600 typedef struct {
601 struct cx_list_s base;
602 void *data;
603 size_t capacity;
604 CxArrayReallocator reallocator;
605 } cx_array_list;
606
607 80 static void cx_arl_destructor(struct cx_list_s *list) {
608 80 cx_array_list *arl = (cx_array_list *) list;
609
610 80 char *ptr = arl->data;
611
612
2/2
✓ Branch 0 taken 5 times.
✓ Branch 1 taken 75 times.
80 if (list->collection.simple_destructor) {
613
2/2
✓ Branch 0 taken 25 times.
✓ Branch 1 taken 5 times.
30 for (size_t i = 0; i < list->collection.size; i++) {
614
2/2
✓ Branch 0 taken 13 times.
✓ Branch 1 taken 12 times.
25 cx_invoke_simple_destructor(list, ptr);
615 25 ptr += list->collection.elem_size;
616 }
617 }
618
2/2
✓ Branch 0 taken 3 times.
✓ Branch 1 taken 77 times.
80 if (list->collection.advanced_destructor) {
619
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 3 times.
4 for (size_t i = 0; i < list->collection.size; i++) {
620
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 cx_invoke_advanced_destructor(list, ptr);
621 1 ptr += list->collection.elem_size;
622 }
623 }
624
625 80 cxFree(list->collection.allocator, arl->data);
626 80 cxFree(list->collection.allocator, list);
627 80 }
628
629 3859 static size_t cx_arl_insert_array(
630 struct cx_list_s *list,
631 size_t index,
632 const void *array,
633 size_t n
634 ) {
635 // out of bounds and special case check
636
3/4
✓ Branch 0 taken 3857 times.
✓ Branch 1 taken 2 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 3857 times.
3859 if (index > list->collection.size || n == 0) return 0;
637
638 // get a correctly typed pointer to the list
639 3857 cx_array_list *arl = (cx_array_list *) list;
640
641 // do we need to move some elements?
642
2/2
✓ Branch 0 taken 80 times.
✓ Branch 1 taken 3777 times.
3857 if (index < list->collection.size) {
643 80 const char *first_to_move = (const char *) arl->data;
644 80 first_to_move += index * list->collection.elem_size;
645 80 size_t elems_to_move = list->collection.size - index;
646 80 size_t start_of_moved = index + n;
647
648
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 80 times.
80 if (cx_array_copy(
649 &arl->data,
650 80 &list->collection.size,
651 80 &arl->capacity,
652 0,
653 start_of_moved,
654 first_to_move,
655 list->collection.elem_size,
656 elems_to_move,
657 &arl->reallocator
658 )) {
659 // if moving existing elems is unsuccessful, abort
660 return 0;
661 }
662 }
663
664 // note that if we had to move the elements, the following operation
665 // is guaranteed to succeed, because we have the memory already allocated
666 // therefore, it is impossible to leave this function with an invalid array
667
668 // place the new elements
669
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 3857 times.
3857 if (cx_array_copy(
670 &arl->data,
671 3857 &list->collection.size,
672 3857 &arl->capacity,
673 0,
674 index,
675 array,
676 list->collection.elem_size,
677 n,
678 &arl->reallocator
679 )) {
680 // array list implementation is "all or nothing"
681 return 0;
682 } else {
683 3857 return n;
684 }
685 }
686
687 16 static size_t cx_arl_insert_sorted(
688 struct cx_list_s *list,
689 const void *sorted_data,
690 size_t n
691 ) {
692 // get a correctly typed pointer to the list
693 16 cx_array_list *arl = (cx_array_list *) list;
694
695
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 16 times.
16 if (cx_array_insert_sorted(
696 &arl->data,
697 &list->collection.size,
698 &arl->capacity,
699 list->collection.cmpfunc,
700 sorted_data,
701 list->collection.elem_size,
702 n,
703 &arl->reallocator
704 )) {
705 // array list implementation is "all or nothing"
706 return 0;
707 } else {
708 16 return n;
709 }
710 }
711
712 3838 static int cx_arl_insert_element(
713 struct cx_list_s *list,
714 size_t index,
715 const void *element
716 ) {
717 3838 return 1 != cx_arl_insert_array(list, index, element, 1);
718 }
719
720 10 static int cx_arl_insert_iter(
721 struct cx_iterator_s *iter,
722 const void *elem,
723 int prepend
724 ) {
725 10 struct cx_list_s *list = iter->src_handle.m;
726
2/2
✓ Branch 0 taken 6 times.
✓ Branch 1 taken 4 times.
10 if (iter->index < list->collection.size) {
727 6 int result = cx_arl_insert_element(
728 list,
729 6 iter->index + 1 - prepend,
730 elem
731 );
732
1/2
✓ Branch 0 taken 6 times.
✗ Branch 1 not taken.
6 if (result == 0) {
733 6 iter->elem_count++;
734
2/2
✓ Branch 0 taken 4 times.
✓ Branch 1 taken 2 times.
6 if (prepend != 0) {
735 4 iter->index++;
736 4 iter->elem_handle = ((char *) iter->elem_handle) + list->collection.elem_size;
737 }
738 }
739 6 return result;
740 } else {
741 4 int result = cx_arl_insert_element(list, list->collection.size, elem);
742
1/2
✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
4 if (result == 0) {
743 4 iter->elem_count++;
744 4 iter->index = list->collection.size;
745 }
746 4 return result;
747 }
748 }
749
750 84 static size_t cx_arl_remove(
751 struct cx_list_s *list,
752 size_t index,
753 size_t num,
754 void *targetbuf
755 ) {
756 84 cx_array_list *arl = (cx_array_list *) list;
757
758 // out-of-bounds check
759 size_t remove;
760
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 82 times.
84 if (index >= list->collection.size) {
761 2 remove = 0;
762
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 80 times.
82 } else if (index + num > list->collection.size) {
763 2 remove = list->collection.size - index;
764 } else {
765 80 remove = num;
766 }
767
768 // easy exit
769
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 82 times.
84 if (remove == 0) return 0;
770
771 // destroy or copy contents
772
2/2
✓ Branch 0 taken 78 times.
✓ Branch 1 taken 4 times.
82 if (targetbuf == NULL) {
773
2/2
✓ Branch 0 taken 92 times.
✓ Branch 1 taken 78 times.
170 for (size_t idx = index; idx < index + remove; idx++) {
774
8/8
✓ Branch 0 taken 24 times.
✓ Branch 1 taken 68 times.
✓ Branch 2 taken 12 times.
✓ Branch 3 taken 12 times.
✓ Branch 5 taken 8 times.
✓ Branch 6 taken 84 times.
✓ Branch 7 taken 4 times.
✓ Branch 8 taken 4 times.
92 cx_invoke_destructor(
775 list,
776 ((char *) arl->data) + idx * list->collection.elem_size
777 );
778 }
779 } else {
780 4 memcpy(
781 targetbuf,
782 4 ((char *) arl->data) + index * list->collection.elem_size,
783 4 remove * list->collection.elem_size
784 );
785 }
786
787 // short-circuit removal of last elements
788
2/2
✓ Branch 0 taken 4 times.
✓ Branch 1 taken 78 times.
82 if (index + remove == list->collection.size) {
789 4 list->collection.size -= remove;
790 4 return remove;
791 }
792
793 // just move the elements to the left
794 78 cx_array_copy(
795 &arl->data,
796 78 &list->collection.size,
797 78 &arl->capacity,
798 0,
799 index,
800 78 ((char *) arl->data) + (index + remove) * list->collection.elem_size,
801 list->collection.elem_size,
802 78 list->collection.size - index - remove,
803 &arl->reallocator
804 );
805
806 // decrease the size
807 78 list->collection.size -= remove;
808
809 78 return remove;
810 }
811
812 6 static void cx_arl_clear(struct cx_list_s *list) {
813
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 6 times.
6 if (list->collection.size == 0) return;
814
815 6 cx_array_list *arl = (cx_array_list *) list;
816 6 char *ptr = arl->data;
817
818
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 4 times.
6 if (list->collection.simple_destructor) {
819
2/2
✓ Branch 0 taken 112 times.
✓ Branch 1 taken 2 times.
114 for (size_t i = 0; i < list->collection.size; i++) {
820
2/2
✓ Branch 0 taken 56 times.
✓ Branch 1 taken 56 times.
112 cx_invoke_simple_destructor(list, ptr);
821 112 ptr += list->collection.elem_size;
822 }
823 }
824
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 4 times.
6 if (list->collection.advanced_destructor) {
825
2/2
✓ Branch 0 taken 142 times.
✓ Branch 1 taken 2 times.
144 for (size_t i = 0; i < list->collection.size; i++) {
826
2/2
✓ Branch 0 taken 71 times.
✓ Branch 1 taken 71 times.
142 cx_invoke_advanced_destructor(list, ptr);
827 142 ptr += list->collection.elem_size;
828 }
829 }
830
831 6 memset(arl->data, 0, list->collection.size * list->collection.elem_size);
832 6 list->collection.size = 0;
833 }
834
835 33 static int cx_arl_swap(
836 struct cx_list_s *list,
837 size_t i,
838 size_t j
839 ) {
840
4/4
✓ Branch 0 taken 27 times.
✓ Branch 1 taken 6 times.
✓ Branch 2 taken 3 times.
✓ Branch 3 taken 24 times.
33 if (i >= list->collection.size || j >= list->collection.size) return 1;
841 24 cx_array_list *arl = (cx_array_list *) list;
842 24 cx_array_swap(arl->data, list->collection.elem_size, i, j);
843 24 return 0;
844 }
845
846 3964 static void *cx_arl_at(
847 const struct cx_list_s *list,
848 size_t index
849 ) {
850
2/2
✓ Branch 0 taken 3956 times.
✓ Branch 1 taken 8 times.
3964 if (index < list->collection.size) {
851 3956 const cx_array_list *arl = (const cx_array_list *) list;
852 3956 char *space = arl->data;
853 3956 return space + index * list->collection.elem_size;
854 } else {
855 8 return NULL;
856 }
857 }
858
859 68 static size_t cx_arl_find_remove(
860 struct cx_list_s *list,
861 const void *elem,
862 bool remove
863 ) {
864 assert(list != NULL);
865 assert(list->collection.cmpfunc != NULL);
866
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 68 times.
68 if (list->collection.size == 0) return 0;
867 68 char *cur = ((const cx_array_list *) list)->data;
868
869 // optimize with binary search, when sorted
870
2/2
✓ Branch 0 taken 8 times.
✓ Branch 1 taken 60 times.
68 if (list->collection.sorted) {
871 8 size_t i = cx_array_binary_search(
872 cur,
873 list->collection.size,
874 list->collection.elem_size,
875 elem,
876 list->collection.cmpfunc
877 );
878
4/4
✓ Branch 0 taken 4 times.
✓ Branch 1 taken 4 times.
✓ Branch 2 taken 2 times.
✓ Branch 3 taken 2 times.
8 if (remove && i < list->collection.size) {
879 2 cx_arl_remove(list, i, 1, NULL);
880 }
881 8 return i;
882 }
883
884 // fallback: linear search
885
2/2
✓ Branch 0 taken 16048 times.
✓ Branch 1 taken 6 times.
16054 for (size_t i = 0; i < list->collection.size; i++) {
886
2/2
✓ Branch 1 taken 54 times.
✓ Branch 2 taken 15994 times.
16048 if (0 == list->collection.cmpfunc(elem, cur)) {
887
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 52 times.
54 if (remove) {
888 2 cx_arl_remove(list, i, 1, NULL);
889 }
890 54 return i;
891 }
892 15994 cur += list->collection.elem_size;
893 }
894 6 return list->collection.size;
895 }
896
897 4 static void cx_arl_sort(struct cx_list_s *list) {
898 assert(list->collection.cmpfunc != NULL);
899 4 qsort(((cx_array_list *) list)->data,
900 list->collection.size,
901 list->collection.elem_size,
902 4 list->collection.cmpfunc
903 );
904 4 }
905
906 14 static int cx_arl_compare(
907 const struct cx_list_s *list,
908 const struct cx_list_s *other
909 ) {
910 assert(list->collection.cmpfunc != NULL);
911
2/2
✓ Branch 0 taken 10 times.
✓ Branch 1 taken 4 times.
14 if (list->collection.size == other->collection.size) {
912 10 const char *left = ((const cx_array_list *) list)->data;
913 10 const char *right = ((const cx_array_list *) other)->data;
914
2/2
✓ Branch 0 taken 352 times.
✓ Branch 1 taken 6 times.
358 for (size_t i = 0; i < list->collection.size; i++) {
915 352 int d = list->collection.cmpfunc(left, right);
916
2/2
✓ Branch 0 taken 4 times.
✓ Branch 1 taken 348 times.
352 if (d != 0) {
917 4 return d;
918 }
919 348 left += list->collection.elem_size;
920 348 right += other->collection.elem_size;
921 }
922 6 return 0;
923 } else {
924
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 2 times.
4 return list->collection.size < other->collection.size ? -1 : 1;
925 }
926 }
927
928 2 static void cx_arl_reverse(struct cx_list_s *list) {
929
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 2 times.
2 if (list->collection.size < 2) return;
930 2 void *data = ((const cx_array_list *) list)->data;
931 2 size_t half = list->collection.size / 2;
932
2/2
✓ Branch 0 taken 50 times.
✓ Branch 1 taken 2 times.
52 for (size_t i = 0; i < half; i++) {
933 50 cx_array_swap(data, list->collection.elem_size, i, list->collection.size - 1 - i);
934 }
935 }
936
937 490 static bool cx_arl_iter_valid(const void *it) {
938 490 const struct cx_iterator_s *iter = it;
939 490 const struct cx_list_s *list = iter->src_handle.c;
940 490 return iter->index < list->collection.size;
941 }
942
943 4188 static void *cx_arl_iter_current(const void *it) {
944 4188 const struct cx_iterator_s *iter = it;
945 4188 return iter->elem_handle;
946 }
947
948 3910 static void cx_arl_iter_next(void *it) {
949 3910 struct cx_iterator_s *iter = it;
950
2/2
✓ Branch 0 taken 30 times.
✓ Branch 1 taken 3880 times.
3910 if (iter->base.remove) {
951 30 iter->base.remove = false;
952 30 cx_arl_remove(iter->src_handle.m, iter->index, 1, NULL);
953 } else {
954 3880 iter->index++;
955 3880 iter->elem_handle =
956 3880 ((char *) iter->elem_handle)
957 3880 + ((const struct cx_list_s *) iter->src_handle.c)->collection.elem_size;
958 }
959 3910 }
960
961 238 static void cx_arl_iter_prev(void *it) {
962 238 struct cx_iterator_s *iter = it;
963 238 const cx_array_list *list = iter->src_handle.c;
964
2/2
✓ Branch 0 taken 28 times.
✓ Branch 1 taken 210 times.
238 if (iter->base.remove) {
965 28 iter->base.remove = false;
966 28 cx_arl_remove(iter->src_handle.m, iter->index, 1, NULL);
967 }
968 238 iter->index--;
969
2/2
✓ Branch 0 taken 229 times.
✓ Branch 1 taken 9 times.
238 if (iter->index < list->base.collection.size) {
970 229 iter->elem_handle = ((char *) list->data)
971 229 + iter->index * list->base.collection.elem_size;
972 }
973 238 }
974
975
976 156 static struct cx_iterator_s cx_arl_iterator(
977 const struct cx_list_s *list,
978 size_t index,
979 bool backwards
980 ) {
981 struct cx_iterator_s iter;
982
983 156 iter.index = index;
984 156 iter.src_handle.c = list;
985 156 iter.elem_handle = cx_arl_at(list, index);
986 156 iter.elem_size = list->collection.elem_size;
987 156 iter.elem_count = list->collection.size;
988 156 iter.base.valid = cx_arl_iter_valid;
989 156 iter.base.current = cx_arl_iter_current;
990
2/2
✓ Branch 0 taken 13 times.
✓ Branch 1 taken 143 times.
156 iter.base.next = backwards ? cx_arl_iter_prev : cx_arl_iter_next;
991 156 iter.base.remove = false;
992 156 iter.base.mutating = false;
993
994 156 return iter;
995 }
996
997 static cx_list_class cx_array_list_class = {
998 cx_arl_destructor,
999 cx_arl_insert_element,
1000 cx_arl_insert_array,
1001 cx_arl_insert_sorted,
1002 cx_arl_insert_iter,
1003 cx_arl_remove,
1004 cx_arl_clear,
1005 cx_arl_swap,
1006 cx_arl_at,
1007 cx_arl_find_remove,
1008 cx_arl_sort,
1009 cx_arl_compare,
1010 cx_arl_reverse,
1011 cx_arl_iterator,
1012 };
1013
1014 80 CxList *cxArrayListCreate(
1015 const CxAllocator *allocator,
1016 cx_compare_func comparator,
1017 size_t elem_size,
1018 size_t initial_capacity
1019 ) {
1020
2/2
✓ Branch 0 taken 3 times.
✓ Branch 1 taken 77 times.
80 if (allocator == NULL) {
1021 3 allocator = cxDefaultAllocator;
1022 }
1023
1024 80 cx_array_list *list = cxCalloc(allocator, 1, sizeof(cx_array_list));
1025
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 80 times.
80 if (list == NULL) return NULL;
1026 80 cx_list_init((CxList*)list, &cx_array_list_class,
1027 allocator, comparator, elem_size);
1028 80 list->capacity = initial_capacity;
1029
1030 // allocate the array after the real elem_size is known
1031 80 list->data = cxCalloc(allocator, initial_capacity,
1032 list->base.collection.elem_size);
1033 if (list->data == NULL) { // LCOV_EXCL_START
1034 cxFree(allocator, list);
1035 return NULL;
1036 } // LCOV_EXCL_STOP
1037
1038 // configure the reallocator
1039 80 list->reallocator = cx_array_reallocator(allocator, NULL);
1040
1041 80 return (CxList *) list;
1042 }
1043