glib-aux: add support for starting with stack-allocated buffer in NMStrBuf

Allow to initialize NMStrBuf with an externally allocated array.
Usually a stack buffer. If the NMStrBuf grows beyond the size of
that initial buffer, then it would switch using malloc.

The idea is to support the common case where the result is small enough
to fit on the stack.

I always wanted to do such optimization because the main purpose of
NMStrBuf is to put it on the stack and ad-hoc construct a string.
I just figured, it would complicate the implementation and add
a runtime overhead. But turns out, it doesn't really.
The biggest question is how NMStrBuf should behave with a pre-allocated
buffer? Turns out, most choices can be made in a rather obvious way.
The only non-obvious thing is that nm_str_buf_finalize() would malloc()
a buffer, but that too seems consistent and what a user would probably
expect. As such, this doesn't seem to add unexpected semantics to the API.
This commit is contained in:
Thomas Haller
2022-04-29 19:01:22 +02:00
parent 77c8b2960a
commit 13d25f9d0b
3 changed files with 120 additions and 28 deletions

View File

@@ -5841,10 +5841,22 @@ _nm_str_buf_ensure_size(NMStrBuf *strbuf, gsize new_size, gboolean reserve_exact
new_size = nm_utils_get_next_realloc_size(!strbuf->_priv_do_bzero_mem, new_size);
}
if (strbuf->_priv_malloced) {
strbuf->_priv_str = nm_secret_mem_realloc(strbuf->_priv_str,
strbuf->_priv_do_bzero_mem,
strbuf->_priv_allocated,
new_size);
} else {
char *old = strbuf->_priv_str;
strbuf->_priv_str = g_malloc(new_size);
if (strbuf->_priv_len > 0) {
memcpy(strbuf->_priv_str, old, strbuf->_priv_len);
if (strbuf->_priv_do_bzero_mem)
nm_explicit_bzero(old, strbuf->_priv_len);
}
strbuf->_priv_malloced = TRUE;
}
strbuf->_priv_allocated = new_size;
}

View File

