home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
Acorn User 10
/
AU_CD10.iso
/
Updates
/
DigitalCD
/
!DigitalCD
/
Copy
/
PowerBars
/
h
/
Exception
< prev
next >
Wrap
Text File
|
1998-11-30
|
9KB
|
253 lines
/*** exception.h ***/
/* C exception handling system
* (c) Paul Field
* v1.00 - 23/8/1995
* v1.01 - 1/9/1995 : Added some more debugging code
* v1.02 - 29/9/1995 : Added backtrace output on exception for debugging purposes
* v1.03 - 9/5/1996 : Catches SIGABRT (which stops assert() exiting the program)
*
* based on code by Martin Ebourne and Jos Horsmeier
* (see also David A. Spuler "C++ and C Debugging, Testing and Reliability",
* Prentice Hall, ISBN 0-13-308172-9)
*
* N.B. This module contains debugging code which will catch some of the
* mistakes you might make with this module. To disable this code (for the
* final version of your program), compile with the macro NDEBUG defined.
*
* This implementation is intended to:
* 1) keep things simple:
* a) macros are necessary for the implementation but this
* version only uses 3; it doesn't try to do clever things in the
* catch block (e.g. the EXCEPTION() and ALLEXCEPT macros in
* Horsmeier's code)
* b) error reporting is left to other program modules - this gives the user
* more control over error handling and reporting
* 2) keep 'normal' code fast: although in most cases the effect will be
* negligable, this implementation tries to make the code executed by
* an un-exceptional use of the 'try' block as fast as possible.
* 3) Provide support for three types of errors:
* os_errors (either thrown internally or by OSLib)
* signals (SIGABRT, SIGFPE, SIGILL, SIGINT, SIGSEGV, SIGTERM)
* user-defined (a user-defined code; useful when returning a message
* is not appropriate)
*
* How to use this module
* ~~~~~~~~~~~~~~~~~~~~~~
* Before doing anything else, call exception_initialise()
*
* To handle exceptions you use a try...catch...catch_end construct:
*
* (1) some code
*
* try
* { (2) try block (DO NOT 'return' or 'break' out of this block
* [or, less likely, 'goto', 'longjmp' or 'continue' out of it])
* }
* catch
* { (3) catch block
* }
* catch_end
*
* (4) more code
*
* Normally execution would go 1,2,4. However, if an exception occurs while 2
* is being executed (the exception can occur in functions called in 2) then
* the flow of control will be 1,some of 2,3,4. In other words the catch
* block contains error-handling code that will be called whenever an error
* (in the form of an exception) occurs in the try block.
*
* You can have many try...catch...catch_end constructs in your program and they
* can be nested (i.e. appear inside another try or catch block). Because of
* this, a common thing to do in a catch block is to partly handle the error
* (e.g. free memory, close files) and then 'throw' the exception so that
* a 'higher-up' catch block will catch the exception and act on it.
*
* Generating exceptions
* ~~~~~~~~~~~~~~~~~~~~~
* Exceptions can be generated by:
* 1) calling the throw() routines (see below)
* 2) calling a non-X SWI (e.g. using OSLib)
* 3) doing something which causes a signal
*
* Handling exceptions (the catch block)
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
* Often a catch block doesn't care what the exception was: it simply tidies
* up and then uses throw() to pass the exception on to a 'higher-up' catch
* block. However, many catch blocks need to know the nature of the exception;
* perhaps so they can generate an error message. Such a block can use
* exception_current(), perhaps like this:
*
* catch
* { const exception *e = exception_current();
* switch(e->type)
* { case exception_signal: signal_report(e->error.signal_id); break;
* case exception_os: os_report(e->error.os); break;
* case exception_user: user_report(e->error.user_id); break;
* }
* }
* catch_end
*
* This example is for exposition only. A real program is likely to have
* a general-purpose exception reporting function.
*
* If an exception occurs outside of a catch block then exit() will be called
* (so any atexit()-registered functions will be called - perhaps to try to
* emergency-save data). In this case, the error will go unreported so it's
* usually better to use a try...catch...catch_end around all the code in main()
*
*
* Things to watch out for
* ~~~~~~~~~~~~~~~~~~~~~~~
* Because try...catch...catch_end is not part of the language, the compiler
* cannot check the syntax. However, if you misuse it you will generally
* get some weird errors so, if an error doesn't make sense, check the
* try...catch...catch_end structure.
*
*
* Unless they are declared as 'volatile', automatic variables that are
* modified within the try block will have an undefined value after
* the try block if an exception occurs i.e.
*
* { int i = 1; // automatic variable
* try
* { i = 2; // modified in try block
* generate_exception();
* }
* catch
* { // i is undefined here
* }
* catch_end
* // i is undefined here
*
* Debugging
* ~~~~~~~~~
* If this code is compiled in debugging mode (i.e. the macro NDEBUG is *not* defined)
* then, on exception, the exception is output to stderr along with a stack dump.
* The exception is then processed as usual.
*/
#ifndef _exception_h
#define _exception_h
#include <setjmp.h>
#include <assert.h>
#include "kernel.h"
typedef enum
{
exception_signal,
exception_os,
exception_user
} exception_type;
typedef struct exception_str
{
exception_type type;
union
{
unsigned signal_id;
unsigned user_id;
const _kernel_oserror* os;
} error;
const char* file;
int line;
} exception;
void exception_initialise(void);
/* Sets up the exception system.
* Call before using any exception routines.
*/
const exception* exception_current(void);
/* Returns a description of the current exception.
* The value returned is undefined if this is used outside of a 'catch' block
*/
void __throw_user(unsigned id, char*, int);
/* Throws an exception of type exception_user.
*/
#define throw_user(x) __throw_user(x, __FILE__, __LINE__)
void __throw_os(const _kernel_oserror* err, char*, int);
/* Throws an exception of type exception_os.
* 'err' should not be stored on the stack.
*/
#define throw_os(x) __throw_os(x, __FILE__, __LINE__)
void __throw_last_os_error(char*, int);
/* Throws an exception of type exception_os.
*/
#define throw_last_os_error() __throw_last_os_error( __FILE__, __LINE__)
#pragma -v1 /* hint to the compiler to check f/s/printf format */
void __throw_string(char*, int, const char* pformat, ...);
#pragma -v0 /* return to default */
/* Throws an exception of type exception_os turning
* string into an os_error to do so.
*/
#define throw_string(x) __throw_string(__FILE__, __LINE__, x)
void throw(void);
/* Throws the current exception.
* (Only makes sense to call this within a 'catch' block)
*/
/*********************************************
* ABANDON HOPE ALL YE WHO ENTER HERE :-) *
* *
* Beyond this point are implementation *
* details which you do not need to be aware *
* of and should not rely on in any way *
* (because they could change in a future *
* release) *
*********************************************/
/* The exception system is currently implemented
* as a linked-list of jumpbufs on the stack.
*/
typedef struct _exception_block_str
{
struct _exception_block_str* previous;
jmp_buf jmpbuf;
} _exception_block;
extern _exception_block* _exception_list;
/* A macro that detects list corruption
* (probably caused by breaking or returning from a try block)
*/
#define escape_from_nested_try_using_return_or_break() (_exception_list != &_exception_current)
/* Basically, 'try' merely links a new _exception_block into the linked list
* and then sets up a jmpbuf so that 'throw' can use longjmp to get at the
* catch block.
*
* The instructions that link in the block could have been put in a function:
* this would save 1 or 2 ARM instructions (depending on how the compiler optimises
* the code) but would incur a function call overhead.
*/
#define try\
{ _exception_block _exception_current;\
_exception_current.previous = _exception_list;\
_exception_list = &_exception_current;\
if (!setjmp(_exception_current.jmpbuf))\
{
#define catch\
assert(!escape_from_nested_try_using_return_or_break());\
_exception_list = _exception_current.previous;\
}\
else\
{ /* Don't need to remove block from list - throw_xxx() will have\
* done this already\
*/
#define catch_end\
}\
}
#endif