Coverage Report

Created: 2026-09-14 07:05

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/curl/lib/hash.c
Line
Count
Source
1
/***************************************************************************
2
 *                                  _   _ ____  _
3
 *  Project                     ___| | | |  _ \| |
4
 *                             / __| | | | |_) | |
5
 *                            | (__| |_| |  _ <| |___
6
 *                             \___|\___/|_| \_\_____|
7
 *
8
 * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
9
 *
10
 * This software is licensed as described in the file COPYING, which
11
 * you should have received as part of this distribution. The terms
12
 * are also available at https://curl.se/docs/copyright.html.
13
 *
14
 * You may opt to use, copy, modify, merge, publish, distribute and/or sell
15
 * copies of the Software, and permit persons to whom the Software is
16
 * furnished to do so, under the terms of the COPYING file.
17
 *
18
 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
19
 * KIND, either express or implied.
20
 *
21
 * SPDX-License-Identifier: curl
22
 *
23
 ***************************************************************************/
24
#include "curl_setup.h"
25
26
#include <stddef.h> /* for offsetof() */
27
28
#include "hash.h"
29
30
/* random patterns for API verification */
31
#ifdef DEBUGBUILD
32
250k
#define HASHINIT 0x7017e781
33
51.3k
#define ITERINIT 0x5FEDCBA9
34
#endif
35
36
37
typedef size_t (*hash_function)(const void *key,
38
                                size_t key_length,
39
                                size_t slots_num);
40
41
typedef size_t (*comp_function)(const void *key1,
42
                                size_t key1_len,
43
                                const void *key2,
44
                                size_t key2_len);
45
46
/* Curl_hash stores its type in a uint8_t. */
47
typedef char hash_types_fit_uint8[
48
  ((CURL_HASH_TYPE_LAST - 1) <= UINT8_MAX) ? 1 : -1];
49
50
struct hash_functions {
51
  hash_function hash;
52
  comp_function compare;
53
};
54
55
/* @unittest 1603
56
 */
57
UNITTEST size_t hash_str(const void *key, size_t key_length, size_t slots_num);
58
UNITTEST size_t hash_str(const void *key, size_t key_length, size_t slots_num)
59
578k
{
60
578k
  const char *key_str = (const char *)key;
61
578k
  const char *end = key_str + key_length;
62
578k
  size_t h = 5381;
63
64
8.30M
  while(key_str < end) {
65
7.72M
    size_t j = (size_t)*key_str++;
66
7.72M
    h += h << 5;
67
7.72M
    h ^= j;
68
7.72M
  }
69
70
578k
  return (h % slots_num);
71
578k
}
72
73
/* Avoid treating the variable-length key[1] as a one-byte object. */
74
static char *hash_elem_key(struct Curl_hash_element *he)
75
992k
{
76
992k
  return (char *)he + offsetof(struct Curl_hash_element, key);
77
992k
}
78
79
static size_t hash_socket(const void *key, size_t key_length,
80
                          size_t slots_num)
81
435k
{
82
435k
  curl_socket_t socket;
83
84
435k
  DEBUGASSERT(key_length == sizeof(socket));
85
435k
  if(key_length != sizeof(socket))
86
0
    return 0;
87
435k
  memcpy(&socket, key, sizeof(socket));
88
435k
  return (size_t)(socket % (curl_socket_t)slots_num);
89
435k
}
90
91
static size_t compare_socket(const void *key1, size_t key1_len,
92
                             const void *key2, size_t key2_len)
93
422k
{
94
422k
  curl_socket_t socket1;
95
422k
  curl_socket_t socket2;
96
97
422k
  if((key1_len != sizeof(socket1)) || (key2_len != sizeof(socket2)))
98
0
    return 0;
99
422k
  memcpy(&socket1, key1, sizeof(socket1));
100
422k
  memcpy(&socket2, key2, sizeof(socket2));
101
422k
  return socket1 == socket2;
102
422k
}
103
104
static size_t compare_bytes(const void *key1, size_t key1_len,
105
                            const void *key2, size_t key2_len)
