From: Timo Sirainen Date: Mon, 16 Nov 2020 16:23:59 +0000 (+0200) Subject: lib: data-stack - Add t_pop_pass_str() and T_END_PASS_STR[_IF]() X-Git-Tag: 2.3.16~248 X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=6f68dc67369264666e7273a4b57e26427df939df;p=thirdparty%2Fdovecot%2Fcore.git lib: data-stack - Add t_pop_pass_str() and T_END_PASS_STR[_IF]() This simplifies passing error strings out of stack frames. For example: const char *error; T_BEGIN { ... if (ret < 0) error = t_strdup_printf("foo() failed: %m"); } T_END_PASS_STR_IF(ret < 0, &error); // error is still valid --- diff --git a/src/lib/data-stack.c b/src/lib/data-stack.c index 85b284d8eb..756e856fc2 100644 --- a/src/lib/data-stack.c +++ b/src/lib/data-stack.c @@ -332,6 +332,21 @@ bool t_pop(data_stack_frame_t *id) return TRUE; } +bool t_pop_pass_str(data_stack_frame_t *id, const char **str) +{ + if (str == NULL || !data_stack_frame_contains(id, *str)) + return t_pop(id); + + /* FIXME: The string could be memmove()d to the beginning of the + data stack frame and the previous frame's size extended past it. + This would avoid the malloc. It's a bit complicated though. */ + char *tmp_str = i_strdup(*str); + bool ret = t_pop(id); + *str = t_strdup(tmp_str); + i_free(tmp_str); + return ret; +} + static struct stack_block *mem_block_alloc(size_t min_size) { struct stack_block *block; diff --git a/src/lib/data-stack.h b/src/lib/data-stack.h index 3f3d41ec4e..c0384e9c3a 100644 --- a/src/lib/data-stack.h +++ b/src/lib/data-stack.h @@ -54,6 +54,10 @@ data_stack_frame_t t_push_named(const char *format, ...) ATTR_HOT ATTR_FORMAT(1, /* Returns TRUE on success, FALSE if t_pop() call was leaked. The caller should panic. */ bool t_pop(data_stack_frame_t *id) ATTR_HOT; +/* Same as t_pop(), but move str out of the stack frame if it is inside. + This can be used to easily move e.g. error strings outside stack frames. */ +bool t_pop_pass_str(data_stack_frame_t *id, const char **str); + /* Pop the last data stack frame. This shouldn't be called outside test code. */ void t_pop_last_unsafe(void); @@ -70,6 +74,33 @@ void t_pop_last_unsafe(void); } STMT_END; \ } STMT_END +/* Usage: + const char *error; + T_BEGIN { + ... + if (ret < 0) + error = t_strdup_printf("foo() failed: %m"); + } T_END_PASS_STR_IF(ret < 0, &error); + // error is still valid +*/ +#define T_END_PASS_STR_IF(pass_condition, str) \ + STMT_START { \ + if (unlikely(!t_pop_pass_str(&_data_stack_cur_id, (pass_condition) ? (str) : NULL))) \ + i_panic("Leaked t_pop() call"); \ + } STMT_END; \ + } STMT_END +/* + Usage: + const char *result; + T_BEGIN { + ... + result = t_strdup_printf(...); + } T_END_PASS_STR(&result); + // result is still valid +*/ +#define T_END_PASS_STR(str) \ + T_END_PASS_STR_IF(TRUE, str) + /* WARNING: Be careful when using these functions, it's too easy to accidentally save the returned value somewhere permanently. diff --git a/src/lib/test-data-stack.c b/src/lib/test-data-stack.c index cc219fe640..34fc4fcad6 100644 --- a/src/lib/test-data-stack.c +++ b/src/lib/test-data-stack.c @@ -159,12 +159,59 @@ static void test_ds_clean_after_pop(void) test_end(); } +static void test_ds_pass_str(void) +{ + data_stack_frame_t frames[32*2 + 1]; /* BLOCK_FRAME_COUNT*2 + 1 */ + const char *strings[N_ELEMENTS(frames)]; + + test_begin("data-stack pass string"); + for (unsigned int frame = 0; frame < N_ELEMENTS(frames); frame++) { + frames[frame] = t_push("test"); + if (frame % 10 == 5) { + /* increase block counts */ + (void)t_malloc_no0(1024*30); + (void)t_malloc_no0(1024*30); + } + strings[frame] = t_strdup_printf("frame %d", frame); + for (unsigned int i = 0; i <= frame; i++) { + test_assert_idx(data_stack_frame_contains(&frames[frame], strings[i]) == (i == frame), + frame * 100 + i); + } + } + + const char *last_str = strings[N_ELEMENTS(frames)-1]; + for (unsigned int frame = N_ELEMENTS(frames); frame > 0; ) { + frame--; + test_assert(t_pop_pass_str(&frames[frame], &last_str)); + } + test_assert_strcmp(last_str, "frame 64"); + + /* make sure the pass_condition works properly */ + const char *error, *orig_error, *orig2_error; + T_BEGIN { + (void)t_strdup("qwertyuiop"); + error = orig_error = t_strdup("123456"); + } T_END_PASS_STR_IF(TRUE, &error); + + orig2_error = orig_error; + T_BEGIN { + (void)t_strdup("abcdefghijklmnopqrstuvwxyz"); + } T_END_PASS_STR_IF(FALSE, &orig2_error); + /* orig_error and orig2_error both point to freed data stack frame */ + test_assert(orig_error == orig2_error); + /* the passed error is still valid though */ + test_assert_strcmp(error, "123456"); + + test_end(); +} + void test_data_stack(void) { test_ds_buffers(); test_ds_realloc(); test_ds_recursive(20, 80); test_ds_clean_after_pop(); + test_ds_pass_str(); } enum fatal_test_state fatal_data_stack(unsigned int stage)