Coverage Report

Created: 2026-03-31 06:21

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/freeradius-server/src/lib/util/minmax_heap.c
Line
Count
Source
1
/*
2
 *   This program is free software; you can redistribute it and/or modify
3
 *   it under the terms of the GNU General Public License as published by
4
 *   the Free Software Foundation; either version 2 of the License, or
5
 *   (at your option) any later version.
6
 *
7
 *   This program is distributed in the hope that it will be useful,
8
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of
9
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
10
 *   GNU General Public License for more details.
11
 *
12
 *   You should have received a copy of the GNU General Public License
13
 *   along with this program; if not, write to the Free Software
14
 *   Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
15
 */
16
17
/** Functions for a minmax heap
18
 *
19
 * @file src/lib/util/minmax_heap.c
20
 *
21
 * @copyright 2021 Network RADIUS SAS (legal@networkradius.com)
22
 */
23
RCSID("$Id: f7256c103ab736d4e2dac7441a1f4503b466b0b2 $")
24
25
#include <freeradius-devel/util/minmax_heap.h>
26
#include <freeradius-devel/util/strerror.h>
27
#include <freeradius-devel/util/debug.h>
28
#include <freeradius-devel/util/misc.h>
29
30
/*
31
 *  The internal representation of minmax heaps is that of plain
32
 *  binary heaps. They differ in where entries are placed, and how
33
 *  the operations are done. Also, minmax heaps allow peeking or
34
 *  popping the maximum value as well as the minimum.
35
 *
36
 *  The heap itself is an array of pointers to objects, each of which
37
 *  contains a key and an fr_minmax_heap_index_t value indicating the
38
 *  location in the array holding the pointer to it. To allow 0 to
39
 *  represent objects not in a heap, the pointers start at element
40
 *  one of the array rather than element zero. The offset of that
41
 *  fr_minmax_heap_index_t value is held inside the heap structure.
42
 *
43
 *  Minmax heaps are trees, like binary heaps, but the levels (all
44
 *  values at the same depth) alternate between "min" (starting at
45
 *  depth 0, i.e. the root) and "max" levels. The operations preserve
46
 *  these properties:
47
 *  - A node on a min level will compare as less than or equal to any
48
 *    of its descendants.
49
 *  - A node on a max level will compare as greater than or equal to
50
 *    any of its descendants.
51
 */
52
53
struct fr_minmax_heap_s {
54
  unsigned int    size;   //!< Number of nodes allocated.
55
  size_t      offset;   //!< Offset of heap index in element structure.
56
57
  unsigned int    num_elements; //!< Number of nodes used.
58
59
  char const    *type;    //!< Talloc type of elements.
60
  fr_minmax_heap_cmp_t  cmp;    //!< Comparator function.
61
62
  void      *p[];   //!< Array of nodes.
63
};
64
65
typedef struct fr_minmax_heap_s minmax_heap_t;
66
67
0
#define INITIAL_CAPACITY  2048
68
69
/*
70
 *  First node in a heap is element 1. Children of i are 2i and
71
 *  2i+1.  These macros wrap the logic, so the code is more
72
 *  descriptive.
73
 */
74
0
#define HEAP_PARENT(_x) ((_x) >> 1)
75
0
#define HEAP_GRANDPARENT(_x)  HEAP_PARENT(HEAP_PARENT(_x))
76
0
#define HEAP_LEFT(_x) (2 * (_x))
77
0
#define HEAP_RIGHT(_x) (2 * (_x) + 1 )
78
0
#define HEAP_SWAP(_a, _b) do { void *_tmp = _a; _a = _b; _b = _tmp; } while (0)
79
80
/**
81
 * @hidecallergraph
82
 */