106
508k
{
107
508k
  return (key1_len == key2_len) && !memcmp(key1, key2, key1_len);
108
508k
}
109
110
static const struct hash_functions hash_functions[] = {
111
  { hash_str, compare_bytes },
112
  { hash_socket, compare_socket }
113
};
114
115
static const struct hash_functions *hash_get_functions(uint8_t type)
116
1.01M
{
117
1.01M
  DEBUGASSERT(CURL_ARRAYSIZE(hash_functions) == CURL_HASH_TYPE_LAST);
118
1.01M
  if((unsigned int)type >= CURL_HASH_TYPE_LAST)
119
0
    type = CURL_HASH_TYPE_BYTES;
120
1.01M
  return &hash_functions[type];
121
1.01M
}
122
123
static bool hash_key_is_valid(uint8_t type, size_t key_len)
124
1.23M
{
125
1.23M
  return (type == CURL_HASH_TYPE_BYTES) ||
126
468k
    ((type == CURL_HASH_TYPE_SOCKET) &&
127
468k
     (key_len == sizeof(curl_socket_t)));
128
1.23M
}
129
130
#if 0 /* useful function for debugging hashes and their contents */
131
void Curl_hash_print(struct Curl_hash *h, void (*func)(void *))
132
{
133
  struct Curl_hash_iterator iter;
134
  struct Curl_hash_element *he;
135
  size_t last_index = UINT_MAX;
136
137
  if(!h)
138
    return;
139
140
  curl_mfprintf(stderr, "=Hash dump=\n");
141
142
  Curl_hash_start_iterate(h, &iter);
143
144
  he = Curl_hash_next_element(&iter);
145
  while(he) {
146
    if(iter.slot_index != last_index) {
147
      curl_mfprintf(stderr, "index %d:", (int)iter.slot_index);
148
      if(last_index != UINT_MAX) {
149
        curl_mfprintf(stderr, "\n");
150
      }
151
      last_index = iter.slot_index;
152
    }
153
154
    if(func)
155
      func(he->ptr);
156
    else
157
      curl_mfprintf(stderr, " [key=%.*s, he=%p, ptr=%p]",
158
                    (int)he->key_len, hash_elem_key(he),
159
                    (void *)he, (void *)he->ptr);
160
161
    he = Curl_hash_next_element(&iter);
162
  }
163
  curl_mfprintf(stderr, "\n");
164
}
165
#endif
166
167
/* Initializes a hash structure.
168
 *
169
 * @unittest: 1602
170
 * @unittest: 1603
171
 */
172
void Curl_hash_init(struct Curl_hash *h,
173
                    size_t slots,
174
                    Curl_hash_type type,
175
                    Curl_hash_dtor dtor)
176
250k
{
177
250k
  DEBUGASSERT(h);
178
250k
  DEBUGASSERT(slots);
179
250k
  DEBUGASSERT((unsigned int)type < CURL_HASH_TYPE_LAST);
180
250k
  DEBUGASSERT(dtor);
181
182
250k
  h->table = NULL;
183
250k
  h->dtor = dtor;
184
250k
  h->size = 0;
185
250k
  h->slots = slots;
186
250k
  h->type = (uint8_t)type;
187
250k
#ifdef DEBUGBUILD
188
250k
  h->init = HASHINIT;
189
250k
#endif
190
250k
}
191
192
static struct Curl_hash_element *hash_elem_create(const void *key,
193
                                                  size_t key_len,
194
                                                  void *p,
195
                                                  Curl_hash_elem_dtor dtor)
196
56.8k
{
197
56.8k
  struct Curl_hash_element *he;
198
199
  /* allocate the struct plus memory after it to store the key */
200
56.8k
  he = curlx_malloc(sizeof(struct Curl_hash_element) + key_len);
201
56.8k
  if(he) {
202
56.8k
    he->next = NULL;
203
    /* copy the key */
204
56.8k
    memcpy(hash_elem_key(he), key, key_len);
205
56.8k
    he->key_len = key_len;
206
56.8k
    he->ptr = p;
207
56.8k
    he->dtor = dtor;
208
56.8k
  }
209
56.8k
  return he;
210
56.8k
}
211
212
static void hash_elem_clear_ptr(struct Curl_hash *h,
213
                                struct Curl_hash_element *he)
214
56.8k
{
215
56.8k
  DEBUGASSERT(h);
216
56.8k
  DEBUGASSERT(he);
217
56.8k
  if(he->ptr) {
218
56.8k
    if(he->dtor)
219
5.63k
      he->dtor(hash_elem_key(he), he->key_len, he->ptr);
220
51.2k
    else
221
51.2k
      h->dtor(he->ptr);
222
56.8k
    he->ptr = NULL;
223
56.8k
  }
224
56.8k
}
225
226
static void hash_elem_destroy(struct Curl_hash *h,
227
                              struct Curl_hash_element *he)
228
56.8k
{
229
56.8k
  hash_elem_clear_ptr(h, he);
230
56.8k
  curlx_free(he);
231
56.8k
}
232
233
static void hash_elem_unlink(struct Curl_hash *h,
234
                             struct Curl_hash_element **he_anchor,
235
                             struct Curl_hash_element *he)
236
56.8k
{
237
56.8k
  *he_anchor = he->next;
238
56.8k
  --h->size;
239
56.8k
}
240
241
static void hash_elem_link(struct Curl_hash *h,
242
                           struct Curl_hash_element **he_anchor,
243
                           struct Curl_hash_element *he)
