1: /* exception.c --- source implementing lightweight, but featured
  2:                    exception handling.

  4: Time-stamp: "2005-04-10 17:06:35 admp"

  6: Copyright (C) 2003-5 Adomas P. <adomas.paltanavicius@gmail.com>

  8: This program is free software; you can redistribute it and/or modify
  9: it under the terms of the GNU General Public License as published by
 10: the Free Software Foundation; either version 2 of the License, or
 11: (at your option) any later version.

 13: This program is distributed in the hope that it will be useful,
 14: but WITHOUT ANY WARRANTY; without even the implied warranty of
 15: MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 16: GNU General Public License for more details.

 18: You should have received a copy of the GNU General Public License
 19: along with this program; if not, write to the Free Software
 20: Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA. */

 22: /* This code relies on few customization macros, and thus
 23:    it must be recompiled once those are changed.
 24:  */

 26: #include "exception-local.h"


 29: /* Variable declarations at first. */
 30:   
 31: /* This counter is used by __EXC_BLOCK.  It works well even if nested. */
 32: volatile int __exc_block_pass;

 34: /* Flag to be set by ON? */
 35: volatile int __exc_handled;

 37: /* For indexing every call to TRY. */
 38: volatile unsigned __exc_tries;

 40: /* These identify the thrown exception.  File, function, line and
 41:    the exception itself. */
 42: char *__exc_file;
 43: char *__exc_function;
 44: unsigned __exc_line;
 45: volatile __EXC_TYPE __exc_code;

 47: /* This is the global stack of catchers. */
 48: struct __exc_stack *__exc_global;



 52: #ifdef __EXC_DEBUG

 54: /* Prints error message. */
 55: void
 56: __exc_debug (char *fmt, ...)
 57: {
 58:   va_list ap;

 60:   fprintf (__EXC_STREAM, "__EXC: ");
 61:   va_start (ap, fmt);
 62:   vfprintf (__EXC_STREAM, fmt, ap);
 63:   va_end (ap);
 64:   fprintf (__EXC_STREAM, "\n");
 65: }



 69: /* For printing __exc_global. */
 70: void
 71: __exc_print_global ()
 72: {
 73:   struct __exc_stack *level = __exc_global;
 74:   unsigned items;

 76:   if (level == NULL)
 77:     {
 78:       fprintf (__EXC_STREAM, "Stack empty\n");
 79:       return;
 80:     }

 82:   fprintf (__EXC_STREAM, "Current stack (from bottom to top):\n");
 83:   for (items = 0; level; level = level->prev)
 84:     {
 85:       fprintf (__EXC_STREAM, "%c ", items == 0 ? '[' : ' ');

 87:       /* FIXME: does printing jmp_buf literally (as we now address and
 88:          its size) good?  No, unless human understands it.  One
 89:          can't unless he chooses one popular C library and study the
 90:          implementation carefully;) */
 91:       fprintf (__EXC_STREAM, "%u", level->num);
 92:       /* TODO: newline every four or so items.  Use tab stops. */
 93:       fprintf (__EXC_STREAM, " %c\n", level->prev ? ' ' : ']');  
 94:       items++;
 95:     }

 97:   fprintf (__EXC_STREAM, "Totally %u items.\n", items);
 98: }

100: #endif



104: /* Prints information about exception.  Called in debug mode, or when
105:    no handler is found. */

107: void
108: __exc_print (FILE *stream, char *file, char *function, unsigned line,
109:              __EXC_TYPE code)
110: {
111:   fprintf (stream, "Exception in file \"%s\", at line %u",
112:            file, line);
113:   if (function)
114:     {
115:       fprintf (stream, ", in function \"%s\"", function);
116:     }
117:   fprintf (stream, ".");

119: #ifdef __EXC_PRINT
120:   fprintf (stream, " Exception: ");
121:   __EXC_PRINT (code, stream);
122: #endif
123:   fprintf (stream, "\n");
124: }



128: /* Pop exception from stack, putting into J (if nonzero).  If stack is
129:    empty, print error message and exit.  Used in EXCEPT. */
130: void
131: __exc_pop (jmp_buf *j)
132: {
133:   struct __exc_stack *stored = __exc_global;

135:   __exc_debug ("POP () to %p", j);

137:   if (stored == NULL)
138:     {
139:       __exc_debug ("Unhandled exception.");

141:       fprintf (stderr, "Unhandled exception:\n");
142:       __exc_print (stderr, __exc_file, __exc_function,
143:                    __exc_line, __exc_code);

145:       exit (3);
146:     }

148:   __exc_global = stored->prev;

150:   if (j)
151:     {
152:       /* This assumes that JMP_BUF is a structure etc. and can be
153:          copied rawely.  This is true in GLIBC, as I know. */
154:       memcpy (j, &stored->j, sizeof (jmp_buf));
155:     }

157:   __exc_debug ("Popped");
158:   __exc_print_global ();

160:   /* While with MALLOC, free.  When using obstacks it is better not to
161:      free and hold up. */
162:   free (stored);
163: }



