Bringing over latest (broken) copy of the library

This commit is contained in:
2011-07-16 23:05:16 -07:00
parent 04fb251fe8
commit 8cc4ee6b6a
5 changed files with 335 additions and 33 deletions

65
exception_test.c Normal file
View File

@@ -0,0 +1,65 @@
#include "exclib.h"
#include <stdio.h>
void nullfunc(int *something)
{
THROW_ZERO(something, EXC_NULLPOINTER, NULL);
printf("nullfunc got %d\n", *something);
}
void badfunc(void)
{
// This function represents a function that you think you can trust; but there's a null
// pointer being passed here that you have no control over. Thankfully 'nullfunc' was
// written to be safe, so it will illustrate the usefulness of TRY/CATCH/ETRY.
int *ptr = NULL;
nullfunc(ptr);
}
void testfunc(void)
{
THROW(34, "Random test exception");
}
int main(void)
{
int x = 2;
exclib_register_signals();
printf("Stack frames are %d bytes in size\n\n", sizeof(struct exc_status));
TRY {
fprintf(stdout, "Throwing %d\n", x);
THROW(2, "First exception");
} CATCH(-1) {
printf("Caught -1\n");
THROW(1, "1 Exception");
} CATCH(1) {
printf("Caught 1\n");
} CATCH(2) {
printf("Caught 2\n");
TRY {
testfunc();
} CATCH(34) {
printf("Caught 34!\n");
printf("Throwing 34 Back Out\n");
THROW(34, "Second-layer exception 34");
} ETRY;
} CATCH(34) {
printf("Caught 34 in upper level!\n");
printf("Using THROW_NONZERO\n");
THROW_NONZERO(strcmp("a", "b"), 0, "String Compare Exception");
} FINALLY {
printf("In finally clause.\n");
exclib_print_exception_stack(__FILE__, (char *)__func__, __LINE__);
} ETRY;
TRY {
badfunc();
} ETRY;
/*printf("\nThrowing an exception with no toplevel TRY block, which should stacktrace and kill us...\n\n");
THROW(34, "Bad Exception");*/
return 0;
}

18
exception_test_simple.c Normal file
View File

@@ -0,0 +1,18 @@
#include "exclib.h"
int main(void)
{
exclib_register_signals();
TRY {
exclib_print_exception_stack(__FILE__, (char *)__func__, __LINE__);
THROW(2, NULL);
} CATCH(2) {
exclib_print_exception_stack(__FILE__, (char *)__func__, __LINE__);
THROW(3, NULL);
} CATCH(3) {
} FINALLY {
exclib_print_exception_stack(__FILE__, (char *)__func__, __LINE__);
return 0;
} ETRY;
}

79
exceptions.c Normal file
View File

@@ -0,0 +1,79 @@
#include <stdio.h>
#include <setjmp.h>
struct exc_status {
struct exc_status *next;
struct exc_status *prev;
jmp_buf buf;
int last_exception;
int last_caught;
int tried;
};
struct exc_status __exc_status_head;
struct exc_status *EXC_STATUS_LIST = &__exc_status_head;
int new_exc_frame(struct exc_status *es)
{
if ( !es )
return 1;
EXC_STATUS_LIST->next = es;
es->next = NULL;
es->prev = EXC_STATUS_LIST;
es->last_exception = 0;
es->last_caught = 0;
es->tried = 0;
EXC_STATUS_LIST = es;
return 0;
}
int clear_exc_frame()
{
struct exc_status *es = EXC_STATUS_LIST;
if ( es->prev )
es->prev->next = NULL;
EXC_STATUS_LIST = es->prev;
es->last_exception = 0;
es->last_caught = 0;
es->tried = 0;
}
jmp_buf exc_buf;
int EXC_LAST_EXCEPTION = 0;
int EXC_LAST_CAUGHT = 0;
int EXC_TRIED = 0;
#define TRY struct exc_status es; new_exc_frame(&es); es.last_exception = sigsetjmp(es.buf, 1); switch( es.last_exception ) { case 0:
#define CATCH(x) break; case x: es.last_caught = x;
#define DEFAULT break; default: es.last_caught = 1;
#define ETRY }; clear_exc_frame();
#define FINALLY }; if ( es.last_caught || es.tried ) {
#define THROW(x) EXC_STATUS_LIST->last_exception = x; siglongjmp(EXC_STATUS_LIST->buf, x);
void testfunc(void)
{
printf("Throwing 34 from testfunc\n");
THROW(34);
}
int main(void)
{
TRY {
printf("Throwing 2\n");
THROW(2);
} CATCH(1) {
printf("Caught 1\n");
} CATCH(2) {
printf("Caught 2\n");
TRY {
testfunc();
} CATCH(34) {
printf("Caught 34!\n");
} ETRY
} DEFAULT {
printf("In default clause\n");
} FINALLY {
printf("In finally clause\n");
} ETRY
return 0;
}