83
static inline uint8_t depth(fr_minmax_heap_index_t i)
84
0
{
85
0
  return fr_high_bit_pos(i) - 1;
86
0
}
87
88
static inline bool is_min_level_index(fr_minmax_heap_index_t i)
89
0
{
90
0
  return (depth(i) & 1) == 0;
91
0
}
92
93
static inline bool is_descendant(fr_minmax_heap_index_t candidate, fr_minmax_heap_index_t ancestor)
94
0
{
95
0
  fr_minmax_heap_index_t  level_min;
96
0
  uint8_t     candidate_depth = depth(candidate);
97
0
  uint8_t     ancestor_depth = depth(ancestor);
98
99
  /*
100
   *  This will never happen given the its use by fr_minmax_heap_extract(),
101
   *  but it's here for safety and to make static analysis happy.
102
   */
103
0
  if (unlikely(candidate_depth < ancestor_depth)) return false;
104
105
0
  level_min = ((fr_minmax_heap_index_t) 1) << (candidate_depth - ancestor_depth);
106
0
  return (candidate - level_min) < level_min;
107
0
}
108
109
0
#define is_max_level_index(_i)  (!(is_min_level_index(_i)))
110
111
fr_minmax_heap_t *_fr_minmax_heap_alloc(TALLOC_CTX *ctx, fr_minmax_heap_cmp_t cmp, char const *type, size_t offset, unsigned int init)
112
0
{
113
0
  fr_minmax_heap_t *hp;
114
0
  minmax_heap_t *h;
115
116
0
  if (!init) init = INITIAL_CAPACITY;
117
118
0
  hp = talloc(ctx, fr_minmax_heap_t);
119
0
  if (unlikely(!hp)) return NULL;
120
121
  /*
122
   *  For small heaps (< 40 elements) the
123
   *  increase in memory locality gives us
124
   *  a 100% performance increase
125
   *  (talloc headers are big);
126
   */
127
0
  h = (minmax_heap_t *)talloc_array(hp, uint8_t, sizeof(minmax_heap_t) + (sizeof(void *) * (init + 1)));
128
0
  if (unlikely(!h)) {
129
0
    talloc_free(hp);
130
0
    return NULL;
131
0
  }
132
0
  talloc_set_type(h, minmax_heap_t);
133
134
0
  *h = (minmax_heap_t){
135
0
    .size = init,
136
0
    .type = type,
137
0
    .cmp = cmp,
138
0
    .offset = offset
139
0
  };
140
141
  /*
142
   *  As we're using unsigned index values
143
   *      index 0 is a special value meaning
144
   *      that the data isn't currently inserted
145
   *  into the heap.
146
   */
147
0
  h->p[0] = (void *)UINTPTR_MAX;
148
149
0
  *hp = h;
150
151
0
  return hp;
152
0
}
153
154
static CC_HINT(nonnull) int minmax_heap_expand(fr_minmax_heap_t *hp)
155
0
{
156
0
  minmax_heap_t *h = *hp;
157
0
  unsigned int  n_size;
158
159
  /*
160
   *  One will almost certainly run out of RAM first,
161
   *  but the size must be representable. This form
162
   *  of the check avoids overflow.
163
   */
164
0
  if (unlikely(h->size > UINT_MAX - h->size)) {
165
0
    if (h->size == UINT_MAX) {
166
0
      fr_strerror_const("Heap is full");
167
0
      return -1;
168
0
    }
169
0
    n_size = UINT_MAX;
170
0
  } else {
171
0
    n_size = 2 * h->size;
172
0
  }
173
174
0
  h = (minmax_heap_t *)talloc_realloc(hp, h, uint8_t, sizeof(minmax_heap_t) + (sizeof(void *) * ((size_t)n_size + 1)));
175
0
  if (unlikely(!h)) {
176
0
    fr_strerror_printf("Failed expanding heap to %u elements (%zu bytes)",
177
0
           n_size, (n_size * sizeof(void *)));
178
0
    return -1;
179
0
  }
180
181
0
  talloc_set_type(h, minmax_heap_t);
182
0
  h->size = n_size;
183
0
  *hp = h;
184
0
  return 0;
185
0
}
186
187
188
static inline CC_HINT(always_inline, nonnull) fr_minmax_heap_index_t index_get(minmax_heap_t *h, void *data)
189
0
{
190
0
  return *((fr_minmax_heap_index_t const *)(((uint8_t const *)data) + h->offset));
191
0
}
192
193
static inline CC_HINT(always_inline, nonnull) void index_set(minmax_heap_t *h, void *data, fr_minmax_heap_index_t idx)
194
0
{
195
0
  *((fr_minmax_heap_index_t *)(((uint8_t *)data) + h->offset)) = idx;
196
0
}
197
198
static inline CC_HINT(always_inline, nonnull) bool has_children(minmax_heap_t *h, fr_minmax_heap_index_t idx)
199
0
{
200
0
  return HEAP_LEFT(idx) <= h->num_elements;
201
0
}
202
203
static inline bool has_grandchildren(minmax_heap_t *h, fr_minmax_heap_index_t i)
204
0
{
205
0
  return HEAP_LEFT(HEAP_LEFT(i)) <= h->num_elements;
206
0
}
207
208
0
#define OFFSET_SET(_heap, _idx) index_set(_heap, _heap->p[_idx], _idx)
209
0
#define OFFSET_RESET(_heap, _idx) index_set(_heap, _heap->p[_idx], 0)
210
211
/*
212
 *  The minmax heap has the same basic idea as binary heaps:
213
 *  1. To insert a value, put it at the bottom and push it up to where it should be.
214
 *  2. To remove a value, take it out; if it's not at the bottom, move what is at the
215
 *     bottom up to fill the hole, and push it down to where it should be.
216
 *  The difference is how you push, and the invariants to preserve.
217
 *
218
 *  Since we store the index in the item (or zero if it's not in the heap), when we
219
 *  move an item around, we have to set its index. The general principle is that we
220
 *  set it when we put the item in the place it will ultimately be when the push_down()
221
 *  or push_up() is finished.
222
 */