167: /* Push J onto the stack, with RETURNED as value from SETJMP.  Return
168:    nonzero, if RETURNED is 0.  If RETURNED is nonzero, returns 0.
169:    Used in TRY. */

171: int
172: __exc_push (jmp_buf *j, int returned)
173: {
174:   struct __exc_stack *new;

176:   __exc_debug ("PUSH (), %p, %d", j, returned);

178:   /* SETJMP returns 0 first time, nonzero from __EXC_THROW.
179:      Returning false-like value here (0) will enter the
180:      else branch (that is, EXCEPT.) */
181:   if (returned != 0)
182:     {
183:       __exc_debug ("Returning from THROW");
184:       return 0;
185:     }

187:   /* Since this didn't come from THROW, fine to increase counter. */
188:   ++__exc_tries;
189:   __exc_debug ("This is PUSH () number %u", __exc_tries);

191:   /* Using memcpy here is the best alternative. */
192:   new = malloc (sizeof (struct __exc_stack));
193:   memcpy (&new->j, j, sizeof (jmp_buf));
194:   new->num = __exc_tries;
195:   new->prev = __exc_global;
196:   __exc_global = new;

198:   __exc_print_global ();

200:   return 1;
201: }



205: /* Throw an exception in FILE at LINE, with code CODE.  Used in THROW. */

207: void
208: __exc_throw (char *file, char *function, unsigned line, __EXC_TYPE code)
209: {
210:   jmp_buf j;

212:   __exc_debug ("THROW ()");
213: #if defined __EXC_DEBUG
214:   __exc_print (__EXC_STREAM, file, function, line, code);
215: #endif

217:   __exc_file = file;
218:   __exc_function = function;
219:   __exc_line = line;
220:   __exc_code = code;

222:   /* Pop for jumping. */
223:   __exc_pop (&j);
224:   
225:   __exc_debug ("Jumping to the handler");

227:   /* LONGJUMP to J with nonzero value. */
228:   longjmp (j, 1);
229: }

231: /* Throw it in upper level of catcher blocks. */

233: void
234: __exc_rethrow ()
235: {
236:   jmp_buf j;

238:   __exc_debug ("RETHROW ()");
239: #ifdef __EXC_DEBUG
240:   __exc_print (__EXC_STREAM, __exc_file, __exc_function,
241:                __exc_line, __exc_code);
242: #endif

244:   __exc_pop (&j);
245:   longjmp (j, 1);
246: }



250: #ifdef __EXC_DEBUG
251: #  define __EXC_NDEBUG_UN(decl)         decl
252: #else
253: #  define __EXC_NDEBUG_UN(decl)         decl __attribute__((unused))
254: #endif

256: int
257: __exc_on (__EXC_NDEBUG_UN(char *file),
258:           __EXC_NDEBUG_UN(char *function),
259:           __EXC_NDEBUG_UN(unsigned line),
260:           __EXC_TYPE code)
261: {
262:   /* It is impossible to jump to the end of EXCEPT block, nor it is
263:      possible to mix ELSE with IF, so use before-defined __EXC_HANDLED
264:      flag. 

266:      Actually, use of
267:       on (...) ...;
268:       else on (...) ...;

270:      Is possible, but don't force the *user* to care about it. */

272:   __exc_debug ("ON ()");
273:   __exc_debug ("Trying to handle in file \"%s\", at line %u", file, line);
274: #ifdef __EXC_DEBUG
275:   if (function)
276:     {
277:       __exc_debug ("In function \"%s\".", function);
278:     }
279: #endif

281:   if (__exc_handled == 1)
282:     {
283:       __exc_debug ("Exception already handled in this level, skip");
284:       return 0;
285:     }
286:   
287:   if (__EXC_EQ (code, __exc_code))
288:     {
289:       __exc_debug ("This handler FITS");

291:       __exc_handled = 1;
292:       return 1;
293:     }

295:   __exc_debug ("This handler DOESN'T FIT");

297:   /* Not matched. */
298:   return 0;
299: }