it is used to control the inline semantics. By combining it with optimize option "-fno-inline" for the internal M*LIB function (using _Pragma optimize) we can control easily if we want to use an inline version or call of an external function. Define new user API: * M_USE_EXTERN_DECL : define it before including any MLIB header to requests to call the M*LIB functions and don't inline then. * M_USE_EXTERN_DEF : define it once in a translation unit to define the M*LIB functions. This translation unit shall contain all templated macro too. If you use one option, you need to use the other one too for another translation unit.
577 lines
23 KiB
C++
577 lines
23 KiB
C++
/*
|
|
* 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 "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_CONTEXT, \
|
|
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 context data that are stored in an exception.
|
|
*/
|
|
#ifndef M_USE_MAX_CONTEXT
|
|
#define M_USE_MAX_CONTEXT 10
|
|
#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 num; // Number of entries in 'context' table
|
|
const char *filename; // filename where the error was detected
|
|
intptr_t context[M_USE_MAX_CONTEXT]; // Specific context of the exception
|
|
#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() )
|
|
|
|
// M_DEFER Injection / pre initialization
|
|
#define M_DEFER_TRY_INJECT_PRE_B(cont, ...) /* Nothing to do */
|
|
|
|
// M_DEFER Injection / post initialization
|
|
// Register the stack frame and tests for the longjmp.
|
|
// In which case call the 'clear' operations (...), unstack the error list and rethrow the error.
|
|
#define M_DEFER_TRY_INJECT_POST_B(cont, ...) \
|
|
for(m_lib::m_regclear M_C(m_try_regclear_, cont){[&](void) { __VA_ARGS__; } } \
|
|
; cont ; M_C(m_try_regclear_, cont).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
|
|
|
|
#if (M_USE_TRY_MECHANISM == 3)
|
|
// Use of builtin setjmp / longjmp for GCC
|
|
// There are at least twice faster at worst, and reduce stack consumption
|
|
// See https://gcc.gnu.org/onlinedocs/gcc/Nonlocal-Gotos.html
|
|
// CLANG doesn't support these builtins officialy (https://groups.google.com/g/llvm-dev/c/9QgfdW23K8M)
|
|
#define m_try_setjmp(x) __builtin_setjmp(x)
|
|
#define m_try_longjmp(x,v) __builtin_longjmp(x, v)
|
|
typedef intptr_t m_try_jmp_buf[5];
|
|
#define m_try_jmp_buf m_try_jmp_buf
|
|
#else
|
|
// C compliant setjmp
|
|
#include <setjmp.h>
|
|
#define m_try_setjmp(x) setjmp(x)
|
|
#define m_try_longjmp(x,v) longjmp(x, v)
|
|
#define m_try_jmp_buf jmp_buf
|
|
#endif
|
|
|
|
// 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))
|
|
|
|
// 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 {
|
|
m_try_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__ } } )
|
|
|
|
// Copy an exception to another.
|
|
M_INLINE void
|
|
m_exception_set(struct m_exception_s *out, const struct m_exception_s *in)
|
|
{
|
|
if (in != out) {
|
|
memcpy(out, in, sizeof *out);
|
|
}
|
|
}
|
|
|
|
// The global thread attribute variables and functions.
|
|
extern M_THREAD_ATTR struct m_try_s *m_global_error_list;
|
|
extern M_THREAD_ATTR struct m_exception_s m_global_exception;
|
|
extern M_ATTR_NO_RETURN M_ATTR_COLD_FUNCTION void m_throw(const struct m_exception_s *exception);
|
|
|
|
// 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; \
|
|
\
|
|
/* Throw the given exception \
|
|
This function should be rarely called. */ \
|
|
M_ATTR_NO_RETURN M_ATTR_COLD_FUNCTION 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; \
|
|
m_try_longjmp(e->data.buf, 1); \
|
|
} \
|
|
/* Next stack frame */ \
|
|
e = e->next; \
|
|
} \
|
|
/* No exception found. \
|
|
Display the information and halt program . */ \
|
|
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
|
|
M_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).
|
|
M_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;
|
|
}
|
|
|
|
// Initialize the state to a TRY state.
|
|
M_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), m_try_setjmp(((s)->data.buf)) != 1))
|
|
|
|
// Disable the current TRY block.
|
|
M_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
|
|
// Helper functions
|
|
// Each mechanisme provide 3 helper functions:
|
|
// * pre: which is called before the constructor
|
|
// * post: which is called after the constructor
|
|
// * final: which is called before the destructor.
|
|
|
|
// 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.
|
|
M_INLINE bool
|
|
m_try_cb_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.
|
|
M_INLINE bool
|
|
m_try_cb_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 will be cleared.
|
|
// We can pop the stack frame of the errors.
|
|
M_INLINE void
|
|
m_try_cb_final(m_try_t state)
|
|
{
|
|
m_global_error_list = state->next;
|
|
}
|
|
|
|
// Pre initialization function. Save the stack frame for a longjmp
|
|
M_INLINE bool
|
|
m_try_jump_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
|
|
M_INLINE void
|
|
m_try_jump_post(m_try_t state)
|
|
{
|
|
m_global_error_list = state;
|
|
}
|
|
// And call setjmp to register the position in the code.
|
|
#define m_try_jump_post(s) \
|
|
M_LIKELY ((m_try_jump_post(s), m_try_setjmp(((s)->data.buf)) != 1))
|
|
|
|
// The object will be cleared.
|
|
// We can pop the stack frame of the errors.
|
|
M_INLINE void
|
|
m_try_jump_final(m_try_t state)
|
|
{
|
|
m_global_error_list = state->next;
|
|
}
|
|
|
|
|
|
// 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
|
|
// Use of CLANG blocks
|
|
|
|
#define M_LET_TRY_INJECT_PRE_B(cont, oplist, name) \
|
|
for(m_try_t M_C(m_try_state_, name); cont && \
|
|
m_try_cb_pre(M_C(m_try_state_, name) ); )
|
|
|
|
#define M_LET_TRY_INJECT_POST_B(cont, oplist, name) \
|
|
for(m_try_cb_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_cb_final(M_C(m_try_state_, name)) )
|
|
|
|
#elif M_USE_TRY_MECHANISM == 3
|
|
// Use of GCC nested functions.
|
|
|
|
#define M_LET_TRY_INJECT_PRE_B(cont, oplist, name) \
|
|
for(m_try_t M_C(m_try_state_, name); cont && \
|
|
m_try_cb_pre(M_C(m_try_state_, name) ); )
|
|
|
|
#define M_LET_TRY_INJECT_POST_B(cont, oplist, name) \
|
|
for(m_try_cb_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_cb_final(M_C(m_try_state_, name)) )
|
|
|
|
#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.
|
|
|
|
// 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_jump_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_jump_final(M_C(m_try_state_, name))) \
|
|
if (m_try_jump_post(M_C(m_try_state_, name)) \
|
|
|| (M_CALL_CLEAR(oplist, name), m_try_jump_final(M_C(m_try_state_, name)), m_rethrow(), false))
|
|
|
|
#else
|
|
# error M*LIB: Invalid value for M_USE_TRY_MECHANISM [1..4]
|
|
#endif
|
|
|
|
|
|
// M_DEFER Injection / pre initialization
|
|
// Initialize the stack frame.
|
|
#define M_DEFER_TRY_INJECT_PRE_B(cont, ...) \
|
|
for(m_try_t M_C(m_try_state_, cont); cont && \
|
|
m_try_jump_pre(M_C(m_try_state_, cont)); )
|
|
|
|
// M_DEFER 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_DEFER_TRY_INJECT_POST_B(cont, ...) \
|
|
for( ; cont ; m_try_jump_final(M_C(m_try_state_, cont))) \
|
|
if (m_try_jump_post(M_C(m_try_state_, cont)) \
|
|
|| (__VA_ARGS__ , m_try_jump_final(M_C(m_try_state_, cont)), m_rethrow(), false))
|
|
|
|
#endif /* cplusplus */
|
|
|
|
/*****************************************************************************/
|
|
|
|
// 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)
|
|
|
|
|
|
// Macro injection for M_DEFER.
|
|
#undef M_DEFER_TRY_INJECT_PRE
|
|
#define M_DEFER_TRY_INJECT_PRE(cont, ...) M_DEFER_TRY_INJECT_PRE_B(cont, __VA_ARGS__)
|
|
#undef M_DEFER_TRY_INJECT_POST
|
|
#define M_DEFER_TRY_INJECT_POST(cont, ...) M_DEFER_TRY_INJECT_POST_B(cont, __VA_ARGS__)
|
|
|
|
|
|
// 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
|
|
|