Coverage Report

Created: 2026-10-15 06:19

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