223
224
/** Find the index of the minimum child or grandchild of the entry at a given index.
225
 *  precondition: has_children(h, idx), i.e. there is stuff in the heap below
226
 *  idx.
227
 *
228
 *  These functions are called by push_down_{min, max}() with idx the index of
229
 *  an element moved into that position but which may or may not be where it
230
 *  should ultimately go. The minmax heap property still holds for its (positional,
231
 *  at least) descendants, though. That lets us cut down on the number of
232
 *  comparisons over brute force iteration over every child and grandchild.
233
 *
234
 *  In the case where the desired item must be a child, there are at most two,
235
 *  so we just do it inlne; no loop needed.
236
 */
237
static CC_HINT(nonnull) fr_minmax_heap_index_t min_child_or_grandchild(minmax_heap_t *h, fr_minmax_heap_index_t idx)
238
0
{
239
0
  fr_minmax_heap_index_t  lwb, upb, min;
240
241
0
  if (is_max_level_index(idx) || !has_grandchildren(h, idx)) {
242
    /* minimum must be a chld */
243
0
    min = HEAP_LEFT(idx);
244
0
    upb = HEAP_RIGHT(idx);
245
0
    if (upb <= h->num_elements && h->cmp(h->p[upb], h->p[min]) < 0) min = upb;
246
0
    return min;
247
0
  }
248
249
  /* minimum must be a grandchild, unless the right child is childless */
250
0
  if (!has_children(h, HEAP_RIGHT(idx))) {
251
0
    min = HEAP_RIGHT(idx);
252
0
    lwb = HEAP_LEFT(HEAP_LEFT(idx));
253
0
  } else {
254
0
    min = HEAP_LEFT(HEAP_LEFT(idx));
255
0
    lwb = min + 1;
256
0
  }
257
0
  upb = HEAP_RIGHT(HEAP_RIGHT(idx));
258
259
  /* Some grandchildren may not exist. */
260
0
  if (upb > h->num_elements) upb = h->num_elements;
261
262
0
  for (fr_minmax_heap_index_t i = lwb; i <= upb; i++) {
263
0
    if (h->cmp(h->p[i], h->p[min]) < 0) min = i;
264
0
  }
265
0
  return min;
266
0
}
267
268
static CC_HINT(nonnull) fr_minmax_heap_index_t max_child_or_grandchild(minmax_heap_t *h, fr_minmax_heap_index_t idx)
269
0
{
270
0
  fr_minmax_heap_index_t  lwb, upb, max;
271
272
0
  if (is_min_level_index(idx) || !has_grandchildren(h, idx)) {
273
    /* maximum must be a chld */
274
0
    max = HEAP_LEFT(idx);
275
0
    upb = HEAP_RIGHT(idx);
276
0
    if (upb <= h->num_elements && h->cmp(h->p[upb], h->p[max]) > 0) max = upb;
277
0
    return max;
278
0
  }
279
280
  /* minimum must be a grandchild, unless the right child is childless */
281
0
  if (!has_children(h, HEAP_RIGHT(idx))) {
282
0
    max = HEAP_RIGHT(idx);
283
0
    lwb = HEAP_LEFT(HEAP_LEFT(idx));
284
0
  } else {
285
0
    max = HEAP_LEFT(HEAP_LEFT(idx));
286
0
    lwb = max + 1;
287
0
  }
288
0
  upb = HEAP_RIGHT(HEAP_RIGHT(idx));
289
290
  /* Some grandchildren may not exist. */
291
0
  if (upb > h->num_elements) upb = h->num_elements;
292
293
0
  for (fr_minmax_heap_index_t i = lwb; i <= upb; i++) {
294
0
    if (h->cmp(h->p[i], h->p[max]) > 0) max = i;
295
0
  }
296
0
  return max;
297
0
}
298
299
/**
300
 * precondition: idx is the index of an existing entry on a min level
301
 */
