Coverage Report

Created: 2026-09-14 07:05

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/curl/lib/uint-hashset.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 "uint-hashset.h"
27
#include "curlx/strdup.h"
28
29
/* random patterns for API verification */
30
#ifdef DEBUGBUILD
31
67.8k
#define CURL_U8_STRSET_MAGIC 0x7117e783
32
#endif
33
34
#define CURL_U8_STRSET_DEBUG      0
35
36
59.1k
#define CURL_SWAP(a, b) (((a) ^= (b)), ((b) ^= (a)), ((a) ^= (b)))
37
38
static const uint8_t u8_smask[] = {
39
  0x00U,
40
  0x01U,
41
  0x03U,
42
  0x07U,
43
  0x0FU,
44
  0x1FU,
45
  0x3FU,
46
  0x7FU,
47
  0xFFU,
48
};
49
50
1.05M
#define CURL_U8_SET_SLOT_IDX(s, i)    (uint8_t)((i) & u8_smask[(s)->slotbits])
51
284k
#define CURL_U8_SLOT_CNT(i)           ((uint16_t)u8_smask[(i)] + 1)
52
276k
#define CURL_U8_SET_SLOT_CNT(s)       CURL_U8_SLOT_CNT((s)->slotbits)
53
54
/* A hashset for tuples (id, string) using Robin Hood Hashing.
55
 * <https://www.cs.cornell.edu/courses/JavaAndDS/files/hashing_RobinHood.pdf>
56
 * The basic idea here to handle collisions by robbing "rich" entries and
57
 * giving to the "poor":
58
 * - We have an array: (id, string) are ideally placed at index "id % size".
59
 * - If slot at index is already occupied, we have a collision.
60
 * - A simple collision strategy would look at the next index, and the
61
 *   next until finding an empty slot.
62
 * - The drawback is that this may lead to many checks on lookups, as it
63
 *   will need to also look at subsequent slots until it finds the match.
64
 *   The amount of lookups is the "probe sequence length" (psl) and this
65
 *   may vary greatly between entries.
66
 * - Robin Hood Hashing balances the 'psl's of all entries more evenly:
67
 *   - psl == 0 means an entry is in exactly the right slot
68
 *   - psl == 1 means it is in the slot right after. psl == 2 is the slot
69
 *     after that, etc.
70
 *   - when inserting a new entry, track its psl. Finding a slot where
71
 *     the existing entry has a lower psl makes a swap. Put the new entry
72
 *     and its psl there, take the previous entry and its psl and find
73
 *     the next best slot for the previous entry. */
