Coverage Report

Created: 2026-08-31 06:47

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