302
static inline CC_HINT(always_inline, nonnull) void push_down_min(minmax_heap_t *h, fr_minmax_heap_index_t idx)
303
0
{
304
0
  while (has_children(h, idx)) {
305
0
    fr_minmax_heap_index_t  m =  min_child_or_grandchild(h, idx);
306
307
    /*
308
     *  If p[m] doesn't precede p[idx], we're done.
309
     */
310
0
    if (h->cmp(h->p[m], h->p[idx]) >= 0) break;
311
312
0
    HEAP_SWAP(h->p[idx], h->p[m]);
313
0
    OFFSET_SET(h, idx);
314
315
    /*
316
     *  The entry now at m may belong where the parent is.
317
     */
318
0
    if (HEAP_GRANDPARENT(m) == idx && h->cmp(h->p[m], h->p[HEAP_PARENT(m)]) > 0) {
319
0
      HEAP_SWAP(h->p[HEAP_PARENT(m)], h->p[m]);
320
0
      OFFSET_SET(h, HEAP_PARENT(m));
321
0
    }
322
0
    idx = m;
323
0
  }
324
0
  OFFSET_SET(h, idx);
325
0
}
326
327
/**
328
 * precondition: idx is the index of an existing entry on a max level
329
 * (Just like push_down_min() save for reversal of ordering, so comments there apply,
330
 * mutatis mutandis.)
331
 */