@@ -26,6 +26,7 @@ typedef struct _NMStrBuf {
};
bool _priv_do_bzero_mem;
bool _priv_malloced;
} NMStrBuf;
/*****************************************************************************/
@@ -36,21 +37,56 @@ _nm_str_buf_assert(const NMStrBuf *strbuf)
nm_assert(strbuf);
nm_assert((!!strbuf->_priv_str) == (strbuf->_priv_allocated > 0));
nm_assert(strbuf->_priv_len <= strbuf->_priv_allocated);
nm_assert(!strbuf->_priv_malloced || strbuf->_priv_str);
}
static inline NMStrBuf
NM_STR_BUF_INIT_FULL(char *str,
gsize len,
gsize allocated,
gboolean malloced,
gboolean do_bzero_mem)
{
NMStrBuf strbuf = {
._priv_str = allocated > 0 ? str : NULL,
._priv_allocated = allocated,
._priv_len = len,
._priv_do_bzero_mem = do_bzero_mem,
._priv_malloced = allocated > 0 && malloced,
};
_nm_str_buf_assert(&strbuf);
return strbuf;
}
static inline NMStrBuf
NM_STR_BUF_INIT(gsize allocated, gboolean do_bzero_mem)
{
NMStrBuf strbuf = {
._priv_str = allocated ? g_malloc(allocated) : NULL,
._priv_allocated = allocated,
._priv_len = 0,
._priv_do_bzero_mem = do_bzero_mem,
};
return strbuf;
return NM_STR_BUF_INIT_FULL(allocated > 0 ? g_malloc(allocated) : NULL,
0,
allocated,
allocated > 0,
do_bzero_mem);
}
#define NM_STR_BUF_INIT_A(size, do_bzero_mem) \
NM_STR_BUF_INIT_FULL( \
g_alloca(size), \
0, \
NM_STATIC_ASSERT_EXPR_1((size) > 0 && (size) <= NM_UTILS_GET_NEXT_REALLOC_SIZE_488) \
? (size) \
: 0, \
FALSE, \
(do_bzero_mem));
#define NM_STR_BUF_INIT_ARR(arr, do_bzero_mem) \
NM_STR_BUF_INIT_FULL((arr), \
0, \
NM_STATIC_ASSERT_EXPR_1(sizeof(arr) > sizeof(char *)) ? sizeof(arr) : 0, \
FALSE, \
(do_bzero_mem));
static inline void
nm_str_buf_init(NMStrBuf *strbuf, gsize len, bool do_bzero_mem)
{
@@ -465,7 +501,9 @@ nm_str_buf_get_char(const NMStrBuf *strbuf, gsize index)
* is afterwards in undefined state, though it can be
* reused after nm_str_buf_init().
* Note that if no string is allocated yet (after nm_str_buf_init() with
* length zero), this will return %NULL. */
* length zero), this will return %NULL.
*
* If the buffer was not malloced before, it will be malloced now. */
static inline char *
nm_str_buf_finalize(NMStrBuf *strbuf, gsize *out_len)
{
@@ -476,6 +514,16 @@ nm_str_buf_finalize(NMStrBuf *strbuf, gsize *out_len)
if (!strbuf->_priv_str)
return NULL;
if (!strbuf->_priv_malloced) {
char *str = g_steal_pointer(&strbuf->_priv_str);
char *result;
result = g_strndup(str, strbuf->_priv_len);
if (strbuf->_priv_do_bzero_mem)
nm_explicit_bzero(str, strbuf->_priv_len);
return result;
}
nm_str_buf_maybe_expand(strbuf, 1, TRUE);
strbuf->_priv_str[strbuf->_priv_len] = '\0';
@@ -517,6 +565,7 @@ nm_str_buf_destroy(NMStrBuf *strbuf)
_nm_str_buf_assert(strbuf);
if (strbuf->_priv_do_bzero_mem)
nm_explicit_bzero(strbuf->_priv_str, strbuf->_priv_len);
if (strbuf->_priv_malloced)
g_free(strbuf->_priv_str);
/* the buffer is in invalid state afterwards, however, we clear it

View File

@@ -917,15 +917,29 @@ test_nm_str_buf(void)
{
guint i_run;
for (i_run = 0; TRUE; i_run++) {
nm_auto_str_buf NMStrBuf strbuf = {};
for (i_run = 0; i_run < 1000; i_run++) {
char stack_buf[1024];
nm_auto_str_buf NMStrBuf strbuf;
nm_auto_free_gstring GString *gstr = NULL;
int i, j, k;
int c;
switch (nmtst_get_rand_uint32() % 10) {
case 0:
memset(&strbuf, 0, sizeof(strbuf));
break;
case 1 ... 4:
strbuf = NM_STR_BUF_INIT_FULL(stack_buf,
0,
nmtst_get_rand_uint32() % sizeof(stack_buf),
FALSE,
nmtst_get_rand_bool());
break;
default:
nm_str_buf_init(&strbuf, nmtst_get_rand_uint32() % 200u + 1u, nmtst_get_rand_bool());
break;
}
if (i_run < 1000) {
c = nmtst_get_rand_word_length(NULL);
for (i = 0; i < c; i++)
nm_str_buf_append_c(&strbuf, '0' + (i % 10));
@@ -935,9 +949,26 @@ test_nm_str_buf(void)
nm_str_buf_erase(&strbuf, j, k, nmtst_get_rand_bool());
g_string_erase(gstr, j, k);
if (gstr->str[0])
g_assert_cmpstr(gstr->str, ==, nm_str_buf_get_str(&strbuf));
else
g_assert(NM_IN_STRSET(nm_str_buf_get_str(&strbuf), NULL, ""));
}
for (i_run = 0; i_run < 50; i_run++) {
char stack_buf[20];
nm_auto_str_buf NMStrBuf strbuf = NM_STR_BUF_INIT_ARR(stack_buf, nmtst_get_rand_bool());
nm_str_buf_append_c_len(&strbuf, 'a', nmtst_get_rand_uint32() % (sizeof(stack_buf) * 2));
if (strbuf.len <= sizeof(stack_buf)) {
g_assert(stack_buf == nm_str_buf_get_str_unsafe(&strbuf));
} else
return;
g_assert(stack_buf != nm_str_buf_get_str_unsafe(&strbuf));
if (strbuf.len < sizeof(stack_buf)) {
g_assert(stack_buf == nm_str_buf_get_str(&strbuf));
} else
g_assert(stack_buf != nm_str_buf_get_str(&strbuf));
}
}