]> git.ipfire.org Git - thirdparty/dovecot/core.git/commitdiff
lib: data-stack - Add t_pop_pass_str() and T_END_PASS_STR[_IF]()
authorTimo Sirainen <timo.sirainen@open-xchange.com>
Mon, 16 Nov 2020 16:23:59 +0000 (18:23 +0200)
committeraki.tuomi <aki.tuomi@open-xchange.com>
Tue, 4 May 2021 07:02:35 +0000 (07:02 +0000)
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

src/lib/data-stack.c
src/lib/data-stack.h
src/lib/test-data-stack.c

index 85b284d8ebedbd7d410519848b30f43b63ced4bb..756e856fc2276240e2df1530815d0ff6a224ae4d 100644 (file)
@@ -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;
index 3f3d41ec4e53373ee252e69c3b2b790c9b36c3ad..c0384e9c3a70a3320d5992ae4b03cb8ac00ee744 100644 (file)
@@ -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.
 
index cc219fe64056d9cd142e15c7ca68a2864ba0ca62..34fc4fcad680528ea271ccf5993cd9675b445795 100644 (file)
@@ -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)