244
56.8k
{
245
56.8k
  he->next = *he_anchor;
246
56.8k
  *he_anchor = he;
247
56.8k
  ++h->size;
248
56.8k
}
249
250
void *Curl_hash_add2(struct Curl_hash *h,
251
                     const void *key, size_t key_len, void *p,
252
                     Curl_hash_elem_dtor dtor)
253
56.8k
{
254
56.8k
  const struct hash_functions *functions;
255
56.8k
  struct Curl_hash_element *he, **slot;
256
257
56.8k
  DEBUGASSERT(h);
258
56.8k
  DEBUGASSERT(h->slots);
259
56.8k
  DEBUGASSERT(h->init == HASHINIT);
260
56.8k
  if(!hash_key_is_valid(h->type, key_len))
261
0
    return NULL;
262
56.8k
  if(!h->table) {
263
45.8k
    h->table = curlx_calloc(h->slots, sizeof(struct Curl_hash_element *));
264
45.8k
    if(!h->table)
265
0
      return NULL; /* OOM */
266
45.8k
  }
267
268
56.8k
  functions = hash_get_functions(h->type);
269
56.8k
  slot = &h->table[functions->hash(key, key_len, h->slots)];
270
56.8k
  for(he = *slot; he; he = he->next) {
271
0
    if(functions->compare(hash_elem_key(he), he->key_len, key, key_len)) {
272
      /* existing key entry, overwrite by clearing old pointer */
273
0
      hash_elem_clear_ptr(h, he);
274
0
      he->ptr = p;
275
0
      he->dtor = dtor;
276
0
      return p;
277
0
    }
278
0
  }
279
280
56.8k
  he = hash_elem_create(key, key_len, p, dtor);
281
56.8k
  if(!he)
282
0
    return NULL; /* OOM */
283
284
56.8k
  hash_elem_link(h, slot, he);
285
56.8k
  return p; /* return the new entry */
286
56.8k
}
287
288
/* Insert the data in the hash. If there already was a match in the hash, that
289
 * data is replaced. This function also "lazily" allocates the table if
290
 * needed, as it is not done in the _init function (anymore).
291
 *
292
 * @unittest: 1305
293
 * @unittest: 1602
294
 * @unittest: 1603
295
 */
296
void *Curl_hash_add(struct Curl_hash *h,
297
                    const void *key, size_t key_len, void *p)
298
51.2k
{
299
51.2k
  return Curl_hash_add2(h, key, key_len, p, NULL);
300
51.2k
}
301
302
/* Remove the identified hash entry.
303
 * Returns non-zero on failure.
304
 *
305
 * @unittest: 1603
306
 */
307
int Curl_hash_delete(struct Curl_hash *h, const void *key, size_t key_len)
308
152k
{
309
152k
  const struct hash_functions *functions;
310
311
152k
  DEBUGASSERT(h);
312
152k
  DEBUGASSERT(h->slots);
313
152k
  DEBUGASSERT(h->init == HASHINIT);
314
152k
  if(!hash_key_is_valid(h->type, key_len))
315
0
    return 1;
316
152k
  if(h->table) {
317
36.2k
    struct Curl_hash_element *he, **he_anchor;
318
319
36.2k
    functions = hash_get_functions(h->type);
320
36.2k
    he_anchor = &h->table[functions->hash(key, key_len, h->slots)];
321
36.2k
    while(*he_anchor) {
322
35.7k
      he = *he_anchor;
323
35.7k
      if(functions->compare(hash_elem_key(he), he->key_len, key, key_len)) {
324
35.7k
        hash_elem_unlink(h, he_anchor, he);
325
35.7k
        hash_elem_destroy(h, he);
326
35.7k
        return 0;
327
35.7k
      }
328
0
      he_anchor = &he->next;
329
0
    }
330
36.2k
  }
331
116k
  return 1;
332
152k
}
333
334
/* Retrieves a hash element.
335
 *
336
 * @unittest: 1603
337
 */
338
void *Curl_hash_pick(struct Curl_hash *h, const void *key, size_t key_len)
339
1.02M
{
340
1.02M
  const struct hash_functions *functions;
341
342
1.02M
  DEBUGASSERT(h);
343
1.02M
  DEBUGASSERT(h->init == HASHINIT);
344
1.02M
  if(!hash_key_is_valid(h->type, key_len))
345
0
    return NULL;
346
1.02M
  if(h->table) {
347
921k
    struct Curl_hash_element *he;
348
921k
    DEBUGASSERT(h->slots);
349
921k
    functions = hash_get_functions(h->type);
350
921k
    he = h->table[functions->hash(key, key_len, h->slots)];
351
921k
    while(he) {
352
894k
      if(functions->compare(hash_elem_key(he), he->key_len, key, key_len)) {
353
894k
        return he->ptr;
354
894k
      }
355
0
      he = he->next;
356
0
    }
357
921k
  }
358
135k
  return NULL;
359
1.02M
}
360
361
/* Destroys all the entries in the given hash and resets its attributes,
362
 * prepping the given hash for [static|dynamic] deallocation.
363
 *
364
 * @unittest: 1305
365
 * @unittest: 1602
366
 * @unittest: 1603
367
 */