332
static CC_HINT(nonnull) void push_down_max(minmax_heap_t *h, fr_minmax_heap_index_t idx)
333
0
{
334
0
  while (has_children(h, idx)) {
335
0
    fr_minmax_heap_index_t  m = max_child_or_grandchild(h, idx);
336
337
0
    if (h->cmp(h->p[m], h->p[idx]) <= 0) break;
338
339
0
    HEAP_SWAP(h->p[idx], h->p[m]);
340
0
    OFFSET_SET(h, idx);
341
342
0
    if (HEAP_GRANDPARENT(m) == idx && h->cmp(h->p[m], h->p[HEAP_PARENT(m)]) < 0) {
343
0
      HEAP_SWAP(h->p[HEAP_PARENT(m)], h->p[m]);
344
0
      OFFSET_SET(h, HEAP_PARENT(m));
345
0
    }
346
0
    idx = m;
347
0
  }
348
0
  OFFSET_SET(h, idx);
349
0
}
350
351
static void push_down(minmax_heap_t *h, fr_minmax_heap_index_t idx)
352
0
{
353
0
  if (is_min_level_index(idx)) {
354
0
    push_down_min(h, idx);
355
0
  } else {
356
0
    push_down_max(h, idx);
357
0
  }
358
0
}
359
360
static void push_up_min(minmax_heap_t *h, fr_minmax_heap_index_t idx)
361
0
{
362
0
  fr_minmax_heap_index_t  grandparent;
363
364
0
  while ((grandparent = HEAP_GRANDPARENT(idx)) > 0 && h->cmp(h->p[idx], h->p[grandparent]) < 0) {
365
0
    HEAP_SWAP(h->p[idx], h->p[grandparent]);
366
0
    OFFSET_SET(h, idx);
367
0
    idx = grandparent;
368
0
  }
369
0
  OFFSET_SET(h, idx);
370
0
}
371
372
static void push_up_max(minmax_heap_t *h, fr_minmax_heap_index_t idx)
373
0
{
374
0
  fr_minmax_heap_index_t  grandparent;
375
376
0
  while ((grandparent = HEAP_GRANDPARENT(idx)) > 0 && h->cmp(h->p[idx], h->p[grandparent]) > 0) {
377
0
    HEAP_SWAP(h->p[idx], h->p[grandparent]);
378
0
    OFFSET_SET(h, idx);
379
0
    idx = grandparent;
380
0
  }
381
0
  OFFSET_SET(h, idx);
382
0
}
383
384
static void push_up(minmax_heap_t *h, fr_minmax_heap_index_t idx)
385
0
{
386
0
  fr_minmax_heap_index_t  parent;
387
0
  int8_t      order;
388
389
  /*
390
   *  First entry? No need to move; set its index and be done with it.
391
   */
392
0
  if (idx == 1) {
393
0
    OFFSET_SET(h, idx);
394
0
    return;
395
0
  }
396
397
  /*
398
   *  Otherwise, move to the next level up if need be.
399
   *  Once it's positioned appropriately on an even or odd layer,
400
   *  it can percolate up two at a time.
401
   */
402
0
  parent = HEAP_PARENT(idx);
403
0
  order = h->cmp(h->p[idx], h->p[parent]);
404
405
0
  if (is_min_level_index(idx)) {
406
0
    if (order > 0) {
407
0
      HEAP_SWAP(h->p[idx], h->p[parent]);
408
0
      OFFSET_SET(h, idx);
409
0
      push_up_max(h, parent);
410
0
    } else {
411
0
      push_up_min(h, idx);
412
0
    }
413
0
  } else {
414
0
    if (order < 0) {
415
0
      HEAP_SWAP(h->p[idx], h->p[parent]);
416
0
      OFFSET_SET(h, idx);
417
0
      push_up_min(h, parent);
418
0
    } else {
419
0
      push_up_max(h, idx);
420
0
    }
421
0
  }
422
0
}
423
424
int fr_minmax_heap_insert(fr_minmax_heap_t *hp, void *data)
425
0
{
426
0
  minmax_heap_t   *h = *hp;
427
0
  fr_minmax_heap_index_t  child = index_get(h, data);
428
429
0
  if (unlikely(fr_minmax_heap_entry_inserted(child))) {
430
0
    fr_strerror_const("Node is already in a heap");
431
0
    return -1;
432
0
  }
433
434
0
  child = h->num_elements + 1;
435
0
  if (unlikely(child > h->size)) {
436
0
    if (unlikely(minmax_heap_expand(hp) < 0)) return -1;
437
0
    h = *hp;
438
0
  }
439
440
  /*
441
   *  Add it to the end, and move it up as needed.
442
   */
443
0
  h->p[child] = data;
444
0
  h->num_elements++;
445
0
  push_up(h, child);
446
0
  return 0;
447
0
}
448
449
void *fr_minmax_heap_min_peek(fr_minmax_heap_t *hp)
450
0
{
451
0
  minmax_heap_t *h = *hp;
452
453
0
  if (unlikely(h->num_elements == 0)) return NULL;
454
0
  return h->p[1];
455
0
}
456
457
void *fr_minmax_heap_min_pop(fr_minmax_heap_t *hp)
458
0
{
459
0
  void  *data = fr_minmax_heap_min_peek(hp);
460
461
0
  if (unlikely(!data)) return NULL;
462
0
  if (unlikely(fr_minmax_heap_extract(hp, data) < 0)) return NULL;
463
0
  return data;
464
0
}
465
466
void *fr_minmax_heap_max_peek(fr_minmax_heap_t *hp)
467
0
{
468
0
  minmax_heap_t   *h = *hp;
469
470
0
  if (unlikely(h->num_elements == 0)) return NULL;
471
472
0
  if (h->num_elements < 3) return h->p[h->num_elements];
473
474
0
  return h->p[2 + (h->cmp(h->p[2], h->p[3]) < 0)];
475
0
}
476
477
void *fr_minmax_heap_max_pop(fr_minmax_heap_t *hp)
478
0
{
479
0
  void  *data = fr_minmax_heap_max_peek(hp);
480
481
0
  if (unlikely(!data)) return NULL;
482
0
  if (unlikely(fr_minmax_heap_extract(hp, data) < 0)) return NULL;
483
0
  return data;
484
0
}
485
486
int fr_minmax_heap_extract(fr_minmax_heap_t *hp, void *data)
487
0
{
488
0
  minmax_heap_t   *h = *hp;
489
0
  fr_minmax_heap_index_t  idx = index_get(h, data);
490
491
0
  if (unlikely(h->num_elements < idx)) {
492
0
    fr_strerror_printf("data (index %u) exceeds heap size %u", idx, h->num_elements);
493
0
    return -1;
494
0
  }
495
0
  if (unlikely(!fr_minmax_heap_entry_inserted(index_get(h, data)) || h->p[idx] != data)) {
496
0
    fr_strerror_printf("data (index %u) not in heap", idx);
497
0
    return -1;
498
0
  }
499
500
0
  OFFSET_RESET(h, idx);
501
502
  /*
503
   *  Removing the last element can't break the minmax heap property, so
504
   *  decrement the number of elements and be done with it.
505
   */
506
0
  if (h->num_elements == idx) {
507
0
    h->num_elements--;
508
0
    return 0;
509
0
  }
510
511
  /*
512
   *  Move the last element into the now-available position,
513
   *  and then move it as needed.
514
   */
515
0
  h->p[idx] = h->p[h->num_elements];
516
0
  h->num_elements--;
517
  /*
518
   * If the new position is the root, that's as far up as it gets.
519
   * If the old position is a descendant of the new position,
520
   * the entry itself remains a descendant of the new position's
521
   * parent, and hence by minmax heap property is in the proper
522
   * relation to the parent and doesn't need to move up.
523
   */
524
0
  if (idx > 1 && !is_descendant(h->num_elements, idx)) push_up(h, idx);
525
0
  push_down(h, idx);
526
0
  return 0;
527
0
}
528
529
/** Return the number of elements in the minmax heap
530
 *
531
 * @param[in] hp  to return the number of elements from.
532
 */