74
void Curl_u8_strset_init(struct u8_strset *set)
75
67.8k
{
76
#if defined(__GNUC__) && __GNUC__ >= 13
77
#pragma GCC diagnostic push
78
#pragma GCC diagnostic ignored "-Warray-bounds"
79
#endif
80
67.8k
  memset(set, 0, sizeof(*set));
81
#if defined(__GNUC__) && __GNUC__ >= 13
82
#pragma GCC diagnostic pop
83
#endif
84
67.8k
  set->data = set->sdata;
85
67.8k
  set->ids = set->sids;
86
67.8k
  set->psl = set->spsl;
87
67.8k
  set->slotbits = CURL_U8_STRSET_START_BITS;
88
67.8k
  set->count = 0;
89
67.8k
#ifdef DEBUGBUILD
90
67.8k
  set->init = CURL_U8_STRSET_MAGIC;
91
67.8k
#endif
92
67.8k
}
93
94
void Curl_u8_strset_clear(struct u8_strset *set)
95
22.6k
{
96
22.6k
  uint16_t i;
97
22.6k
  DEBUGASSERT(set->init == CURL_U8_STRSET_MAGIC);
98
209k
  for(i = 0; i < CURL_U8_SET_SLOT_CNT(set); ++i)
99
187k
    curlx_safefree(set->data[i]);
100
101
22.6k
  if(set->data != set->sdata)
102
695
    curlx_safefree(set->data);
103
22.6k
  Curl_u8_strset_init(set);
104
22.6k
}
105
106
static void u8_strset_addn(struct u8_strset *set, uint8_t id, char *val)
107
73.3k
{
108
73.3k
  uint8_t i = CURL_U8_SET_SLOT_IDX(set, id);
109
73.3k
  uint8_t psl = 0;
110
138k
  while(set->data[i]) {
111
64.9k
    if(psl > set->psl[i]) { /* SWAP */
112
29.5k
      char *tmpdata;
113
29.5k
      tmpdata = set->data[i];
114
29.5k
      set->data[i] = val;
115
29.5k
      val = tmpdata;
116
29.5k
      CURL_SWAP(set->psl[i], psl);
117
29.5k
      CURL_SWAP(set->ids[i], id);
118
29.5k
    }
119
64.9k
    i = CURL_U8_SET_SLOT_IDX(set, i + 1);
120
64.9k
    ++psl;
121
64.9k
  }
122
73.3k
  set->ids[i] = id;
123
73.3k
  set->data[i] = val;
124
73.3k
  set->psl[i] = psl;
125
73.3k
  ++set->count;
126
73.3k
}
127
128
static bool u8_strset_grow(struct u8_strset *set)
129
739
{
130
739
  uint8_t i, *prev_ids, nslotbits;
131
739
  uint16_t prev_slots;
132
739
  char **prev_data;
133
739
  size_t nslots;
134
739
  void *d;
135
136
739
  if(set->slotbits >= 8)
137
0
    return FALSE;
138
739
  nslotbits = (uint8_t)(set->slotbits + 1);
139
#if CURL_U8_STRSET_DEBUG
140
  curl_mfprintf(stderr, "u8_strset_grow from %d to %d\n",
141
                set->slotbits, nslotbits);
142
#endif
143
739
  nslots = CURL_U8_SLOT_CNT(nslotbits);
144
739
  d = curlx_calloc(1, (nslots * sizeof(char *)) + (2 * nslots));
145
739
  if(!d)
146
0
    return FALSE;
147
148
739
  prev_data = set->data;
149
739
  prev_ids = set->ids;
150
739
  prev_slots = set->slotbits;
151
#if defined(__GNUC__) && __GNUC__ >= 13
152
#pragma GCC diagnostic push
153
#pragma GCC diagnostic ignored "-Wanalyzer-allocation-size"
154
#endif
155
739
  set->data = (char **)d;
156
#if defined(__GNUC__) && __GNUC__ >= 13
157
#pragma GCC diagnostic pop
158
#endif
159
739
  set->ids = (uint8_t *)d + (nslots * sizeof(char *));
160
739
  set->psl = set->ids + nslots;
161
739
  set->slotbits = nslotbits;
162
739
  set->count = 0;
163
  /* re-add previous entries */
164
7.06k
  for(i = 0; i < CURL_U8_SLOT_CNT(prev_slots); ++i) {
165
6.32k
    if(prev_data[i])
166
6.32k
      u8_strset_addn(set, prev_ids[i], prev_data[i]);
167
6.32k
  }
168
739
  if(prev_data != set->sdata)
169
44
    curlx_free(prev_data);
170
739
  return TRUE;
171
739
}
172
173
static bool u8_strset_get_index(struct u8_strset *set,
174
                                uint8_t id, uint8_t *pindex)
175
471k
{
176
471k
  uint8_t i = CURL_U8_SET_SLOT_IDX(set, id);
177
471k
  uint8_t psl = 0;
178
910k
  while(set->data[i] && (psl <= set->psl[i])) {
179
508k
    if(set->ids[i] == id) {
180
69.8k
      *pindex = i;
181
#if CURL_U8_STRSET_DEBUG
182
      curl_mfprintf(stderr, "u8_strset_index %d=%s\n", id, set->data[i]);
183
#endif
184
69.8k
      return TRUE;
185
69.8k
    }
186
438k
    i = CURL_U8_SET_SLOT_IDX(set, i + 1);
187
438k
    ++psl;
188
438k
  }
189
#if CURL_U8_STRSET_DEBUG
190
  curl_mfprintf(stderr, "u8_strset_index %d not found\n", id);
191
#endif
192
402k
  *pindex = 0;
193
402k
  return FALSE;
194
471k
}
195
196
#ifdef UNITTESTS
197
/* @unittest 3211 */
198
uint16_t Curl_u8_strset_count(struct u8_strset *set)
199
0
{
200
0
  return set->count;
201
0
}
202
#endif
203
204
const char *Curl_u8_strset_get(struct u8_strset *set, uint8_t id)
205
289k
{
206
289k
  uint8_t i;
207
289k
  DEBUGASSERT(set->init == CURL_U8_STRSET_MAGIC);
208
289k
  if(u8_strset_get_index(set, id, &i))
209
67.9k
    return set->data[i];
210
221k
  return NULL;
211
289k
}
212
213
CURLcode Curl_u8_strset_setn(struct u8_strset *set,
214
                             uint8_t id, char *str)
