chiark / gitweb /
@@@ fltfmt wip
[mLib] / utils / exc.h
1 /* -*-c-*-
2  *
3  * Structured exception handling in C
4  *
5  * (c) 1998 Straylight/Edgeware
6  */
7
8 /*----- Licensing notice --------------------------------------------------*
9  *
10  * This file is part of the mLib utilities library.
11  *
12  * mLib is free software; you can redistribute it and/or modify
13  * it under the terms of the GNU Library General Public License as
14  * published by the Free Software Foundation; either version 2 of the
15  * License, or (at your option) any later version.
16  *
17  * mLib is distributed in the hope that it will be useful,
18  * but WITHOUT ANY WARRANTY; without even the implied warranty of
19  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
20  * GNU Library General Public License for more details.
21  *
22  * You should have received a copy of the GNU Library General Public
23  * License along with mLib; if not, write to the Free
24  * Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
25  * MA 02111-1307, USA.
26  */
27
28 #ifndef MLIB_EXC_H
29 #define MLIB_EXC_H
30
31 #ifdef __cplusplus
32   extern "C" {
33 #endif
34
35 #include <setjmp.h>
36
37 #ifndef MLIB_MACROS_H
38 #  include "macros.h"
39 #endif
40
41 /*----- Quick documentation -----------------------------------------------*
42  *
43  * This header file provides some exception handling facilities in C
44  * programs.  It modifies the syntax of the language slightly, using the
45  * preprocessor.
46  *
47  * The `throw' expression returns no value.  It has the syntax:
48  *
49  *   THROW ( expr , expr )
50  *
51  * The first expression must have type compatible with unsigned integer; it
52  * identifies an `exception type'.  The second must have type compatible
53  * with pointer to void; it contains the `exception data'.  Control is
54  * passed to the current exception handler.
55  *
56  * The `RETHROW' expression, valid only within an exception handler, causes
57  * the current exception to be thrown again.
58  *
59  * A `try' statement has the syntax:
60  *
61  *   TRY stat CATCH stat END_TRY;
62  *
63  * The first statement is called the `test'; the second is the `handler'.
64  * During execution of the test, the handler is added to a stack of
65  * active exception handlers; the topmost handler on this stack is called
66  * the `current' handler.  When execution of the test completes, the
67  * corresponding handler is removed from the stack.
68  *
69  * The test statement may complete in one of these ways:
70  *
71  *   * Normal completion -- control reaches the end of the statement
72  *     normally.
73  *
74  *   * Throwing an exception -- an exception is thrown when the handler is
75  *     the current exception handler.
76  *
77  *   * By executing a `break' statement.
78  *
79  *   * By executing the expression `EXIT_TRY' and transferring control to
80  *     a point outside the entire `try' statement (e.g., executing a `goto'
81  *     or `return' statement).
82  *
83  * Any other attempt to leave the test causes undefined behaviour.
84  *
85  * If an exception is thrown while the handler is the current exception
86  * handler, it is given control.  The variables `exc_type' and `exc_val'
87  * denote the exception type and value respectively -- they are passed
88  * unchanged from the `throw' expression which caused the exception.
89  * A handler is deactivated before it is invoked; if it causes an
90  * exception to be thrown (and does not contain a nested `try' statement)
91  * control will be passed to an earlier active handler.
92  *
93  * Control is passed to handlers using the `longjmp' function.
94  *
95  * Example:
96  *
97  *   TRY {
98  *     ... something dangerous ...
99  *   } CATCH switch (exc_type) {
100  *     case EXC_INTERESTING:
101  *       ... handle exception ...
102  *       break;
103  *     default:
104  *       ... do tidying up ...
105  *       RETHROW;
106  *   } END_TRY;
107  */
108
109 /*----- Exception type allocation -----------------------------------------*
110  *
111  * Nobody allocates exception types, so we'll just have to try to get along
112  * without too many collisions.  An exception type is an unsigned long,
113  * which gives us four bytes.  The top two bytes identify the library which
114  * `owns' the exception, with special values zero meaning `defined as part
115  * of the system' and 0xFFFF providing a shared space of types which can
116  * be used by anyone as long as they don't get seen by anyone else.
117  *
118  * The lower byte pair encodes a type number, and a value which defines
119  * the type of the value field (see below).
120  */
121
122 /* --- Type of an exception --- */
123
124 typedef unsigned long exc_extype;
125
126 /* --- Build a byte pair from two characters --- *
127  *
128  * Note the icky casting to handle signed chars.
129  */
130
131 #define EXC_PAIR(x, y) (((unsigned long)(unsigned char)(x) << 8) |      \
132                         (unsigned long)(unsigned char)(y))
133
134 /* --- Allocate an exception number --- */
135
136 #define EXC_ALLOC(owner, type) (((unsigned long)(owner) << 16) |        \
137                                 (unsigned long)(type))
138
139 /* --- Special owner codes --- */
140
141 #define EXC_GLOBAL 0u                   /* The global space defined here */
142 #define EXC_SHARED 0xFFFFu              /* The shared space for everyone */
143 #define EXC_MLIB EXC_PAIR('m', 'L')     /* Space for mLib exceptions */
144
145 /*----- Exception values --------------------------------------------------*
146  *
147  * Exception values can have several different types.  This is a mess, and
148  * C doesn't handle it too well, but we can try.  I'll encode the value type
149  * as part of the exception type, in the top bits of the bottom byte.  Messy?
150  * You betcha.
151  */
152
153 /* --- Encoding a value type in an extype --- */
154
155 #define EXC_TYPECODE(t, w) (((w) & ~0xC0u) | ((t) & 0xC0u))
156
157 /* --- The various value types --- */
158
159 #define EXC_NOVAL 0x00u                 /* No interesting value */
160 #define EXC_INTVAL 0x40u                /* Integer value */
161 #define EXC_PTRVAL 0x80u                /* Arbitrary pointer value */
162 #define EXC_STRVAL 0xC0u                /* Pointer to character string */
163
164 /* --- Allocating exceptions with appropriate types --- */
165
166 #define EXC_ALLOCN(o, t) EXC_TYPECODE(EXC_NOVAL,  EXC_ALLOC(o, t))
167 #define EXC_ALLOCI(o, t) EXC_TYPECODE(EXC_INTVAL, EXC_ALLOC(o, t))
168 #define EXC_ALLOCP(o, t) EXC_TYPECODE(EXC_PTRVAL, EXC_ALLOC(o, t))
169 #define EXC_ALLOCS(o, t) EXC_TYPECODE(EXC_STRVAL, EXC_ALLOC(o, t))
170
171 /* --- A union representing the type --- */
172
173 typedef union exc_exval {
174   int i;
175   void *p;
176   char *s;
177 } exc_exval;
178
179 /*----- Predefined exceptions ---------------------------------------------*/
180
181 /* --- @EXC_NOMEM@ --- *
182  *
183  * Value:       ---
184  *
185  * Meaning:     An attempt to allocate memory failed.
186  */
187
188 #define EXC_NOMEM EXC_ALLOCN(EXC_GLOBAL, 0u)
189
190 /* --- @EXC_ERRNO@ --- *
191  *
192  * Value:       @int errno@ = the error raised
193  *
194  * Meaning:     Some kind of OS error occurred.
195  */
196
197 #define EXC_ERRNO EXC_ALLOCI(EXC_GLOBAL, 1u)
198
199 /* --- @EXC_OSERROR@ --- *
200  *
201  * Value:       @os_error *e@ = pointer to error block
202  *
203  * Meaning:     For RISC OS programmers only: alternative way of propagating
204  *              errors.
205  */
206
207 #define EXC_OSERROR EXC_ALLOCP(EXC_GLOBAL, 1u)
208
209 /* --- @EXC_SIGNAL@ --- *
210  *
211  * Value:       @int sig@ = signal number
212  *
213  * Meaning:     Report the raising of a signal.
214  */
215
216 #define EXC_SIGNAL EXC_ALLOCI(EXC_GLOBAL, 2u)
217
218 /* --- @EXC_FAIL@ --- *
219  *
220  * Value:       @const char *p@ = pointer to expanatory string
221  *
222  * Meaning:     Miscellaneous error.
223  */
224
225 #define EXC_FAIL EXC_ALLOCS(EXC_GLOBAL, 0xFFu)
226
227 /*----- An exception handler block ----------------------------------------*/
228
229 /* --- Try to think of this as being opaque --- */
230
231 typedef struct __exc_hnd {
232   struct __exc_hnd *next;               /* Pointer to next record down */
233   exc_extype type;                      /* Type of this exception */
234   exc_exval val;                        /* Value of this exception */
235   jmp_buf buf;                          /* Jump buffer when exceptions hit */
236 } __exc_hnd;
237
238 /*----- Global variables --------------------------------------------------*/
239
240 extern __exc_hnd *__exc_list;           /* List of active handlers */
241
242 /*----- Macros ------------------------------------------------------------*/
243
244 /* --- References to current exception type and value --- */
245
246 #define exc_type (__exc_ec.type)
247 #define exc_val (__exc_ec.val)
248 #define exc_i (__exc_ec.val.i)
249 #define exc_p (__exc_ec.val.p)
250 #define exc_s (__exc_ec.val.s)
251
252 /* --- How it actually works --- *
253  *
254  * A `try' block is contained within a block which provides an exception
255  * handler buffer in automatic storage.  This block is a loop, to allow
256  * `break' to escape from it.  It adds the handler buffer to the top of a
257  * list, and does a `setjmp' to allow a return here following an exception.
258  * The `setjmp' returns zero for the `try' section, and nonzero if there's
259  * an exception to `catch'.  It looks a little like this:
260  *
261  *   do {
262  *     __exc_hnd h;
263  *     add_handler(&h);
264  *     if (!setjmp(h.buf)) {
265  *       do <try code> while (0);
266  *       remove_handler(&h);
267  *     } else
268  *       <catch code>
269  *   } while (0)
270  *
271  * Everything else is ugly hacking to make things work.
272  */
273
274 /* --- Trying things which may cause exceptions --- */
275
276 #define TRY do {                                                        \
277   volatile __exc_hnd __exc_ec;                                          \
278   __exc_ec.next = __exc_list;                                           \
279   __exc_list = (__exc_hnd *)&__exc_ec;                                  \
280   if (!setjmp(*(jmp_buf *)&__exc_ec.buf /* very nasty! */ )) { do
281
282 #define EXIT_TRY do __exc_list = __exc_ec.next; while (0)
283 #define CATCH while (0); EXIT_TRY; } else
284
285 #define END_TRY } while (0)
286
287 /* --- Raising exceptions --- */
288
289 #define THROW __exc_throw
290 #define RETHROW __exc_rethrow(__exc_ec.type, __exc_ec.val)
291
292 /*----- Functions ---------------------------------------------------------*/
293
294 /* --- @exc_uncaught@ --- *
295  *
296  * Arguments:   @void (*proc)(exc_extype type, exc_exval val) = new handler
297  *
298  * Returns:     Pointer to the old handler value.
299  *
300  * Use:         Sets the handler for uncaught exceptions.
301  */
302
303 typedef void (*exc__uncaught)(exc_extype /*type*/, exc_exval /*val*/);
304 extern exc__uncaught exc_uncaught(exc__uncaught /*proc*/);
305
306 /* --- @__exc_throw@ --- *
307  *
308  * Arguments:   @exc_extype type@ = type of exception to throw
309  *
310  * Returns:     Doesn't
311  *
312  * Use:         NOT FOR USER CONSUMPTION.  Reads an appropriate exception
313  *              value and throws an exception.
314  */
315
316 extern NORETURN void __exc_throw(exc_extype /*type*/, ...);
317
318 /* --- @__exc_rethrow@ --- *
319  *
320  * Arguments:   @exc_extype type@ = type of exception to throw
321  *              @exc_exval val@ = value of exception to throw
322  *
323  * Returns:     Doesn't
324  *
325  * Use:         NOT FOR USER CONSUMPTION.  Does the donkey-work of raising
326  *              an exception.
327  */
328
329 extern NORETURN void __exc_rethrow(exc_extype /*type*/, exc_exval /*val*/);
330
331 /*----- That's all, folks -------------------------------------------------*/
332
333 #ifdef __cplusplus
334   }
335 #endif
336
337 #endif