368
void Curl_hash_destroy(struct Curl_hash *h)
369
250k
{
370
250k
  DEBUGASSERT(h->init == HASHINIT);
371
250k
  if(h->table) {
372
45.8k
    Curl_hash_clean(h);
373
45.8k
    curlx_safefree(h->table);
374
45.8k
  }
375
250k
  DEBUGASSERT(h->size == 0);
376
250k
  h->slots = 0;
377
250k
}
378
379
/* Removes all the entries in the given hash.
380
 *
381
 * @unittest: 1602
382
 */
383
void Curl_hash_clean(struct Curl_hash *h)
384
45.8k
{
385
45.8k
  if(h && h->table) {
386
45.8k
    struct Curl_hash_element *he, **he_anchor;
387
45.8k
    size_t i;
388
45.8k
    DEBUGASSERT(h->init == HASHINIT);
389
4.66M
    for(i = 0; i < h->slots; ++i) {
390
4.61M
      he_anchor = &h->table[i];
391
4.64M
      while(*he_anchor) {
392
21.1k
        he = *he_anchor;
393
21.1k
        hash_elem_unlink(h, he_anchor, he);
394
21.1k
        hash_elem_destroy(h, he);
395
21.1k
      }
396
4.61M
    }
397
45.8k
  }
398
45.8k
}
399
400
size_t Curl_hash_count(struct Curl_hash *h)
401
60.6k
{
402
60.6k
  DEBUGASSERT(h->init == HASHINIT);
403
60.6k
  return h->size;
404
60.6k
}
405
406
/* Cleans all entries that pass the comp function criteria. */
407
void Curl_hash_clean_with_criterium(struct Curl_hash *h, void *user,
408
                                    int (*comp)(void *, void *))
409
60.6k
{
410
60.6k
  size_t i;
411
412
60.6k
  if(!h || !h->table)
413
0
    return;
414
415
60.6k
  DEBUGASSERT(h->init == HASHINIT);
416
4.36M
  for(i = 0; i < h->slots; ++i) {
417
4.30M
    struct Curl_hash_element *he, **he_anchor = &h->table[i];
418
4.36M
    while(*he_anchor) {
419
      /* ask the callback function if we shall remove this entry or not */
420
60.6k
      if(!comp || comp(user, (*he_anchor)->ptr)) {
421
0
        he = *he_anchor;
422
0
        hash_elem_unlink(h, he_anchor, he);
423
0
        hash_elem_destroy(h, he);
424
0
      }
425
60.6k
      else
426
60.6k
        he_anchor = &(*he_anchor)->next;
427
60.6k
    }
428
4.30M
  }
429
60.6k
}
430
431
void Curl_hash_start_iterate(struct Curl_hash *hash,
432
                             struct Curl_hash_iterator *iter)
433
51.3k
{
434
51.3k
  DEBUGASSERT(hash->init == HASHINIT);
435
51.3k
  iter->hash = hash;
436
51.3k
  iter->slot_index = 0;
437
51.3k
  iter->current = NULL;
438
51.3k
#ifdef DEBUGBUILD
439
51.3k
  iter->init = ITERINIT;
440
51.3k
#endif
441
51.3k
}
442
443
struct Curl_hash_element *Curl_hash_next_element(
444
  struct Curl_hash_iterator *iter)
445
58.5k
{
446
58.5k
  struct Curl_hash *h;
447
58.5k
  DEBUGASSERT(iter->init == ITERINIT);
448
58.5k
  h = iter->hash;
449
58.5k
  if(!h->table)
450
19.8k
    return NULL; /* empty hash, nothing to return */
451
452
  /* Get the next element in the current list, if any */
453
38.7k
  if(iter->current)
454
7.16k
    iter->current = iter->current->next;
455
456
  /* If we have reached the end of the list, find the next one */
457
38.7k
  if(!iter->current) {
458
38.7k
    size_t i;
459
2.97M
    for(i = iter->slot_index; i < h->slots; i++) {
460
2.95M
      if(h->table[i]) {
461
11.6k
        iter->current = h->table[i];
462
11.6k
        iter->slot_index = i + 1;
463
11.6k
        break;
464
11.6k
      }
465
2.95M
    }
466
38.7k
  }
467
468
38.7k
  return iter->current;
469
58.5k
}