533
unsigned int fr_minmax_heap_num_elements(fr_minmax_heap_t *hp)
534
0
{
535
0
  minmax_heap_t *h = *hp;
536
537
0
  return h->num_elements;
538
0
}
539
540
/** Iterate over entries in a minmax heap
541
 *
542
 * @note If the heap is modified the iterator should be considered invalidated.
543
 *
544
 * @param[in] hp  to iterate over.
545
 * @param[in] iter  Pointer to an iterator struct, used to maintain
546
 *      state between calls.
547
 * @return
548
 *  - User data.
549
 *  - NULL if at the end of the list.
550
 */
551
void *fr_minmax_heap_iter_init(fr_minmax_heap_t *hp, fr_minmax_heap_iter_t *iter)
552
0
{
553
0
  minmax_heap_t *h = *hp;
554
555
0
  *iter = 1;
556
557
0
  if (h->num_elements == 0) return NULL;
558
559
0
  return h->p[1];
560
0
}
561
562
/** Get the next entry in a minmax heap
563
 *
564
 * @note If the heap is modified the iterator should be considered invalidated.
565
 *
566
 * @param[in] hp  to iterate over.
567
 * @param[in] iter  Pointer to an iterator struct, used to maintain
568
 *      state between calls.
569
 * @return
570
 *  - User data.
571
 *  - NULL if at the end of the list.
572
 */