215
68.8k
{
216
68.8k
  uint8_t i;
217
218
68.8k
  DEBUGASSERT(set->init == CURL_U8_STRSET_MAGIC);
219
#if CURL_U8_STRSET_DEBUG
220
  curl_mfprintf(stderr, "u8_strset_setn %d=%s\n", id, str);
221
#endif
222
68.8k
  if(!str) {
223
724
    Curl_u8_strset_unset(set, id);
224
724
    return CURLE_OK;
225
724
  }
226
227
68.0k
  if(u8_strset_get_index(set, id, &i)) {
228
    /* `id` is in set, replace value */
229
1.03k
    curlx_free(set->data[i]);
230
1.03k
    set->data[i] = str;
231
1.03k
    return CURLE_OK;
232
1.03k
  }
233
  /* `id` not in set yet, grow if full */
234
67.0k
  if((set->count >= CURL_U8_SET_SLOT_CNT(set)) && !u8_strset_grow(set)) {
235
0
    curlx_free(str);
236
0
    return CURLE_OUT_OF_MEMORY;
237
0
  }
238
239
67.0k
  u8_strset_addn(set, id, str);
240
67.0k
  return CURLE_OK;
241
67.0k
}
242
243
CURLcode Curl_u8_strset_setx(struct u8_strset *set,
244
                             uint8_t id, const char *str, size_t slen)
245
67.3k
{
246
67.3k
  char *val;
247
248
67.3k
  DEBUGASSERT(set->init == CURL_U8_STRSET_MAGIC);
249
67.3k
  if(!str) {
250
0
    Curl_u8_strset_unset(set, id);
251
0
    return CURLE_OK;
252
0
  }
253
254
67.3k
  val = curlx_memdup0(str, slen);
255
67.3k
  if(!val)
256
0
    return CURLE_OUT_OF_MEMORY;
257
67.3k
  return Curl_u8_strset_setn(set, id, val);
258
67.3k
}
259
260
/* @unittest 3211 */
261
UNITTEST CURLcode u8_strset_set(struct u8_strset *set,
262
                                uint8_t id, const char *str);
263
UNITTEST CURLcode u8_strset_set(struct u8_strset *set,
264
                                uint8_t id, const char *str)
265
0
{
266
0
  return Curl_u8_strset_setx(set, id, str, str ? strlen(str) : 0);
267
0
}
268
269
static void u8_strset_unset(struct u8_strset *set, uint8_t id, bool zero)
270
114k
{
271
114k
  uint8_t i, j;
272
273
114k
  DEBUGASSERT(set->init == CURL_U8_STRSET_MAGIC);
274
114k
  if(u8_strset_get_index(set, id, &i)) {
275
    /* `id` is in set */
276
871
    if(zero)
277
833
      curlx_strzero(set->data[i]);
278
871
    curlx_safefree(set->data[i]);
279
871
    set->ids[i] = set->psl[i] = 0;
280
871
    --set->count;
281
871
    j = CURL_U8_SET_SLOT_IDX(set, i + 1);
282
    /* shift all entries with positive psl "down" */
283
2.14k
    while(set->data[j] && set->psl[j]) {
284
1.27k
      set->data[i] = set->data[j];
285
1.27k
      set->ids[i] = set->ids[j];
286
1.27k
      set->psl[i] = (uint8_t)(set->psl[j] - 1);
287
1.27k
      set->data[j] = NULL;
288
1.27k
      set->ids[j] = set->psl[j] = 0;
289
1.27k
      i = j;
290
1.27k
      j = CURL_U8_SET_SLOT_IDX(set, i + 1);
291
1.27k
    }
292
871
  }
293
114k
}
294
295
void Curl_u8_strset_unset(struct u8_strset *set, uint8_t id)
296
1.10k
{
297
1.10k
  u8_strset_unset(set, id, FALSE);
298
1.10k
}
299
300
void Curl_u8_strset_unset0(struct u8_strset *set, uint8_t id)
301
113k
{
302
113k
  u8_strset_unset(set, id, TRUE);
303
113k
}
304
305
CURLcode Curl_u8_strset_copy(struct u8_strset *dest, struct u8_strset *src)
306
0
{
307
0
  CURLcode result = CURLE_OK;
308
0
  uint16_t i;
309
310
0
  DEBUGASSERT(src->init == CURL_U8_STRSET_MAGIC);
311
0
  Curl_u8_strset_clear(dest);
312
0
  for(i = 0; !result && (i < CURL_U8_SET_SLOT_CNT(src)); ++i) {
313
0
    if(src->data[i])
314
0
      result = u8_strset_set(dest, src->ids[i], src->data[i]);
315
0
  }
316
0
  return result;
317
0
}