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.
This commit is contained in:
Patrick Pelissier committed 2023-03-13 23:31:50 +01:00
1 parent 94b6d1b9fd
commit bfa948dc7a
2 files changed
+886

No files matched your search

+519
View File
@@ -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 <setjmp.h>
#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 <unsigned int N>
#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<N> *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<error_code> &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<error_code>
#define M_THROW_1(error_code) \
throw m_lib::m_exception_s<error_code>{ error_code, __LINE__, 0, __FILE__, { 0 } }
// Reuse the throw keyword of the C++
// by throwing the type m_lib::m_exception_s<error_code>
#define M_THROW_N(error_code, ...) \
throw m_lib::m_exception_s<error_code>{ 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 <functional>
namespace m_lib {
class m_regclear {
std::function<void(void)> function;
bool done;
public:
inline m_regclear(const std::function<void(void)> &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
+367
View File
@@ -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 <stdio.h>
#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);
}