2012-09-17 19:28:01 -03:00
|
|
|
License
|
|
|
|
|
=======
|
|
|
|
|
|
|
|
|
|
This project is released under the MIT license.
|
|
|
|
|
|
|
|
|
|
Copyright (c) 2012 Andrew Kesterson <andrew@aklabs.net>
|
|
|
|
|
|
|
|
|
|
Permission is hereby granted, free of charge, to any person obtaining a
|
|
|
|
|
copy of this software and associated documentation files (the "Software"),
|
|
|
|
|
to deal in the Software without restriction, including without limitation
|
|
|
|
|
the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
|
|
|
|
and/or sell copies of the Software, and to permit persons to whom the
|
|
|
|
|
Software is furnished to do so, subject to the following conditions:
|
|
|
|
|
|
|
|
|
|
The above copyright notice and this permission notice shall be included
|
|
|
|
|
in all copies or substantial portions of the Software.
|
|
|
|
|
|
|
|
|
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
|
|
|
|
OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
|
|
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
|
|
|
|
THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
|
|
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
|
|
|
|
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
|
|
|
|
DEALINGS IN THE SOFTWARE.
|
|
|
|
|
|
2012-09-17 19:38:04 -03:00
|
|
|
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).
|
|
|
|
|
*
|
|
|
|
|
*/
|
|
|
|
|
|