573
void *fr_minmax_heap_iter_next(fr_minmax_heap_t *hp, fr_minmax_heap_iter_t *iter)
574
0
{
575
0
  minmax_heap_t *h = *hp;
576
577
0
  if ((*iter + 1) > h->num_elements) return NULL;
578
0
  *iter += 1;
579
580
0
  return h->p[*iter];
581
0
}
582
583
#ifndef TALLOC_GET_TYPE_ABORT_NOOP
584
void fr_minmax_heap_verify(char const *file, int line, fr_minmax_heap_t const *hp)
585
0
{
586
0
  minmax_heap_t *h;
587
588
  /*
589
   *  The usual start...
590
   */
591
0
  fr_fatal_assert_msg(hp, "CONSISTENCY CHECK FAILED %s[%i]: fr_minmax_heap_t pointer was NULL", file, line);
592
0
  (void) talloc_get_type_abort(hp, fr_minmax_heap_t);
593
594
  /*
595
   *  Allocating the heap structure and the array holding the heap as described in data structure
596
   *  texts together is a respectable savings, but it means adding a level of indirection so the
597
   *  fr_heap_t * isn't realloc()ed out from under the user, hence the following (and the use of h
598
   *  rather than hp to access anything in the heap structure).
599
   */
600
0
  h = *hp;
601
0
  fr_fatal_assert_msg(h, "CONSISTENCY CHECK FAILED %s[%i]: minmax_heap_t pointer was NULL", file, line);
602
0
  (void) talloc_get_type_abort(h, minmax_heap_t);
603
604
0
  fr_fatal_assert_msg(h->num_elements <= h->size,
605
0
          "CONSISTENCY CHECK FAILED %s[%i]: num_elements exceeds size", file, line);
606
607
0
  fr_fatal_assert_msg(h->p[0] == (void *)UINTPTR_MAX,
608
0
          "CONSISTENCY CHECK FAILED %s[%i]: zeroeth element special value overwritten", file, line);
609
610
0
  for (fr_minmax_heap_index_t i = 1; i <= h->num_elements; i++) {
611
0
    void  *data = h->p[i];
612
613
0
    fr_fatal_assert_msg(data, "CONSISTENCY CHECK FAILED %s[%i]: node %u was NULL", file, line, i);
614
0
    if (h->type) (void)_talloc_get_type_abort(data, h->type, __location__);
615
0
    fr_fatal_assert_msg(index_get(h, data) == i,
616
0
            "CONSISTENCY CHECK FAILED %s[%i]: node %u index != %u", file, line, i, i);
617
0
  }
618
619
  /*
620
   *  Verify minmax heap property, which is:
621
   *  A node in a min level precedes all its descendants;
622
   *  a node in a max level follows all its descencdants.
623
   *  (if equal keys are allowed, that should be "doesn't follow" and
624
   *  "doesn't precede" respectively)
625
   *
626
   *  We claim looking at one's children and grandchildren (if any)
627
   *  suffices. Why? Induction on floor(depth / 2):
628
   *
629
   *  Base case:
630
   *     If the depth of the tree is <= 2, that *is* all the
631
   *     descendants, so we're done.
632
   *  Induction step:
633
   *     Suppose you're on a min level and the check passes.
634
   *     If the test works on the next min level down, transitivity
635
   *     of <= means the level you're on satisfies the property
636
   *     two levels further down.
637
   *     For max level, >= is transitive, too, so you're good.
638
   */
639
640
0
  for (fr_minmax_heap_index_t i = 1; HEAP_LEFT(i) <= h->num_elements; i++) {
641
0
    bool      on_min_level = is_min_level_index(i);
642
0
    fr_minmax_heap_index_t  others[] = {
643
0
      HEAP_LEFT(i),
644
0
      HEAP_RIGHT(i),
645
0
      HEAP_LEFT(HEAP_LEFT(i)),
646
0
      HEAP_RIGHT(HEAP_LEFT(i)),
647
0
      HEAP_LEFT(HEAP_RIGHT(i)),
648
0
      HEAP_RIGHT(HEAP_RIGHT(i))
649
0
    };
650
651
0
    for (size_t j = 0; j < NUM_ELEMENTS(others) && others[j] <= h->num_elements; j++) {
652
0
      int8_t  cmp_result = h->cmp(h->p[i], h->p[others[j]]);
653
654
0
      fr_fatal_assert_msg(on_min_level ? (cmp_result <= 0) : (cmp_result >= 0),
655
0
          "CONSISTENCY CHECK FAILED %s[%i]: node %u violates %s level condition",
656
0
          file, line, i, on_min_level ? "min" : "max");
657
0
    }
658
0
  }
659
0
}
660
#endif