Skip to content

Instantly share code, notes, and snippets.

@kulp
Created November 8, 2011 15:31
Show Gist options
  • Save kulp/1348052 to your computer and use it in GitHub Desktop.
Save kulp/1348052 to your computer and use it in GitHub Desktop.
A toy exception handling system for C
sauf is a lightweight exception handling system for C.
The original implementation idea came from an article in Dr.
Dobb's Journal : http://www.on-time.com/ddj0011.htm
Some implementation details were taken from the excc project :
http://adomas.org/excc/ . This project was almost sufficient to my
requirements, but it requires GCC (sauf aims to work with any C99-
compliant compiler) and does not support 'finally' blocks.
The design goals of the sauf project are portability, robustness
(absolutely correct behavior) and lightweightness. Portability in this
context means that sauf will strive to depend upon no behavior that is
not guaranteed by the C99 standard. Correct behavior is a less easily
proven goal; the gradual growth of the test suite attemps to address
this. The lightweightness of the project (no dynamically-allocated
memory, tiny library size) strives to make it suitable for every
application, including, for example, embedded systems.
The code is admittedly ugly and difficult to read, due to its heavy
reliance upon macros and infrequently-used "features" of the C standard.
Over time it is hoped that functionality will be moved into (inline)
functions where possible in order to improve clarity and modularity.
The name 'sauf' comes from the French word meaning 'except' or
'safe'. Both of these meanings are appropriate in the context of
exception handling. An approximate pronunciation for English speakers is
"sohf" with a long O sound.
Darren Kulp
2009-10-02
/**
* @file
* A lightweight exception-handling system for C.
*/
#include "sauf.h"
#include <stdarg.h>
#include <stdio.h>
/// @todo rename __trace to something sauf-specific
#if !defined(__trace) && SAUF_TRACE
#define __trace(...) _sauf__trace(__func__, __VA_ARGS__)
#else
#define __trace(...)
#endif
/// @todo handle thread-local storage generically
/*__thread*/ volatile struct _sauf_link * volatile _sauf_curr,
* volatile _sauf_old;
/// @todo rewrite to show where I am in the call tree
void _sauf__trace(const char *func, const char *fmt, ...)
{
printf("%s:", func);
va_list vl;
va_start(vl, fmt);
vprintf(fmt, vl);
va_end(vl);
putchar('\n');
}
void _sauf_rethrower(volatile struct _sauf_state *_sauf_state)
{
/// @todo test this code path
if (_sauf_state->rc == SAUF_EX_BROKEN)
return;
_sauf_curr = _sauf_state->saved;
__trace("__otherwise");
if (_sauf_curr) {
__trace("rethrowing: %d", _sauf_state->rc);
_sauf_state->rethrow = _sauf_state->rc;
_sauf_longjmp(_sauf_state->jb, SAUF_EX_FINALLY);
} else {
__trace("unhandled: %d", _sauf_state->rc);
_sauf_longjmp(_sauf_state->jb, SAUF_EX_FINALLY);
}
}
void _sauf_unwind(volatile struct _sauf_state *_sauf_state)
{
__trace("popping exception frame");
_sauf_curr = _sauf_state->saved;
if (_sauf_curr) {
__trace("[not at top frame]");
_sauf_state->jb = &_sauf_curr->curr;
}
}
struct _sauf_state _sauf_setup_struct(const char *file, const char *func, long line, ...)
{
return (struct _sauf_state) {
.saved = _sauf_old,
.jb = &_sauf_curr->curr,
.state = 0,
.frame = {
.next = _sauf_old,
/// @todo make these fields optional to save space
.func = func,
.file = file,
.line = line,
},
};
}
void _sauf_teardown(volatile struct _sauf_state *_sauf_state)
{
_sauf_state->state = -1;
_sauf_curr = _sauf_state->saved;
_sauf_state->jb = _sauf_curr ? &_sauf_curr->curr : _sauf_state->jb;
if (_sauf_state->rethrow)
_sauf_longjmp(_sauf_state->jb, _sauf_state->rethrow);
}
void _sauf_longjmp(volatile jmp_buf *px, int val)
{
if (px)
longjmp(*(jmp_buf*)px, val);
else
// if there is no enclosing scope to catch me, abort()
/// @todo change abort() to a user-specifiable function macro
abort();
}
/* vim:set et ts=4 sw=4 syntax=c.doxygen: */
/**
* @file
* A lightweight exception-handling system for C.
*/
#ifndef SAUF_H_
#define SAUF_H_
#include <setjmp.h>
// for abort()
#include <stdlib.h>
//------------------------------------------------------------------------------
// Public "API"
//------------------------------------------------------------------------------
#define __except \
for (volatile struct _sauf_state _sauf_state = (_sauf_old = _sauf_curr, \
_sauf_curr = &_sauf_state.frame, \
_sauf_setup_struct(__FILE__, __func__, __LINE__)); \
_sauf_state.state != -1; \
_sauf_teardown(&_sauf_state)) \
switch ((_sauf_state.rc = _sauf_setjmp(_sauf_state.jb)))
#define __catch(X) \
_sauf_longjmp(_sauf_state.jb, SAUF_EX_FINALLY); break; \
case X: _sauf_catch;
#define __finally \
_sauf_longjmp(_sauf_state.jb, SAUF_EX_FINALLY); break; \
default: _sauf_rethrower(&_sauf_state); break; \
case SAUF_EX_FINALLY: _sauf_unwind(&_sauf_state);
#define __first case SAUF_EX_FIRST:
#define __throw(X) _sauf_longjmp(_sauf_state.jb, X)
//------------------------------------------------------------------------------
// Private "API"
//------------------------------------------------------------------------------
// if SAUF_EXCEPTIONS is defined as a comma-separated list of enum values, it
// will be substituted here ; otherwise, it's just an unused enum value
enum exceptions {
SAUF_EX_BROKEN=-2,
SAUF_EX_FINALLY=-1,
SAUF_EX_FIRST=0,
SAUF_EXCEPTIONS
};
struct _sauf_link {
volatile jmp_buf curr;
const char *file; ///< throw location (file)
const char *func; ///< throw location (function)
long line; ///< throw location (line)
/// @todo remove this if we are not going to use it anywhere
volatile struct _sauf_link * volatile next;
};
/// @todo remove volatiles where possible
struct _sauf_state {
/// the result of setjmp()
volatile int rc;
/// an exception to rethrow after __finally, if non-zero
volatile int rethrow;
/// controls for()-loop subversion
volatile int state;
/// saved state
volatile struct _sauf_link * volatile saved;
/// temporary link story
volatile struct _sauf_link frame;
/// used to subvert struct initialization to run imperative code
volatile void *scratch;
/// pointer to the parent jump-buffer to longjmp() to
volatile jmp_buf * volatile jb;
};
#define _sauf_setjmp(px) ((px) ? setjmp(*((jmp_buf*)px)) : SAUF_EX_BROKEN)
#define _sauf_catch _sauf_curr = _sauf_state.saved
extern volatile struct _sauf_link * volatile _sauf_curr, * volatile _sauf_old;
void _sauf_longjmp(volatile jmp_buf *px, int val);
void _sauf_rethrower(volatile struct _sauf_state *_sauf_state);
void _sauf_unwind(volatile struct _sauf_state *_sauf_state);
// intentionally declared as varargs so we can call it how we want for
// side-effects
struct _sauf_state _sauf_setup_struct(const char *file, const char *func, long line, ...);
void _sauf_teardown(volatile struct _sauf_state *_sauf_state);
void _sauf__trace(const char *func, const char *fmt, ...);
#endif /* SAUF_H_ */
/* vim:set et ts=4 sw=4 syntax=c.doxygen: */
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment