From bfa948dc7af261792f584c293d50c7e56820f07f Mon Sep 17 00:00:00 2001 From: Patrick Pelissier Date: Mon, 13 Mar 2023 23:31:50 +0100 Subject: [PATCH] Add WIP implementation for the exception memory model: add header m-try that defines the M_TRY / M_CATCH macros that macros similarly to the C++ try / catch keywords. Add implementation for C++, GCC, CLANG and pure C compiler. --- m-try.h | 519 ++++++++++++++++++++++++++++++++++++++++++++++ tests/test-mtry.c | 367 ++++++++++++++++++++++++++++++++ 2 files changed, 886 insertions(+) create mode 100644 m-try.h create mode 100644 tests/test-mtry.c diff --git a/m-try.h b/m-try.h new file mode 100644 index 00000000..70a359ac --- /dev/null +++ b/m-try.h @@ -0,0 +1,519 @@ +/* + * M*LIB - try / catch mechanism for M*LIB + * + * Copyright (c) 2017-2023, Patrick Pelissier + * All rights reserved. + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE REGENTS AND CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ +#ifndef MSTARLIB_TRY_H +#define MSTARLIB_TRY_H + +#include +#include "m-core.h" +#include "m-thread.h" + +/* + * Select mechanism to use for support of RAII and exception, + * so that for each variables defined in M_LET, + * their destructor is still called on throwing exceptions. + * It is either the C++ try, + * it uses a GCC or CLANG extension, + * or the standard c compliant way (much slower). + */ +#ifndef M_USE_TRY_MECHANISM +# if defined(__has_extension) +# if __has_extension(blocks) +# define M_TRY_CLANG_BLOCKS +# endif +# endif +# if defined(__cplusplus) +# define M_USE_TRY_MECHANISM 1 +# elif defined(M_TRY_CLANG_BLOCKS) +# define M_USE_TRY_MECHANISM 2 +# elif defined(__GNUC__) && !defined(__clang__) +# define M_USE_TRY_MECHANISM 3 +# else +# define M_USE_TRY_MECHANISM 4 +# endif +#endif + + +/* + * Start a protected section of code 'name' where all exceptions are catched + * by the associated CATCH section. + */ +#define M_TRY(name) \ + M_TRY_B( M_C(m_try_bool_, name), M_C(m_try_buf_, name), name) + + +/* + * Catch an exception associated to the TRY block 'name' that matches the given error_code + * If error_code is 0, it catches all error codes. + * error code shall be a constant positive integer. + */ +#define M_CATCH(name, error_code) M_CATCH_B(name, error_code) + + +/* + * Throw an exception to the upper try block + * error_code shall be the first argument. + * Other arguments are integers or pointers stored in the exception. + * error code shall be a constant positive integer. + */ +#define M_THROW(...) do { \ + M_STATIC_ASSERT(M_RET_ARG1 (__VA_ARGS__) != 0, \ + M_LIB_NOT_A_CONSTANT_NON_NULL_INTEGER, \ + "The error code shall be a non null constant"); \ + M_STATIC_ASSERT(M_NARGS (__VA_ARGS__) <= 1+M_USE_MAX_EXCEPTION, \ + M_LIB_TOO_MANY_ARGUMENTS, \ + "There are too many arguments for an exception."); \ + M_IF_NARGS_EQ1(__VA_ARGS__)(M_THROW_1, M_THROW_N)(__VA_ARGS__); \ + } while (0) + + +/* + * Size of the extra data that are stored in an exception. + */ +#ifndef M_USE_MAX_EXCEPTION +#define M_USE_MAX_EXCEPTION 5 +#endif + +/* + * The exception itself. + * + * It is POD data where every fields can be used by the user. + * It has been decided to have only one exception to simplify error + * code, and because : + * - using generic types is much hard in C to do (still possible) + * - it will make exceptions more usable for errors which should not + * be handled by exceptions. + * + * For C++, we need to encapsulate it in a template, + * so that it can be a unique type for each error code, + * which is needed for the catch mechanism. + * We all need to override the operator -> since the C++ + * throw the type and catch the type, whereas the C back-end + * throw the type and catch a pointer to the type: + * within the catch block you are supposed to use the arrow + * operator to test the content of the exception. + */ +#if M_USE_TRY_MECHANISM == 1 +namespace m_lib { +template +#endif +struct m_exception_s { + unsigned error_code; // Error code + unsigned short line; // Line number where the error was detected + unsigned short n; // Number of entries in 'error' table + const char *filename; // filename where the error was detected + intptr_t error[M_USE_MAX_EXCEPTION]; // Error specific data +#ifdef __cplusplus + m_exception_s *operator->() { return this; } +#endif +}; +#if M_USE_TRY_MECHANISM == 1 +} +#endif + +// Typical Error codes (TODO: add more classic?) +#define M_ERROR_MEMORY 1 +#define M_ERROR_ACCESS 2 +#define M_ERROR_BUSY 3 + +/* + * Define all global needed by the try mechanism with a + * thread attribute. It needs to be defined once in all the program + */ +#define M_TRY_DEF_ONCE() M_TRY_DEF_ONCE_B() + + +/* + * Re-throw the last exception + * It shall be done in a CATCH block. + */ +#define M_RETHROW() m_rethrow() + + +/*****************************************************************************/ +/********************************** INTERNAL *********************************/ +/*****************************************************************************/ + +/* + * Define the C++ back-end. + * It is fully different from C back-end as it reuses the classic try of the C++. + * Surprisingly it has more constraints than the C one. + * error_code shall be a positive, constant integer. + * the catch all block shall always be the last block. + * at least catch block is mandatory for each try block. + * Note that theses constraints are meaningless in real code, + * and simply good behavior. + * Notice also that you won't have any access to the exception for a catch all error. + */ +#if M_USE_TRY_MECHANISM == 1 + +// Define the CATCH block. If error_code is 0, it shall catch all errors. +// NOTE: It will even catch non M*LIB errors. +#define M_CATCH_B(name, error_code) \ + M_IF(M_BOOL(error_code)) \ + (catch (m_lib::m_exception_s &name), catch (...)) + +// No global to define in C++ +#define M_TRY_DEF_ONCE_B() /* Nothing to do */ + +// Reuse the try keyword of the C++ +#define M_TRY_B(cont, buf, exception) \ + try + +// Reuse the throw keyword of the C++ +// by throwing the type m_lib::m_exception_s +#define M_THROW_1(error_code) \ + throw m_lib::m_exception_s{ error_code, __LINE__, 0, __FILE__, { 0 } } + +// Reuse the throw keyword of the C++ +// by throwing the type m_lib::m_exception_s +#define M_THROW_N(error_code, ...) \ + throw m_lib::m_exception_s{ error_code, __LINE__, \ + M_NARGS(__VA_ARGS__), __FILE__, { __VA_ARGS__ } } + +// Nothing to inject for a pre initialization of a M*LIB object +#define M_LET_TRY_INJECT_PRE_B(cont, oplist, name) /* Nothing to do */ + +// Code to inject for a post initialization of a M*LIB object +// We create a C++ object with a destructor that will call the CLEAR operator of the M*LIB object +// by using a lambda function. +// If the CLEAR operator is called naturally, we disable the destructor of the C++ object. +#define M_LET_TRY_INJECT_POST_B(cont, oplist, name) \ + for(m_lib::m_regclear M_C(m_try_regclear_, name){[&](void) { M_CALL_CLEAR(oplist, name); } } \ + ; cont ; M_C(m_try_regclear_, name).disable() ) + +// Definition of the C++ object wrapper +// The registered function is called by the destructor, +// except if the disable function has been called. +#include +namespace m_lib { + class m_regclear { + std::function function; + bool done; + public: + inline m_regclear(const std::function &f) : function{f}, done{false} { } + inline void disable(void) { done = true; } + inline ~m_regclear() { if (done == false) { function(); done = true; } } + }; +} + +// Rethrow is simply throw without any argument +#define m_rethrow() throw + + +/*****************************************************************************/ + +/* The C back-end. + * It is fully different from the C++ back-end and is based on setjmp/lonjmp + * (classic implementation). + * The main difficulty is the mechanism to register the CLEAR operators + * to call when throwing an exception. + * Contrary to the C++ back-end, it is not cost-free as it adds some + * instructions to the normal behavior of the program. + */ +#else + +// Define the CATCH block associated to the 'name' TRY to catch the exception +// associated to 'error_code' and provide 'name' as a pointer to the exception +// if the exception matches the error code. +// If error code is 0, it matches all errors. +#define M_CATCH_B(name, error_code) \ + else if (m_catch( M_C(m_try_buf_, name), (error_code), &name)) + +// Macro to add once in one source file to define theses global: +#define M_TRY_DEF_ONCE_B() \ + M_THREAD_ATTR struct m_try_s *m_global_error_list; \ + M_THREAD_ATTR struct m_exception_s m_global_exception; + +// Define the operator to define nested functions (GCC) or blocks (CLANG) +#if M_USE_TRY_MECHANISM == 2 +# define M_TRY_FUNC_OPERATOR ^ +#else +# define M_TRY_FUNC_OPERATOR * +#endif + +// Define the linked structure used to identify what is present in the C stack. +// We create for each M_TRY and each M_LET a new node in the stack that represents +// this point in the stack frame. Each nodes are linked together, so that we can +// analyze the stack frame on exception. +typedef struct m_try_s { + enum { M_STATE_TRY, M_STATE_EXCEPTION_IN_PROGRESS, M_STATE_EXCEPTION_CATCHED, + M_STATE_CLEAR_JMPBUF, M_STATE_CLEAR_CB } kind; + struct m_try_s *next; + union { + jmp_buf buf; + struct { void (M_TRY_FUNC_OPERATOR func)(void*); void *data; } clear; + } data; +} m_try_t[1]; + +// Define the TRY block. +// Classic usage of the for trick to push destructor on the exit path. +#define M_TRY_B(cont, buf, exception) \ + for(bool cont = true ; cont ; cont = false) \ + for(m_try_t buf ; cont ; m_try_clear(buf), cont = false ) \ + for(const struct m_exception_s *exception = NULL; cont; cont = false, exception = exception) \ + if (m_try_init(buf)) + +// Throw the error code +#define M_THROW_1(error_code) \ + m_throw( &(const struct m_exception_s) { error_code, __LINE__, 0, __FILE__, { 0 } } ) + +// Throw the error code +#define M_THROW_N(error_code, ...) \ + m_throw( &(const struct m_exception_s) { error_code, __LINE__, M_NARGS(__VA_ARGS__), __FILE__, \ + { __VA_ARGS__ } } ) + +// The global thread attribute variables. +extern M_THREAD_ATTR struct m_try_s *m_global_error_list; +extern M_THREAD_ATTR struct m_exception_s m_global_exception; + +// Copy an exception to another. +static inline void +m_exception_set(struct m_exception_s *out, const struct m_exception_s *in) +{ + if (in != out) { + memcpy(out, in, sizeof *out); + } +} + +// Initialize the state to a TRY state. +static inline void +m_try_init(m_try_t state) +{ + state->kind = M_STATE_TRY; + state->next = m_global_error_list; + m_global_error_list = state; + // setjmp needs to be done in the MACRO. +} +#define m_try_init(s) \ + M_LIKELY ((m_try_init(s), setjmp(((s)->data.buf)) != 1)) + +// Throw the given exception +// TODO: attribute no return +static inline void +m_throw(const struct m_exception_s *exception) +{ + // Analyze the error list to see what has been registered + struct m_try_s *e = m_global_error_list; + while (e != NULL) { + // A CLEAR operator has been registered + // Call it + if (e->kind == M_STATE_CLEAR_CB) { + e->data.clear.func(e->data.clear.data); + } + else { + // A JUMP command has been registered. + // Either due to the M_TRY block + // or because of the jump to the CLEAR operator of the object to clear. + M_ASSERT(e->kind == M_STATE_TRY || e->kind == M_STATE_CLEAR_JMPBUF); + // If the exception is already m_global_exception, it won't be copied + m_exception_set(&m_global_exception, exception); + e->kind = M_STATE_EXCEPTION_IN_PROGRESS; + m_global_error_list = e; + longjmp(e->data.buf, 1); + } + // Next stack frame + e = e->next; + } + // No exception found. + // Display the information. + M_RAISE_FATAL("Exception '%u' raised by (%s:%d) is not catched. Program aborted.\n", + exception->error_code, exception->filename, exception->line); +} + +// Rethrow the error +static inline void +m_rethrow(void) +{ + M_ASSERT(m_global_error_list != NULL); + m_throw(&m_global_exception); +} + +// Catch the error code associated to the TRY block state +// and provide a pointer to the exception (which is a global). +static inline bool +m_catch(m_try_t state, unsigned error_code, const struct m_exception_s **exception) +{ + M_ASSERT(m_global_error_list == state); + M_ASSERT(state->kind == M_STATE_EXCEPTION_IN_PROGRESS); + *exception = &m_global_exception; + if (error_code != 0 && m_global_exception.error_code != error_code) + return false; + // The exception has been catched. + state->kind = M_STATE_EXCEPTION_CATCHED; + // Unstack the try block, so that next throw command in the CATCH block + // will reach the upper TRY block. + m_global_error_list = state->next; + return true; +} + +// Disable the current TRY block. +static inline void +m_try_clear(m_try_t state) +{ + // Even if there is a CATCH block and an unstack of the exception + // m_global_error_list won't be changed. + m_global_error_list = state->next; + if (M_UNLIKELY (state->kind == M_STATE_EXCEPTION_IN_PROGRESS)) { + // There was no catch for this error. + // Forward it to the upper level. + m_rethrow(); + } +} + +// Implement the M_LET injection macros, so that the CLEAR operator is called on exception +// +#if M_USE_TRY_MECHANISM == 1 +# error M*LIB: Internal error. C++ back-end requested within C implementation. + +#elif M_USE_TRY_MECHANISM == 2 || M_USE_TRY_MECHANISM == 3 +// Use of CLANG blocks or GCC nested functions. + +// We register a call to the CLEAR callback. +// We don't modify m_global_error_list until we have successfully called the INIT operator +// to avoid registering the CLEAR operator on exception whereas the object is not initialized yet. +// However we register the position in the stack frame now so that in case of partial initialization +// of the object (if the INIT operator of the object calls other INIT operators of composed fields), +// since partial initialization will be unstacked naturally by the composing object. +static inline bool +m_try_init_pre(m_try_t state) +{ + state->kind = M_STATE_CLEAR_CB; + state->next = m_global_error_list; + return true; +} + +// We register the function to call of the initialized object. +static inline bool +m_try_init_post(m_try_t state, void (M_TRY_FUNC_OPERATOR func)(void*), void *data) +{ + state->data.clear.func = func; + state->data.clear.data = data; + m_global_error_list = state; + return true; +} + +// The object has been cleared. +// We can pop the stack frame of the errors. +static inline void +m_try_clear_pre(m_try_t state) +{ + m_global_error_list = state->next; +} + +// M_LET Injection / pre initialization +// Initialize the stack frame. +#define M_LET_TRY_INJECT_PRE_B(cont, oplist, name) \ + for(m_try_t M_C(m_try_state_, name); cont && \ + m_try_init_pre(M_C(m_try_state_, name) ); ) + +// M_LET Injection / post initialization +// Save the function to call using +#if M_USE_TRY_MECHANISM == 2 +// blocks (CLANG) +#define M_LET_TRY_INJECT_POST_B(cont, oplist, name) \ + for(m_try_init_post(M_C(m_try_state_, name), \ + ^ void (void *_data) { M_GET_TYPE oplist *_t = _data; M_CALL_CLEAR(oplist, *_t); }, \ + (void*) &name); cont; m_try_clear_pre(M_C(m_try_state_, name)) ) +#else +// nested functions (GCC) +#define M_LET_TRY_INJECT_POST_B(cont, oplist, name) \ + for(m_try_init_post(M_C(m_try_state_, name), \ + __extension__ ({ __extension__ void _callback (void *_data) { M_GET_TYPE oplist *_t = _data; M_CALL_CLEAR(oplist, *_t); } _callback; }), \ + (void*) &name); cont; m_try_clear_pre(M_C(m_try_state_, name)) ) +#endif + +#elif M_USE_TRY_MECHANISM == 4 +// STD C compliant (without compiler extension): use of setjmp +// This is the basic implementation in case of compiler unknown. +// It uses setjmp/longjmp, and as such, is much slower than +// other implementations. + +// Pre initialization function. Save the stack frame for a longjmp +static inline bool +m_try_init_pre(m_try_t state) +{ + state->kind = M_STATE_CLEAR_JMPBUF; + state->next = m_global_error_list; + return true; +} + +// Post initialization function. Register the stack frame for a longjmp +static inline void +m_try_init_post(m_try_t state) +{ + m_global_error_list = state; +} +// And call setjmp to register the position in the code. +#define m_try_init_post(s) \ + M_LIKELY ((m_try_init_post(s), setjmp(((s)->data.buf)) != 1)) + +// The object has been cleared. +// We can pop the stack frame of the errors. +static inline void +m_try_clear_pre(m_try_t state) +{ + m_global_error_list = state->next; +} + +// M_LET Injection / pre initialization +// Initialize the stack frame. +#define M_LET_TRY_INJECT_PRE_B(cont, oplist, name) \ + for(m_try_t M_C(m_try_state_, name); cont && \ + m_try_init_pre(M_C(m_try_state_, name)); ) + +// M_LET Injection / post initialization +// Register the stack frame and tests for the longjmp. +// In which case call the CLEAR operator, unstack the error list and rethrow the error. +#define M_LET_TRY_INJECT_POST_B(cont, oplist, name) \ + for( ; cont ; m_try_clear_pre(M_C(m_try_state_, name))) \ + if (m_try_init_post(M_C(m_try_state_, name)) \ + || (M_CALL_CLEAR(oplist, name), m_try_clear_pre(M_C(m_try_state_, name)), m_rethrow(), false)) + +#else +#error M*LIB: Invalid value for M_USE_TRY_MECHANISM [1..4] +#endif + +#endif + +/*****************************************************************************/ + +// Macro injection for M_LET. +// If the oplist defined NOCLEAR property, we won't register this variable for clear on exception +#undef M_LET_TRY_INJECT_PRE +#define M_LET_TRY_INJECT_PRE(cont, oplist, name) \ + M_IF(M_GET_PROPERTY(oplist, NOCLEAR))(M_EAT, M_LET_TRY_INJECT_PRE_B) \ + (cont, oplist, name) + +#undef M_LET_TRY_INJECT_POST +#define M_LET_TRY_INJECT_POST(cont, oplist, name) \ + M_IF(M_GET_PROPERTY(oplist, NOCLEAR))(M_EAT, M_LET_TRY_INJECT_POST_B) \ + (cont, oplist, name) + +// In case of MEMORY FULL errors, throw an error instead of aborting. +#undef M_MEMORY_FULL +#define M_MEMORY_FULL(size) M_THROW(M_ERROR_MEMORY, (intptr_t)(size)) + +#endif + diff --git a/tests/test-mtry.c b/tests/test-mtry.c new file mode 100644 index 00000000..9d4b2bab --- /dev/null +++ b/tests/test-mtry.c @@ -0,0 +1,367 @@ +/* + * Copyright (c) 2017-2023, Patrick Pelissier + * All rights reserved. + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE REGENTS AND CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ +#include +#include "m-try.h" +#include "test-obj.h" + +M_TRY_DEF_ONCE() + +static void test_throw(void) +{ + M_TRY(protect) { + M_THROW(67, 56); + } M_CATCH(protect, 67) { + assert(protect->n == 1); + M_THROW(M_ERROR_MEMORY, protect->error[0], 4, 6, 8); + } +} + +// Test of try / catch with throw. +// No memory allocation +static void test1(void) +{ + volatile int flow = 0; + + flow = 0; + assert(flow ++ == 0); + M_TRY(test1) { + assert(flow ++ == 1); + } M_CATCH(test1, M_ERROR_MEMORY) { + assert(0); + } + assert(flow ++ == 2); + + flow = 0; + assert(flow ++ == 0); + M_TRY(test1) { + assert(flow ++ == 1); + M_THROW(M_ERROR_MEMORY, 0); + assert(0); + } M_CATCH(test1, M_ERROR_MEMORY) { + assert(flow++ == 2); + } + assert(flow ++ == 3); + + flow = 0; + assert(flow ++ == 0); + M_TRY(test1) { + assert(flow ++ == 1); + M_TRY(test2) { + assert(flow ++ == 2); + M_THROW(189, 0); + assert(0); + } M_CATCH(test2, 189) { + assert(flow++ == 3); + } + } M_CATCH(test1, M_ERROR_MEMORY) { + assert(0); + } + assert(flow ++ == 4); + + flow = 0; + assert(flow ++ == 0); + M_TRY(test1) { + assert(flow ++ == 1); + M_TRY(test2) { + assert(flow ++ == 2); + M_TRY(test3) { + assert(flow ++ == 3); + } M_CATCH(test3, 67) {} + assert(flow ++ == 4); + M_TRY(test4) { + assert(flow ++ == 5); + M_THROW(M_ERROR_ACCESS, 12); + assert(0); + } M_CATCH(test4, 67) {} + assert(0); + } M_CATCH(test2, M_ERROR_ACCESS) { + assert(flow++ == 6); + assert(test2->n == 1); + assert(test2->error[0] == 12); + } + } M_CATCH(test1, M_ERROR_MEMORY) { + assert(0); + } + assert(flow ++ == 7); + + flow = 0; + assert(flow ++ == 0); + M_TRY(test1) { + assert(flow ++ == 1); + M_TRY(test2) { + assert(flow ++ == 2); + M_TRY(test3) { + assert(flow ++ == 3); + } M_CATCH(test3, 42) { + assert(0); + } + assert(flow ++ == 4); + M_TRY(test4) { + assert(flow ++ == 5); + M_THROW(42, 1, 3); + assert(0); + } M_CATCH(test4, 67) {} + assert(0); + } M_CATCH(test2, M_ERROR_ACCESS) { + assert(0); + } + } M_CATCH(test1, M_ERROR_MEMORY) { + assert(0); + } M_CATCH(test1, 42) { + assert(flow++ == 6); + assert(test1->n == 2); + assert(test1->error[0] == 1); + assert(test1->error[1] == 3); + } + assert(flow ++ == 7); + + flow = 0; + assert(flow ++ == 0); + M_TRY(test1) { + assert(flow ++ == 1); + M_TRY(test2) { + assert(flow ++ == 2); + M_TRY(test3) { + assert(flow ++ == 3); + } M_CATCH(test3, 42) { + assert(0); + } + assert(flow ++ == 4); + M_TRY(test4) { + assert(flow ++ == 5); + } M_CATCH(test4, 67) {} + assert(flow ++ == 6); + } M_CATCH(test2, M_ERROR_ACCESS) { + assert(0); + } + } M_CATCH(test1, M_ERROR_MEMORY) { + assert(0); + } M_CATCH(test1, 42) { + assert(0); + } + assert(flow ++ == 7); + + flow = 0; + assert(flow ++ == 0); + M_TRY(test1) { + assert(flow ++ == 1); + M_TRY(test2) { + assert(flow ++ == 2); + test_throw(); + } M_CATCH(test2, M_ERROR_ACCESS) { + assert(0); + } + } M_CATCH(test1, M_ERROR_MEMORY) { + assert(flow ++ == 3); + } M_CATCH(test1, 42) { + assert(0); + } + assert(flow ++ == 4); + + flow = 0; + assert(flow ++ == 0); + M_TRY(test1) { + assert(flow ++ == 1); + M_TRY(test2) { + assert(flow ++ == 2); + test_throw(); + } M_CATCH(test2, M_ERROR_MEMORY) { + assert(flow ++ == 3); + M_RETHROW(); + } + } M_CATCH(test1, M_ERROR_MEMORY) { + assert(flow ++ == 4); + assert(test1->n == 4); + assert(test1->error[0] == 56); + assert(test1->error[1] == 4); + assert(test1->error[2] == 6); + assert(test1->error[3] == 8); + } M_CATCH(test1, 42) { + assert(0); + } + assert(flow ++ == 5); + + flow = 0; + assert(flow ++ == 0); + M_TRY(test1) { + assert(flow ++ == 1); + M_TRY(test2) { + assert(flow ++ == 2); + M_THROW(23); + assert(0); + } M_CATCH(test2, M_ERROR_MEMORY) { + assert(0); + } + } M_CATCH(test1, 42) { + assert(0); + } M_CATCH(test1, 0) { + // Error code 0 is special and catch all errors. + assert(flow ++ == 3); +#ifndef __cplusplus + assert(test1->error_code == 23); + assert(test1->n == 0); +#endif + } + assert(flow ++ == 4); + +} + +static void test2(void) +{ + M_TRY(test1) { + M_LET(obj, TESTOBJ_OPLIST) { + testobj_set_ui(obj, 15); + } + } M_CATCH(test1, 0) { + assert(0); + } + + M_TRY(test1) { + M_LET(obj, TESTOBJ_OPLIST) { + testobj_set_ui(obj, 15); + M_THROW(1); + assert(0); + } + } M_CATCH(test1, 0) { + // Nothing to do + } + + M_TRY(test1) { + M_LET(obj, TESTOBJ_OPLIST) { + testobj_set_ui(obj, 15); + M_LET(obj2, TESTOBJ_OPLIST) { + testobj_set_ui(obj2, 16); + M_THROW(1); + assert(0); + } + } + } M_CATCH(test1, 0) { + // Nothing to do + } + + M_TRY(test1) { + M_LET(obj, TESTOBJ_OPLIST) { + testobj_set_ui(obj, 15); + M_LET(obj2, TESTOBJ_OPLIST) { + testobj_set_ui(obj2, 16); + } + M_THROW(1); + assert(0); + } + } M_CATCH(test1, 1) { + // Nothing to do + } + + M_TRY(test1) { + M_LET(obj, TESTOBJ_OPLIST) { + testobj_set_ui(obj, 15); + M_TRY(test2) { + M_LET(obj2, TESTOBJ_OPLIST) { + testobj_set_ui(obj2, 16); + } + } M_CATCH(test2, 67) {} + M_THROW(1); + assert(0); + } + } M_CATCH(test1, 1) { + // Nothing to do + } + + M_TRY(test1) { + M_LET(obj, TESTOBJ_OPLIST) { + testobj_set_ui(obj, 15); + M_TRY(test2) { + M_LET(obj2, TESTOBJ_OPLIST) { + testobj_set_ui(obj2, 16); + } + M_THROW(1); + assert(0); + } M_CATCH(test2, 67) {} + } + } M_CATCH(test1, 1) { + // Nothing to do + } + + M_TRY(test1) { + M_LET(obj, TESTOBJ_OPLIST) { + testobj_set_ui(obj, 15); + M_TRY(test2) { + M_LET(obj2, TESTOBJ_OPLIST) { + testobj_set_ui(obj2, 16); + M_THROW(1); + assert(0); + } + } M_CATCH(test2, 1) { + assert(testobj_cmp_ui(obj, 15) == 0); + M_THROW(1); + assert(0); + } + } + } M_CATCH(test1, 1) { + // Nothing to do + } + + M_TRY(test1) { + M_LET(obj, TESTOBJ_OPLIST) { + testobj_set_ui(obj, 15); + M_TRY(test2) { + M_LET(obj2, TESTOBJ_OPLIST) { + testobj_set_ui(obj2, 16); + M_THROW(1); + assert(0); + } + } M_CATCH(test2, 1) { + // Nothing to do + assert(testobj_cmp_ui(obj, 15) == 0); + } + } + } M_CATCH(test1, 1) { + // Nothing to do + assert(0); + } + + M_TRY(test1) { + M_LET( (obj, (19)), TESTOBJ_OPLIST) { + M_TRY(test2) { + M_LET( (obj2, (16)), TESTOBJ_OPLIST) { + M_THROW(1); + assert(0); + } + } M_CATCH(test2, 1) { + // Nothing to do + assert(testobj_cmp_ui(obj, 19) == 0); + } + } + } M_CATCH(test1, 1) { + // Nothing to do + assert(0); + } +} + +int main(void) +{ + test1(); + test2(); + testobj_final_check(); + exit(0); +}