diff --git a/m-core.h b/m-core.h index 5507f96b..7f20d040 100644 --- a/m-core.h +++ b/m-core.h @@ -4381,6 +4381,33 @@ m_core_parse2_enum (const char str[], const char **endptr) #define M_DEFER_TRY_INJECT_POST(cont, clear) /* Nothing */ +/* If exceptions are activated, M_CHAIN_INIT enables support for chaining + initialization at the begining of a constructor for the fields of the constructed + object so that even if the constructor failed and throw an exception, + the fields of the constructed object are properly cleared. + This is equivalent to C++ construct: void type() : field1(), field2() { rest of constructor } + M_CHAIN_INIT shall be the first instruction of the init function. + First argument is the initialization code. + Second argument is the clear code. + The clear code is only executed on exception. + USAGE: + void init_function(struct_t s) { + M_CHAIN_INIT(string_init(s->str), string_clear(s->str)) { + // Rest of initialization code + } + } + */ +#define M_CHAIN_INIT(init, clear) \ + M_CHAIN_INIT_INTERNAL(M_C(m_var_, __LINE__), init, clear) + +#define M_CHAIN_INIT_INTERNAL(cont, init, clear) \ + for(bool cont = true; cont; cont = false) \ + M_DEFER_TRY_INJECT_PRE(cont, clear) \ + for( init; cont ; cont = false) \ + M_DEFER_TRY_INJECT_POST(cont, clear) \ + for( (void) 0; cont; cont = false) + + /* Declare a variable, initialize it, continue if the initialization succeeds, and clears the variable afterwards. Otherwise, stop the execution and execute else_code if defined. diff --git a/tests/test-mtry.c b/tests/test-mtry.c index 3e389e92..f2f84c8c 100644 --- a/tests/test-mtry.c +++ b/tests/test-mtry.c @@ -456,6 +456,102 @@ static void test3(void) assert(init == false); } +typedef struct { + testobj_t o1; + testobj_t o2; + int num; +} struct_t[1]; + +// Pseudo execution counter +volatile int g_flow = 0; +// Throw execution when g_flow == g_throw +volatile int g_throw = 0; + +static void struct_init(struct_t x) +{ + M_CHAIN_INIT(testobj_init(x->o1), testobj_clear(x->o1) ) + M_CHAIN_INIT(testobj_init(x->o2), testobj_clear(x->o2) ) { + x->num = 0; + if (++g_flow == g_throw) M_THROW(1); + } +} + +static void struct_init_set(struct_t x, const struct_t y) +{ + M_CHAIN_INIT(testobj_init_set(x->o1, y->o1), testobj_clear(x->o1) ) { + // Throw within the constructor (object partially constructed) + if (++g_flow == g_throw) M_THROW(1); + M_CHAIN_INIT(testobj_init_set(x->o2, y->o2), testobj_clear(x->o2) ) { + x->num = y->num; + if (++g_flow == g_throw) M_THROW(1); + } + } +} + +static void struct_clear(struct_t x) +{ + testobj_clear(x->o1); + testobj_clear(x->o2); +} + +#define M_OPL_struct_t() M_CLASSIC_OPLIST(struct) + +static void test4(void) +{ + g_flow = 0; + g_throw = 4; + M_TRY(main) { + assert(++g_flow == 1); + M_LET(a, struct_t) { + assert(++g_flow == 3); + M_LET( (b, a), struct_t) { + assert(false); + } + } + } M_CATCH(main, 0) { + assert(++g_flow == 5); + } + + g_flow = 0; + g_throw = 5; + M_TRY(main) { + assert(++g_flow == 1); + M_LET(a, struct_t) { + assert(++g_flow == 3); + M_LET( (b, a), struct_t) { + assert(false); + } + } + } M_CATCH(main, 0) { + assert(++g_flow == 6); + } + + g_flow = 0; + g_throw = 2; + M_TRY(main) { + assert(++g_flow == 1); + M_LET(a, struct_t) { + assert(false); + } + } M_CATCH(main, 0) { + assert(++g_flow == 3); + } + + g_flow = 0; + g_throw = 0; + M_TRY(main) { + assert(++g_flow == 1); + M_LET(a, struct_t) { + assert(++g_flow == 3); + M_LET( (b, a), struct_t) { + assert(++g_flow == 6); + } + } + } M_CATCH(main, 0) { + assert(false); + } +} + static void test_final(void) { // Throw without a try block shall raise the M_RAISE_FATAL macro @@ -472,6 +568,7 @@ int main(void) test1(); test2(); test3(); + test4(); testobj_final_check(); test_final(); exit(1); // Shall not be reached.