133
exclib.c
View File

@@ -11,8 +11,54 @@
int __exc_curidx = 0; int __exc_curidx = 0;
int __exc_initstrings = 0; int __exc_initstrings = 0;
struct exc_status *EXC_STATUS_LIST = &__exc_statuses[0]; struct exc_status *EXC_STATUS_LIST = &__exc_statuses[0];
char *__exc_names[EXC_MAX_EXCEPTIONS]; char *__exc_names[EXC_MAX_EXCEPTIONS];
int __exc_signals[EXC_MAX_EXCEPTIONS];
struct exc_name_data __exclib_exc_names[EXC_PREDEFINED_EXCEPTIONS] = {
{EXC_NULLPOINTER, "Null Pointer", SIGSEGV},
{EXC_OUTOFBOUNDS, "Array Index Out of Bounds", SIGKILL}
};
void exclib_print_exception_stack(char *file, char *func, int line)
{
char buf[256];
char flagbuf[256];
struct exc_status *cur = EXC_STATUS_LIST;
int idx = 0;
fprintf(stderr,
"%s:%d:%s: Contents of the Exception Stack at this time:\n",
file,
line,
func);
if ( ! cur )
fprintf(stderr, "#0: No exception stack\n");
while ( cur != NULL ) {
memset((char *)&buf, 0, 256);
memset((char *)&flagbuf, 0, 256);
if ( cur->catching == 1)
strcat((char *)&flagbuf, "catching ");
if ( cur->caught == 1)
strcat((char *)&flagbuf, "caught ");
if ( cur->tried == 1)
strcat((char *)&flagbuf, "tried ");
sprintf((char *)&buf,
"#%d[0x%x] %s:%d:%s: %s (%d) ( %s): %s\n",
idx,
cur,
cur->file,
cur->line,
cur->function,
cur->name,
cur->value,
(char *)&flagbuf,
cur->description);
fprintf(stderr, "%s", (char *)&buf);
idx += 1;
cur = cur->prev;
}
}
void exclib_print_stacktrace(char *msg, char *file, char *func, int line) void exclib_print_stacktrace(char *msg, char *file, char *func, int line)
{ {
@@ -57,34 +103,104 @@ void exclib_print_exc_stacktrace (char *msg)
EXC_STATUS_LIST->description); EXC_STATUS_LIST->description);
strings = backtrace_symbols (EXC_STATUS_LIST->bt_frames, EXC_STATUS_LIST->bt_size); strings = backtrace_symbols (EXC_STATUS_LIST->bt_frames, EXC_STATUS_LIST->bt_size);
for (i = 0; i < EXC_STATUS_LIST->bt_size; i++) for (i = 0; i < EXC_STATUS_LIST->bt_size; i++) {
fprintf(stderr, "#%d: %s\n", i, strings[i]); fprintf(stderr, "#%d: %s\n", i, strings[i]);
}
free (strings); free (strings);
} }
void exclib_name_exception(int value, char *name) void __exclib_sigsegv_handler(int signal)
{
exclib_print_stacktrace("SIGSEGV - Segmentation Fault.", __FILE__, (char *)__func__, __LINE__);
#ifdef DEBUG_LOOPONSEGFAULT
fprintf(stderr, "Entering infinite loop; please connect a debugger to this process or kill it.\n");
fflush(stderr);
while ( 1 ) { };
#endif
exit(signal);
}
void exclib_register_signals(void)
{
signal(SIGSEGV, &__exclib_sigsegv_handler);
}
void exclib_bulk_name_exceptions(struct exc_name_data *exclib_exc_names, int size)
{
struct exc_name_data *ptr;
int i = 0;
exclib_init_strings();
//THROW_ZERO(exclib_exc_names, EXC_NULLPOINTER, "Null Pointer");
ptr = exclib_exc_names;
for ( i = 0; i < size ; i++) {
exclib_name_exception(ptr->exc,
ptr->name,
ptr->signal);
ptr += sizeof(struct exc_name_data);
}
}
void exclib_name_exception(int value, char *name, int signal)
{ {
exclib_init_strings(); exclib_init_strings();
__exc_names[value] = name; __exc_names[value] = name;
__exc_signals[value] = signal;
} }
void exclib_init_strings() void exclib_init_strings()
{ {
if ( __exc_initstrings = 1) // many functions call this one as a prerequisite to make sure the string table
// is safe, so we just check it here and return if we've already been called
if ( __exc_initstrings == 1)
return; return;
int i = 0; int i = 0;
// this takes some time but the security is worth it, also the ease of use // this takes some time but the security is worth it, also the ease of use
for ( i = 0; i < EXC_MAX_EXCEPTIONS; i++) { for ( i = 0; i < EXC_MAX_EXCEPTIONS; i++) {
__exc_names[i] = 0; __exc_names[i] = 0;
__exc_signals[i] = SIGKILL;
} }
// make sure all the exceptions we (the library) export are defined & named
__exc_initstrings = 1; __exc_initstrings = 1;
exclib_bulk_name_exceptions(&__exclib_exc_names[0], EXC_PREDEFINED_EXCEPTIONS);
} }
void exclib_prep_throw(int value, char *msg, char *file, char *func, int line)
{
char stbuf[256];
memset((char *)&stbuf, 0, 256);
if ( EXC_STATUS_LIST && EXC_STATUS_LIST->tried == 1) {
sprintf((char *)&stbuf, "Tried to THROW %d but couldn't create new exception frame", value);
if ( EXC_STATUS_LIST->catching == 1 && EXC_STATUS_LIST->prev ) {
if ( exclib_new_exc_frame(EXC_STATUS_LIST, file, func, line) )
exclib_print_stacktrace((char *)&stbuf, file, func, line);
memcpy(EXC_STATUS_LIST->buf, EXC_STATUS_LIST->prev->buf, sizeof(jmp_buf));
} else if ( EXC_STATUS_LIST->catching && (EXC_STATUS_LIST->prev == NULL) ) {
if ( exclib_new_exc_frame(&__exc_statuses[__exc_curidx++], file, func, line) )
exclib_print_stacktrace((char *)&stbuf, file, func, line);
exclib_clear_exc_frame();
__exc_curidx--;
} else {
if ( exclib_new_exc_frame(EXC_STATUS_LIST, file, func, line) )
exclib_print_stacktrace((char *)&stbuf, file, func, line);
}
EXC_STATUS_LIST->value = value;
EXC_STATUS_LIST->name = __exc_names[value];
EXC_STATUS_LIST->description = msg;
} else {
sprintf((char *)&stbuf, "Tried to THROW Exception %d but had no exception context. (Called outside of TRY block, or thrown while TRY was setting up?)", value);
exclib_print_stacktrace("", file, func, line);
kill(getpid(), __exc_signals[value]);
}
}
int exclib_new_exc_frame(struct exc_status *es, char *file, char *function, int line) int exclib_new_exc_frame(struct exc_status *es, char *file, char *function, int line)
{ {
int i = 0; int i = 0;
if ( !es ) if ( !es )
return 1; return 1;
exclib_init_strings();
if (EXC_STATUS_LIST && EXC_STATUS_LIST != es) { if (EXC_STATUS_LIST && EXC_STATUS_LIST != es) {
EXC_STATUS_LIST->next = es; EXC_STATUS_LIST->next = es;
es->prev = EXC_STATUS_LIST; es->prev = EXC_STATUS_LIST;
@@ -99,7 +215,8 @@ int exclib_new_exc_frame(struct exc_status *es, char *file, char *function, int
es->description = "No Description"; es->description = "No Description";
es->value = 0; es->value = 0;
es->caught = 0; es->caught = 0;
es->tried = 1; es->tried = 0;
es->catching = 0;
es->bt_size = backtrace(es->bt_frames, EXC_BT_FRAMES); es->bt_size = backtrace(es->bt_frames, EXC_BT_FRAMES);
EXC_STATUS_LIST = es; EXC_STATUS_LIST = es;
return 0; return 0;
@@ -122,8 +239,9 @@ int exclib_clear_exc_frame()
// we do kill here instead of just exit() to try and let any custom signal // we do kill here instead of just exit() to try and let any custom signal
// handlers clean things up for us // handlers clean things up for us
exclib_print_exc_stacktrace(NULL); exclib_print_exc_stacktrace(NULL);
kill(getpid(), SIGKILL); kill(getpid(), __exc_signals[es->value]);
} else if ( es->tried ) { //kill(getpid(), SIGKILL);
} else if ( es->tried && es->prev && (es->catching == 0)) {
// copy this exception up into the upper frame and siglongjmp back to that // copy this exception up into the upper frame and siglongjmp back to that
EXC_STATUS_LIST = es->prev; EXC_STATUS_LIST = es->prev;
EXC_STATUS_LIST->caught = 0; EXC_STATUS_LIST->caught = 0;
@@ -141,6 +259,7 @@ int exclib_clear_exc_frame()
es->value = 0; es->value = 0;
es->caught = 0; es->caught = 0;
es->tried = 0; es->tried = 0;
es->catching = 0;
} }
} }

View File

@@ -4,6 +4,7 @@
#include <setjmp.h> #include <setjmp.h>
#include <execinfo.h> #include <execinfo.h>
#include <signal.h> #include <signal.h>
#include <string.h>
#include <unistd.h> #include <unistd.h>
#include <sys/types.h> #include <sys/types.h>
@@ -18,11 +19,15 @@
* 6- Uncaught exceptions generate SIGKILL on the current process to help w/ graceful shutdown * 6- Uncaught exceptions generate SIGKILL on the current process to help w/ graceful shutdown
* 7- The current exception frame is always available as EXC_STATUS_LIST, and is of type (struct exc_status) * 7- The current exception frame is always available as EXC_STATUS_LIST, and is of type (struct exc_status)
* 8- All exceptions store a stacktrace at the time their TRY block is encountered, though it is not necessarily printed * 8- All exceptions store a stacktrace at the time their TRY block is encountered, though it is not necessarily printed
* 9- Each member of the exception stack is currently ~700 bytes, so beware of making EXC_BT_FRAMES too large (the default, 50, is already unimaginably deep and adds ~35kB to your memory usage automatically). Don't be afraid to make EXC_BT_FRAMES smaller; most programs won't need more than 10 or 15. * 9- Each member of the exception stack is currently ~700 bytes, so beware of making EXC_BT_FRAMES too large (the default, 50, is already unimaginably deep and adds ~35kB to your memory usage automatically). Don't be afraid to make EXC_BT_FRAMES smaller; most programs won't need more than 10 or 15, because this only tracks where TRY/THROW have been used, and each TRY/THROW pair use only one entry. Stacktraces aren't stored here, so this doesn't limit the possible size of a stacktrace.
* 10- The underlying mechanism behind this is sigsetjmp / siglongjmp, two "arcane and slightly dangerous" functions. Essentially this is used to treat your *entire* codebase as something we can GOTO between when there's an exception. I don't think it will, but if this does funny things to your code, I'm sorry. * 10- Because this library allows you to name your exceptions, you will automatically use an additional (1 * EXC_MAX_EXCEPTIONS) bytes of memory for the array of character pointers to store linkage to your exception strings, not counting whatever memory is used up by the actual string table for your strings. By default, EXC_MAX_EXCEPTIONS is set to 65535. You should probably leave it there, since you have no way of knowing how many exceptions a dependent library might use (and remember, compiler array bounds checking may not always save you here)... The 64k memory hit is, in this day and age, a pretty small price to pay for the verbosity provided.
* 11- You should limit your involvement with this library to the all-capital #defines. The functions themselves are undocumented and may change without warning. * 11- You get an additional (sizeof(int) * EXC_MAX_EXCEPTIONS) in memory usage from the table of signals to match exceptions. When an uncaught exception rises to the top, it generates a signal action. By default, that signal is SIGKILL, but you can override it in the call to exclib_name_exception.
* 12- I haven't tried it, but this should be safe for C++ as well. Beware of mangled symbols in tracebacks. (C++ already has exception handling, you shouldn't need this there, I'm just saying it should work.) * 12- The underlying mechanism behind this is sigsetjmp / siglongjmp, two "arcane and slightly dangerous" functions. Essentially this is used to treat your *entire* codebase as something we can GOTO between when there's an exception. I don't think it will, but if this does funny things to your code, I'm sorry.
* 13- It is impossible to THROW(0). A compile time error will occur complaining about duplicate case value. If by some miracle you do get it to compile, you will enter an infinite loop, as there is no exit path for this. * 13- You should limit your involvement with this library to the all-capital #defines. The functions themselves are undocumented and may change without warning, and they were never meant for human consumption anyways.
* 14- There are exceptions to every rule, and the exception to #11 is that you can safely call "exclib_print_stacktrace", and that you MUST call "exclib_name_exception" if you want your exceptions to have pretty names in tracebacks, and not just numbers.
* 15- I haven't tried it, but this should be safe for C++ as well. Beware of mangled symbols in tracebacks. (C++ already has exception handling, you shouldn't need this there, I'm just saying it should work.)
* 16- It is impossible to THROW(0). A compile time error will occur complaining about duplicate case value. If by some miracle you do get it to compile, you will enter an infinite loop, as there is no exit path for this.
* 17- These are done as defines for a reason; at the time of TRY/THROW, a lot of stack information is saved to show the user (via traceback) where the exception handling occured. And besides, sigsetjmp/siglongjmp won't work if we use functions for this instead of defines, because the call stack will be different.
* *
* Model usage: * Model usage:
* TRY { * TRY {
@@ -42,8 +47,9 @@
* first TRY() block encountered in the program. (Unhandled exceptions behave as in #5,6 in the bullet list above.) * first TRY() block encountered in the program. (Unhandled exceptions behave as in #5,6 in the bullet list above.)
* *
* The FINALLY clause is executed after the try block has executed, and after any applicable CATCH/DEFAULT blocks. If present, it will * The FINALLY clause is executed after the try block has executed, and after any applicable CATCH/DEFAULT blocks. If present, it will
* be executed REGARDLESS of whether an exception was actually caught. This is useful to examine an exception as it passes through * be executed REGARDLESS of which exception was actually caught. This is useful to examine an exception as it passes through
* without actually handling/modifying it. * without actually handling/modifying it, or to execute some generic action on exception, regardless of what type, but only after
* specific exceptions have been handled.
* *
* THROW_ZERO is a convenience function to replace blocks like this: * THROW_ZERO is a convenience function to replace blocks like this:
* *
@@ -93,23 +99,28 @@
#define TRY \ #define TRY \
if (__exc_curidx >= EXC_BT_FRAMES) \ if (__exc_curidx >= EXC_BT_FRAMES) \
exclib_print_stacktrace("No available exception stack context", __FILE__, (char *)__func__, __LINE__); \ exclib_print_stacktrace("No available exception stack context", __FILE__, (char *)__func__, __LINE__); \
exclib_new_exc_frame(&__exc_statuses[__exc_curidx++], __FILE__, (char *)__func__, __LINE__); \ if ( exclib_new_exc_frame(&__exc_statuses[__exc_curidx++], __FILE__, (char *)__func__, __LINE__) != 0) \
exclib_print_stacktrace("Tried to TRY but couldn't create new exception frame", __FILE__, (char *)__func__, __LINE__); \
EXC_STATUS_LIST->value = sigsetjmp(EXC_STATUS_LIST->buf, 1); \ EXC_STATUS_LIST->value = sigsetjmp(EXC_STATUS_LIST->buf, 1); \
EXC_STATUS_LIST->tried = 1;\
switch( EXC_STATUS_LIST->value ) { case 0: switch( EXC_STATUS_LIST->value ) { case 0:
#define CATCH(x) \ #define CATCH(x) \
break; \ break; \
case x: \ case x: \
EXC_STATUS_LIST->caught = 1; EXC_STATUS_LIST->caught = 1; \
EXC_STATUS_LIST->catching = 1;
#define CATCH_GROUP(x) \ #define CATCH_GROUP(x) \
case x: \ case x: \
EXC_STATUS_LIST->caught = 1; EXC_STATUS_LIST->caught = 1; \
EXC_STATUS_LIST->catching = 1;
#define DEFAULT \ #define DEFAULT \
break; \ break; \
default: \ default: \
EXC_STATUS_LIST->caught = 1; EXC_STATUS_LIST->caught = 1; \
EXC_STATUS_LIST->catching = 1;
#define ETRY \ #define ETRY \
}; \ }; \
@@ -118,22 +129,24 @@ __exc_curidx--;
#define FINALLY \ #define FINALLY \
}; \ }; \
if ( EXC_STATUS_LIST->caught || EXC_STATUS_LIST->tried ) { if ( EXC_STATUS_LIST->caught ) {
#define THROW_NONZERO(x, y, z) int rc = (x); if ( rc != 0 ) THROW(rc + y, z); #define THROW_NONZERO(x, y, z) int rc = (x); if ( rc != 0 ) THROW(rc + y, z);
#define THROW_ZERO(x, y, z) if ( !x ); THROW(y, z); #define THROW_ZERO(x, y, z) if ( (x) == 0 ) THROW(y, z);
#define THROW(x, y) \ #define THROW(x, y) exclib_prep_throw(x, y, __FILE__, (char *)__func__, __LINE__); if ( EXC_STATUS_LIST ) { siglongjmp(EXC_STATUS_LIST->buf, x); } else { exclib_print_stacktrace("Unhandled Exception "#x"", __FILE__, (char *)__func__, __LINE__); };
if ( EXC_STATUS_LIST ) { \
exclib_new_exc_frame(EXC_STATUS_LIST, __FILE__, (char *)__func__, __LINE__); \
EXC_STATUS_LIST->value = x; \ struct exc_name_data {
EXC_STATUS_LIST->name = __exc_names[x]; \ int exc;
EXC_STATUS_LIST->description = y; \ char *name;
siglongjmp(EXC_STATUS_LIST->buf, x); \ int signal;
} else { \ };
exclib_print_stacktrace("Tried to THROW Exception "#x" but had no exception context. (Called outside of TRY block?)", __FILE__, (char *)__func__, __LINE__); \
kill(getpid(), SIGKILL); \ #define EXC_NULLPOINTER 1
} #define EXC_OUTOFBOUNDS 2
#define EXC_PREDEFINED_EXCEPTIONS 2
struct exc_status { struct exc_status {
struct exc_status *next; struct exc_status *next;
@@ -142,6 +155,7 @@ struct exc_status {
int value; int value;
int caught; int caught;
int tried; int tried;
int catching;
char *file; char *file;
char *function; char *function;
int line; int line;
@@ -151,15 +165,22 @@ struct exc_status {
size_t bt_size; size_t bt_size;
}; };
extern struct exc_name_data __exclib_exc_names[EXC_PREDEFINED_EXCEPTIONS];
extern char *__exc_names[EXC_MAX_EXCEPTIONS]; extern char *__exc_names[EXC_MAX_EXCEPTIONS];
extern int __exc_signals[EXC_MAX_EXCEPTIONS];
extern int __exc_curidx; extern int __exc_curidx;
struct exc_status __exc_statuses[EXC_BT_FRAMES]; struct exc_status __exc_statuses[EXC_BT_FRAMES];
extern struct exc_status *EXC_STATUS_LIST; extern struct exc_status *EXC_STATUS_LIST;
extern void exclib_init_strings(); extern void exclib_init_strings();
extern void exclib_name_exception(int value, char *name); extern void __exclib_sigsegv_handler(int signal);
extern void exclib_register_signals(void);
extern void exclib_prep_throw(int value, char *msg, char *file, char *func, int line);
extern void exclib_name_exception(int value, char *name, int signal);
extern void exclib_bulk_name_exceptions(struct exc_name_data *exclib_exc_names, int size);
extern void exclib_print_stacktrace(char *msg, char *file, char *func, int line); extern void exclib_print_stacktrace(char *msg, char *file, char *func, int line);
extern void exclib_print_exc_stacktrace(char *msg); extern void exclib_print_exc_stacktrace(char *msg);
extern void exclib_print_exception_stack(char *file, char *func, int line);
extern int exclib_new_exc_frame(struct exc_status *es, char *file, char *function, int line); extern int exclib_new_exc_frame(struct exc_status *es, char *file, char *function, int line);
extern int exclib_clear_exc_frame(); extern int exclib_clear_exc_frame();