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;
/* 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);
} 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.
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)