lib: new easy option string storage

Change the storage of easy handle option strings from an array sized for
all possible options to a hash set to reduce memory footprint.

Give the hash set initially room for 4 strings, with first allocation
happening when it goes beyond that. Measurements without test suite
and a forced fail on growing the set gives:

Size Result
2    1261 tests out of 1951 reported OK: 64%
4    1792 tests out of 1951 reported OK: 91%
8    1944 tests out of 1951 reported OK: 99%
16   1949 tests out of 1951 reported OK: 99%
32   single fail of 3211, unit test for u8_strset

Add u8_strset that keeps the tuples (uint8_t id, char *str)
and allows set/unset by `id`. Add that as data->set.strings.

Define MACROS
* CURL_EASY_STR(data, id) for access
* CURL_EASY_STR_SET(data, id, s) for setting, making a copy
* CURL_EASY_STR_SETN(data, id, s) for setting, no copy
* CURL_EASY_STR_CLEAR(data, id) for unsetting
* CURL_EASY_STR_CLEAR0(data, id) for unsetting and zero-ing value

Add `data->set.str_copypostfields` to handle former `STRING_COPYPOSTFIELDS`
string that was not always a string and could carry NUL bytes.

Add unit tests to test3211.

Closes #22628
This commit is contained in:
Stefan Eissing 2026-08-20 16:06:17 +02:00 committed by Daniel Stenberg
parent 961c95fea6
commit c8df3defd9
No known key found for this signature in database
GPG key ID: 5CC908FDB71E12C2
45 changed files with 1028 additions and 488 deletions

View file

@ -127,7 +127,7 @@ static CURLcode test_unit1620(const char *arg)
Curl_freeset(empty);
for(i = (enum dupstring)0; i < STRING_LAST; i++) {
fail_unless(!empty->set.str[i], "Curl_free() did not set to NULL");
fail_unless(!CURL_EASY_STR(empty, i), "Curl_free() did not set to NULL");
}
result = Curl_close(&dupe);

View file

@ -24,10 +24,11 @@
#include "unitcheck.h"
#include "urldata.h"
#include "uint-bset.h"
#include "uint-hashset.h"
#include "curl_trc.h"
static void check_set(const char *name, uint32_t capacity,
const uint32_t *s, size_t slen)
static void t3211_check_bset(const char *name, uint32_t capacity,
const uint32_t *s, size_t slen)
{
struct uint32_bset bset;
size_t i, j;
@ -122,6 +123,103 @@ static void check_set(const char *name, uint32_t capacity,
Curl_uint32_bset_destroy(&bset);
}
static bool t3211_strcmp(const char *s1, const char *s2)
{
if(s1 && s2)
return strcmp(s1, s2);
return s1 == s2;
}
static void t3211_check_strset1(void)
{
struct u8_strset set;
char buf[128];
CURLcode result;
uint8_t i, idx;
int j;
Curl_u8_strset_init(&set);
fail_unless(!Curl_u8_strset_count(&set), "initial strset not empty");
result = Curl_u8_strset_set(&set, 0, "123");
fail_unless(!result, "add1 failed");
fail_unless(Curl_u8_strset_get(&set, 0), "get failed");
fail_unless(!t3211_strcmp("123", Curl_u8_strset_get(&set, 0)), "wrong get1");
result = Curl_u8_strset_set(&set, 0, "456");
fail_unless(!result, "add2 failed");
fail_unless(!t3211_strcmp("456", Curl_u8_strset_get(&set, 0)), "wrong get2");
Curl_u8_strset_unset(&set, 0);
fail_unless(!Curl_u8_strset_get(&set, 0), "unset failed");
/* Initial size is 4, add 4 hash collisions */
for(i = 0; i < 4; ++i) {
idx = (uint8_t)((4 * i) + 3);
curl_msnprintf(buf, sizeof(buf), "str-%d", idx);
result = Curl_u8_strset_set(&set, idx, buf);
fail_unless(!result, "loop4-add failed");
fail_unless(!t3211_strcmp(buf, Curl_u8_strset_get(&set, idx)),
"wrong get loop4");
}
/* Remove collided entry 2, check again */
idx = (uint8_t)((4 * 2) + 3);
Curl_u8_strset_unset(&set, idx);
fail_unless(!Curl_u8_strset_get(&set, idx), "unset2 failed");
for(i = 0; i < 4; ++i) {
if(i == 2)
continue;
idx = (uint8_t)((4 * i) + 3);
curl_msnprintf(buf, sizeof(buf), "str-%d", idx);
fail_unless(!t3211_strcmp(buf, Curl_u8_strset_get(&set, idx)),
"wrong get loop6");
}
/* Add entry 2 again, check */
idx = (uint8_t)((4 * 2) + 3);
curl_msnprintf(buf, sizeof(buf), "str-%d", idx);
result = Curl_u8_strset_set(&set, idx, buf);
fail_unless(!result, "re-add 2 failed");
fail_unless(!t3211_strcmp(buf, Curl_u8_strset_get(&set, idx)),
"wrong re-add 2 get");
for(i = 0; i < 4; ++i) {
idx = (uint8_t)((4 * i) + 3);
curl_msnprintf(buf, sizeof(buf), "str-%d", idx);
fail_unless(!t3211_strcmp(buf, Curl_u8_strset_get(&set, idx)),
"wrong get loop6");
}
/* Add a 5th, set grows */
fail_unless(Curl_u8_strset_count(&set) == 4, "wrong count pre add 5");
idx = (uint8_t)((4 * 4) + 3);
curl_msnprintf(buf, sizeof(buf), "str-%d", idx);
result = Curl_u8_strset_set(&set, idx, buf);
fail_unless(!result, "add4 failed");
fail_unless(!t3211_strcmp(buf, Curl_u8_strset_get(&set, idx)),
"wrong get4");
for(i = 0; i < 5; ++i) {
idx = (uint8_t)((4 * i) + 3);
curl_msnprintf(buf, sizeof(buf), "str-%d", idx);
fail_unless(!t3211_strcmp(buf, Curl_u8_strset_get(&set, idx)),
"wrong get loop5");
}
fail_unless(Curl_u8_strset_count(&set) == 5, "wrong count aftger add 5");
Curl_u8_strset_clear(&set);
/* Make a full set */
for(j = 0; j <= UINT8_MAX; ++j) {
i = (uint8_t)j;
curl_msnprintf(buf, sizeof(buf), "str-%d", i);
result = Curl_u8_strset_set(&set, i, buf);
fail_unless(!result, "loop256-add failed");
fail_unless(!t3211_strcmp(buf, Curl_u8_strset_get(&set, i)),
"wrong get loop256");
}
Curl_u8_strset_clear(&set);
fail_unless(!Curl_u8_strset_count(&set), "cleared strset not empty");
}
static CURLcode test_unit3211(const char *arg)
{
UNITTEST_BEGIN_SIMPLE
@ -142,8 +240,10 @@ static CURLcode test_unit3211(const char *arg)
120, 121, 122, 123, 124, 125, 126, 127,
};
check_set("s1", 100, s1, CURL_ARRAYSIZE(s1));
check_set("s2", 1000, s2, CURL_ARRAYSIZE(s2));
t3211_check_bset("s1", 100, s1, CURL_ARRAYSIZE(s1));
t3211_check_bset("s2", 1000, s2, CURL_ARRAYSIZE(s2));
t3211_check_strset1();
UNITTEST_END_SIMPLE
}