Update README

This commit is contained in:
2012-09-17 19:38:04 -03:00
parent 1c4ba1a814
commit 6f07834c77

125
README
View File

@@ -23,3 +23,128 @@ This project is released under the MIT license.
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.
Brief documentation
=====
This information used to be in exclib.h, so please forgive the ugly C comment.
/*
* These defines create a primitive sort of exception handling for ANSI C. Some things of note:
*
* 1- There is no dynamic memory allocation, ever, unless we print backtrace
* (in which case it's not our code doing it, it's execinfo)
* 2- We work in our own sort of exception context stack (__exclib_statuses)
* to do this; we can only ever have EXC_MAX_FRAMES number of frames tracked
* at any given time. This must be defined at compile time.
* 3- The benefit of the 2nd point is that we don't do malloc/free in our exception
* handling, and we can also keep from overwriting the current context within
* multiple nested TRY/ETRY blocks (and yes I've already tried, just scoping the
* operators {} does not help)
* 4- THROW() is smart enough to know when it is being used outside the context of
* a TRY block, and it will raise its own exception/traceback
* 5- Uncaught exceptions print a stacktrace; will have file names if debug is
* compiled and symbols aren't mangled, otherwise addr2line is your friend
* 6- Uncaught exceptions generate SIGKILL on the current process to help w/ graceful shutdown
* 7- The current exception frame is always available as EXCLIB_EXCEPTION, 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
* 9- Each member of the exception stack is currently ~700 bytes, so beware of making
* EXC_MAX_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_MAX_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- Because this library allows you to name your exceptions, you will automatically
* use an additional (sizeof(char *) * 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 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- The underlying mechanism behind this is setjmp / longjmp, 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- 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, setjmp/longjmp won't work if we use functions for this
* instead of defines, because the call stack will be different.
*
* Model usage:
*
* TRY {
* ... do something here ...
* THROW(int, "descriptive message")
* } CATCH(int) {
* ... do error handling here ...
* } DEFAULT {
* ... Do something with an exception here without caring about its value ...
* } FINALLY {
* ... Do something here ...
* } ETRY;
*
* The syntax is fairly obvious. Code inside the TRY block is executed, and any exceptions are propagated out to the CATCH blocks.
* All exceptions are integers, no exceptions (zing!). DEFAULT, if present, is executed when there is no CATCH block for the specific
* integer exception. If DEFAULT is not present and there is no CATCH block, then the exception is propagated back up the stack to the
* 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
* 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, 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:
*
* if ( !some_pointer )
* THROW(EXC_NULLPOINTER, "null pointer!");
*
* ... to become:
*
* THROW_ZERO(some_pointer, EXC_NULLPOINTER, "null pointer!");
*
* THROW_NONZERO is a wrapper you can use around libc functions that return nonzero on failure. The usage is fairly obvious:
*
* THROW_NONZERO(strcmp("one", "two"), 0, "not equal!");
*
* ... where the second argument is an integer which will be added to the return code of the expression, and can be used to base
* more complex exception codes around old -1/0/1 return code structures. For example you could have:
*
* #define EXC_CSTR_LESS 999
* #define EXC_CSTR_EQUAL 1000
* #define EXC_CSTR_GREATER 1001
*
* TRY {
* THROW_NONZERO(strcmp("one", "two"), EXC_CSTR_EQUAL, "not equal!");
* } CATCH (EXC_CSTR_LESS) {
* } CATCH_GROUP (EXC_CSTR_GREATER) {
* ...
* } CATCH (EXC_NULLPOINTER) {
* ...
* }
*
* This example also highlights one of the problems of this library : You can't check for multiple exceptions on the same block,
* because it's all actually a hidden 'switch' statement. But you can use a mechanism like shown above; when you have multiple
* conditions that could match the same situation, list the first one as CATCH, and the proceeding ones as CATCH_GROUP. CATCH_GROUP
* does not provide a break in the flow between the previous case and the current one, so fallthrough is achieved. Just make
* sure to use a CATCH on any proceeding exceptions that don't need to fall through (like the EXC_NULLPOINTER above).
*
*/