3 <title>libsm : Exception Handling
</title>
7 <a href=
"index.html">Back to libsm overview
</a>
10 <h1> libsm : Exception Handling
</h1>
11 <br> $Id: exc.html,v
1.13 2006/
06/
20 17:
18:
16 ca Exp $
14 <h2> Introduction
</h2>
16 The exception handling package provides the facilities that
17 functions in libsm use to report errors.
18 Here are the basic concepts:
22 When a function detects an exceptional condition at the library level,
23 it does not print an error message, or call syslog, or
24 exit the program. Instead, it reports the error back to its
25 caller, and lets the caller decide what to do.
26 This improves modularity, because error handling is separated
30 Errors are not represented by a single integer error code,
31 because then you can't represent everything that an error handler
32 might need to know about an error by a single integer.
33 Instead, errors are represented by exception objects.
34 An exception object contains an exception code and an array
35 of zero or more exception arguments.
36 The exception code is a string that specifies what kind of exception
37 this is, and the arguments may be integers, strings or exception objects.
40 Errors are not reported using a special return value,
41 because if you religiously check for error returns from every
42 function call that could fail, then most of your code ends up being
43 error handling code. Errors are reported by raising an exception.
44 When an exception is raised, we unwind the call stack
45 until we find an exception handler. If the exception is
46 not handled, then we print the exception on stderr and
53 #include
<sm/exc.h
>
55 typedef struct sm_exc_type SM_EXC_TYPE_T;
56 typedef struct sm_exc SM_EXC_T;
57 typedef union sm_val SM_VAL_T;
63 extern const char SmExcTypeMagic[];
68 const char *etype_category;
69 const char *etype_argformat;
70 void (*etype_print)(SM_EXC_T *exc, SM_FILE_T *stream);
71 const char *etype_printcontext;
74 extern const SM_EXC_TYPE_T SmEtypeOs;
75 extern const SM_EXC_TYPE_T SmEtypeErr;
86 extern const char SmExcMagic[];
100 const SM_EXC_TYPE_T *exc_type;
106 const SM_EXC_TYPE_T *type,
120 const char *pattern);
138 const SM_EXC_TYPE_T *type,
142 ** Ensure that cleanup code is executed,
143 ** and/or handle an exception.
146 Block of code that may raise an exception.
148 Cleanup code that may raise an exception.
149 This clause is guaranteed to be executed even if an exception is
150 raised by the SM_TRY clause or by an earlier SM_FINALLY clause.
151 You may have
0 or more SM_FINALLY clauses.
152 SM_EXCEPT(exc, pattern)
153 Exception handling code, triggered by an exception
154 whose category matches 'pattern'.
155 You may have
0 or more SM_EXCEPT clauses.
161 An exception is an object which represents an exceptional condition,
162 which might be an error condition like
"out of memory", or might be
163 a condition like
"end of file".
165 Functions in libsm report errors and other unusual conditions by
166 raising an exception, rather than by returning an error code or
167 setting a global variable such as errno. If a libsm function is
168 capable of raising an exception, its name ends in
"_x".
169 (We do not raise an exception when a bug is detected in the
170 program; instead, we terminate the program using
<tt>sm_abort
</tt>.
171 See
<a href=
"assert.html">the assertion package
</a>
174 When you are using the libsm exception handling package,
175 you are using a new programming paradigm.
176 You will need to abandon some of the programming idioms
177 you are accustomed to, and switch to new idioms.
178 Here is an overview of some of these idioms.
181 When a function is unable to complete its task because
182 of an exceptional condition, it reports this condition
183 by raising an exception.
185 Here is an example of how to construct an exception object
186 and raise an exception.
187 In this example, we convert a Unix system error into an exception.
189 fd = open(path, O_RDONLY);
191 sm_exc_raise_x(sm_exc_new_x(&SmEtypeOs, errno,
"open",
"%s", path));
194 Because the idiom
<tt>sm_exc_raise_x(sm_exc_new_x(...))
</tt>
195 is so common, it can be abbreviated as
<tt>sm_exc_raisenew_x(...)
</tt>.
198 When you detect an error at the application level,
199 you don't call a function like BSD's
<tt>errx
</tt>,
200 which prints an error message on stderr and exits the program.
201 Instead, you raise an exception.
202 This causes cleanup code in surrounding exception handlers
203 to be run before the program exits.
204 For example, instead of this:
206 errx(
1,
"%s:%d: syntax error", filename, lineno);
212 sm_exc_raisenew_x(&SmEtypeErr,
"%s:%d: syntax error", filename, lineno);
215 The latter code raises an exception, unwinding the call stack
216 and executing cleanup code.
217 If the exception is not handled, then the exception is printed
218 to stderr and the program exits.
219 The end result is substantially the same as a call to
<tt>errx
</tt>.
222 The SM_TRY ... SM_FINALLY ... control structure
223 ensures that cleanup code is executed and resources are released
224 in the presence of exceptions.
226 For example, suppose that you have written the following code:
229 rpool = sm_rpool_new_x(&SmRpoolRoot,
0);
231 sm_rpool_free_x(rpool);
234 If any of the functions called within
"... some code ..." have
235 names ending in _x, then it is possible that an exception will be
236 raised, and if that happens, then
"rpool" will not be freed.
237 And that's a bug. To fix this bug, change your code so it looks
241 rpool = sm_rpool_new_x(&SmRpoolRoot,
0);
243 ... some code that can raise an exception ...
245 sm_rpool_free_x(rpool);
250 The SM_TRY ... SM_EXCEPT ... control structure handles an exception.
251 Unhandled exceptions terminate the program.
252 For example, here is a simple exception handler
253 that traps all exceptions, and prints the exceptions:
257 /* code that can raise an exception */
260 /* catch all exceptions */
261 sm_exc_print(exc, stderr);
265 Exceptions are reference counted. The SM_END_TRY macro contains a
266 call to sm_exc_free, so you don't normally need to worry about freeing
267 an exception after handling it. In the rare case that you want an
268 exception to outlive an exception handler, then you increment its
269 reference count by calling sm_exc_addref.
272 The second argument of the SM_EXCEPT macro is a glob pattern
273 which specifies the types of exceptions that are to be handled.
274 For example, you might want to handle an end-of-file exception
275 differently from other exceptions.
276 Here's how you do that:
280 /* code that might raise end-of-file, or some other exception */
282 SM_EXCEPT(exc,
"E:sm.eof")
283 /* what to do if end-of-file is encountered */
286 /* what to do if some other exception is raised */
292 <h2> Exception Values
</h2>
294 In traditional C code, errors are usually denoted by a single integer,
295 such as errno. In practice, errno does not carry enough information
296 to describe everything that an error handler might want to know about
297 an error. And the scheme is not very extensible: if several different
298 packages want to add additional error codes, it is hard to avoid
302 In libsm, an exceptional condition is described
303 by an object of type SM_EXC_T.
304 An exception object is created by specifying an exception type
305 and a list of exception arguments.
308 The exception arguments are an array of zero or more values.
309 The values may be a mixture of ints, longs, strings, and exceptions.
310 In the SM_EXC_T structure, the argument vector is represented
311 by
<tt>SM_VAL_T
*exc_argv
</tt>, where
<tt>SM_VAL_T
</tt>
312 is a union of the possible argument types.
313 The number and types of exception arguments is determined by
317 An exception type is a statically initialized const object
318 of type SM_EXC_TYPE_T, which has the following members:
322 <tt> const char *sm_magic
</tt>
324 A pointer to
<tt>SmExcTypeMagic
</tt>.
327 <tt> const char *etype_category
</tt>
329 This is a string of the form
330 <tt>"</tt><i>class</i><tt>:</tt><i>name</i><tt>"</tt>.
332 The
<i>class
</i> is used to assign the exception type to
333 one of a number of broad categories of exceptions on which an
334 exception handler might want to discriminate.
335 I suspect that what we want is a hierarchical taxonomy,
336 but I don't have a full design for this yet.
337 For now, I am recommending the following classes:
340 <dd>A fatal error has occurred.
341 This is an error that prevents the application
342 from making any further progress, so the only
343 recourse is to raise an exception, execute cleanup code
344 as the stack is unwound, then exit the application.
345 The out-of-memory exception raised by sm_malloc_x
346 has category
"F:sm.heap" because sendmail commits suicide
347 (after logging the error and cleaning up) when it runs out
351 <dd>The function could not complete its task because an error occurred.
352 (It might be useful to define subclasses of this category,
353 in which case our taxonony becomes a tree, and 'F' becomes
357 <dd>This exception is being raised in order to effect a
358 non-local jump. No error has occurred; we are just
359 performing the non-local equivalent of a
<tt>continue
</tt>,
360 <tt>break
</tt> or
<tt>return
</tt>.
363 <dd>The function was interrupted by a signal.
364 Signals are not errors because they occur asynchronously,
365 and they are semantically unrelated to the function that
366 happens to be executing when the signal arrives.
367 Note that it is extremely dangerous to raise an exception
368 from a signal handler. For example, if you are in the middle
369 of a call to malloc, you might corrupt the heap.
371 Eric's libsm paper defines
<tt>"W"</tt>,
<tt>"D"</tt> and
<tt>"I"</tt>
372 for Warning, Debug and Informational:
373 I suspect these categories only make sense in the context of
374 Eric's
1985 exception handling system which allowed you to
375 raise conditions without terminating the calling function.
377 The
<i>name
</i> uniquely identifies the exception type.
378 I recommend a string of the form
379 <i>library
</i><tt>.
</tt><i>package
</i><tt>.
</tt><i>detail
</i>.
382 <tt> const char *etype_argformat
</tt>
384 This is an array of single character codes.
385 Each code indicates the type of one of the exception arguments.
386 <tt>sm_exc_new_x
</tt> uses this string to decode its variable
387 argument list into an exception argument vector.
388 The following type codes are supported:
392 The exception argument has type
<tt>int
</tt>.
395 The exception argument has type
<tt>long
</tt>.
398 The exception argument has type
<tt>SM_EXC_T*
</tt>.
399 The value may either be
<tt>NULL
</tt> or a pointer
400 to an exception. The pointer value is simply copied
401 into the exception argument vector.
404 The exception argument has type
<tt>char*
</tt>.
405 The value may either be
<tt>NULL
</tt> or a pointer
406 to a character string. In the latter case,
407 <tt>sm_exc_new_x
</tt> will make a copy of the string.
410 The exception argument has type
<tt>char*
</tt>.
411 <tt>sm_exc_new_x
</tt> will read a printf-style
412 format string argument followed by a list of printf
413 arguments from its variable argument list, and convert
415 This type code can only occur as the last element
416 of
<tt>exc_argformat
</tt>.
420 <tt> void (*etype_print)(SM_EXC_T *exc, SM_FILE_T *stream)
</tt>
422 This function prints an exception of the specified type
423 onto an output stream.
424 The final character printed is not a newline.
427 <h2> Standard Exceptions and Exception Types
</h2>
429 Libsm defines one standard exception value,
<tt>SmHeapOutOfMemory
</tt>.
430 This is a statically initialized const variable, because it seems
431 like a bad idea to dynamically allocate an exception object to
432 report a low memory condition.
433 This exception has category
<tt>"F:sm.heap"</tt>.
434 If you need to, you can explicitly raise this exception
435 with
<tt>sm_exc_raise_x(&SmHeapOutOfMemory)
</tt>.
438 Statically initialized exception values cannot contain any
439 run-time parameters, so the normal case is to dynamically allocate
440 a new exception object whenever you raise an exception.
441 Before you can create an exception, you need an exception type.
442 Libsm defines the following standard exception types.
448 This represents a generic operating system error.
449 The category is
<tt>"E:sm.os"</tt>.
450 The argformat is
<tt>"isr"</tt>,
451 where argv[
0] is the value of
<tt>errno
</tt>
452 after a system call has failed,
453 argv[
1] is the name of the function (usually a system call) that failed,
454 and argv[
2] is either
<tt>NULL
</tt>
455 or a character string which describes some of the arguments
456 to the failing system call (usually it is just a file name).
457 Here's an example of raising an exception:
460 fd = open(filename, O_RDONLY);
462 sm_exc_raisenew_x(&SmEtypeOs, errno,
"open",
"%s", filename);
465 If errno is ENOENT and filename is
"/etc/mail/snedmail.cf",
466 then the exception raised by the above code will be printed as
469 /etc/mail/snedmail.cf: open failed: No such file or directory
473 <tt> SmEtypeErr
</tt>
475 This represents a generic error.
476 The category is
<tt>"E:sm.err"</tt>,
477 and the argformat is
<tt>"r"</tt>.
479 in application contexts where you are raising an exception
480 for the purpose of terminating the program.
481 You know the exception won't be handled,
482 so you don't need to worry about packaging the error for
483 later analysis by an exception handler.
484 All you need to specify is the message string that
485 will be printed to stderr before the program exits.
489 sm_exc_raisenew_x(&SmEtypeErr,
"name lookup failed: %s", name);
493 <h2> Custom Exception Types
</h2>
495 If you are writing a library package, and you need to raise
496 exceptions that are not standard Unix system errors,
497 then you need to define one or more new exception types.
500 Every new exception type needs a print function.
501 The standard print function
<tt>sm_etype_printf
</tt>
502 is all you need in the majority of cases.
503 It prints the
<tt>etype_printcontext
</tt> string of the exception type,
504 substituting occurrences of %
0 through %
9 with the corresponding
506 If exception argument
3 is an int or long,
507 then %
3 will print the argument in decimal,
508 and %o3 or %x3 will print it in octal or hex.
511 In the following example, I will assume that your library
512 package implements regular expressions, and can raise
5 different exceptions.
513 When compiling a regular expression,
3 different syntax errors
516 <li>unbalanced parenthesis
517 <li>unbalanced bracket
518 <li>missing argument for repetition operator
520 Whenever one of these errors is reported, you will also report
521 the index of the character within the regex string at which the
522 syntax error was detected.
523 The fourth exception is raised if a compiled regular expression
524 is invalid: this exception has no arguments.
525 The fifth exception is raised if the package runs out of memory:
526 for this, you use the standard
<tt>SmHeapOutOfMemory
</tt> exception.
529 The obvious approach is to define
4 separate exception types.
533 /* print a regular expression syntax error */
535 rx_esyntax_print(SM_EXC_T *exc, SM_FILE_T *stream)
537 sm_io_fprintf(stream,
"rx syntax error at character %d: %s",
538 exc-
>exc_argv[
0].v_int,
539 exc-
>exc_type-
>etype_printcontext);
541 SM_EXC_TYPE_T RxSyntaxParen = {
543 "E:mylib.rx.syntax.paren",
546 "unbalanced parenthesis"
548 SM_EXC_TYPE_T RxSyntaxBracket = {
550 "E:mylib.rx.syntax.bracket",
555 SM_EXC_TYPE_T RxSyntaxMissingArg = {
557 "E:mylib.rx.syntax.missingarg",
560 "missing argument for repetition operator"
563 SM_EXC_TYPE_T RxRunCorrupt = {
565 "E:mylib.rx.run.corrupt",
568 "rx runtime error: compiled regular expression is corrupt"
573 With the above definitions, you can raise a syntax error reporting
574 an unbalanced parenthesis at string offset
<tt>i
</tt> using:
576 sm_exc_raisenew_x(&RxSyntaxParen, i);
579 If
<tt>i==
42</tt> then this exception will be printed as:
581 rx syntax error at character
42: unbalanced parenthesis
584 An exception handler can provide special handling for regular
585 expression syntax errors using this code:
588 ... code that might raise an exception ...
589 SM_EXCEPT(exc,
"E:mylib.rx.syntax.*")
590 int i = exc-
>exc_argv[
0].v_int;
591 ... handle a regular expression syntax error ...
596 External requirements may force you to define an integer code
597 for each error reported by your package. Or you may be wrapping
598 an existing package that works this way. In this case, it might
599 make sense to define a single exception type, patterned after SmEtypeOs,
600 and include the integer code as an exception argument.
603 Your package might intercept an exception E generated by a lower
604 level package, and then reclassify it as a different expression E'.
605 For example, a package for reading a configuration file might
606 reclassify one of the regular expression syntax errors from the
607 previous example as a configuration file syntax error.
608 When you do this, the new exception E' should include the original
609 exception E as an exception parameter, and the print function for
610 exception E' should print the high level description of the exception
611 (eg,
"syntax error in configuration file %s at line %d\n"),
612 then print the subexception that is stored as an exception parameter.
614 <h2> Function Reference
</h2>
618 <tt> SM_EXC_T *sm_exc_new_x(const SM_EXC_TYPE_T *type, ...)
</tt>
620 Create a new exception. Raise an exception on heap exhaustion.
621 The new exception has a reference count of
1.
624 A list of zero or more exception arguments follows the exception type;
625 these are copied into the new exception object.
626 The number and types of these arguments is determined
627 by
<tt>type-
>etype_argformat
</tt>.
630 Note that there is no rpool argument to sm_exc_new_x.
631 Exceptions are allocated directly from the heap.
632 This is because exceptions are normally raised at low levels
633 of abstraction and handled at high levels. Because the low
634 level code typically has no idea of how or at what level the
635 exception will be handled, it also has no idea of which resource
636 pool, if any, should own the exception.
639 <tt> SM_EXC_T *sm_exc_addref(SM_EXC_T *exc)
</tt>
641 Increment the reference count of an exception.
642 Return the first argument.
645 <tt> void sm_exc_free(SM_EXC_T *exc)
</tt>
647 Decrement the reference count of an exception.
648 If it reaches
0, free the exception object.
651 <tt> bool sm_exc_match(SM_EXC_T *exc, const char *pattern)
</tt>
653 Compare the exception's category to the specified glob pattern,
654 return true if they match.
657 <tt> void sm_exc_print(SM_EXC_T *exc, SM_FILE_T *stream)
</tt>
659 Print the exception on the stream
660 as a sequence of one or more newline terminated lines.
663 <tt> void sm_exc_write(SM_EXC_T *exc, SM_FILE_T *stream)
</tt>
665 Write the exception on the stream without a terminating newline.
668 <tt> void sm_exc_raise_x(SM_EXC_T *exc)
</tt>
670 Raise the exception. This function does not return to its caller.
673 <tt> void sm_exc_raisenew_x(const SM_EXC_TYPE_T *type, ...)
</tt>
675 A short form for
<tt>sm_exc_raise_x(sm_exc_new_x(type,...))
</tt>.
678 <h2> Macro Reference
</h2>
680 The SM_TRY ... SM_END_TRY control structure
681 ensures that cleanup code is executed in the presence of exceptions,
682 and permits exceptions to be handled.
686 A block of code that may raise an exception.
688 Cleanup code that may raise an exception.
689 This code is guaranteed to be executed whether or not
690 an exception was raised by a previous clause.
691 You may have
0 or more SM_FINALLY clauses.
693 Exception handling code, which is triggered by an exception
694 whose category matches the glob pattern 'pat'.
695 The exception value is bound to the local variable 'e'.
696 You may have
0 or more SM_EXCEPT clauses.
700 First, the SM_TRY clause is executed, then each SM_FINALLY clause is
701 executed in sequence.
702 If one or more of these clauses was terminated by an exception,
703 then the first such exception is remembered, and the other exceptions
706 If no exception was raised, then we are done.
708 Otherwise, each of the SM_EXCEPT clauses is examined in sequence.
709 and the first SM_EXCEPT clause whose pattern argument matches the exception
710 (see
<tt>sm_exc_match
</tt>) is executed.
711 If none of the SM_EXCEPT clauses matched the exception, or if there are
712 no SM_EXCEPT clauses, then the remembered exception is re-raised.
715 SM_TRY .. SM_END_TRY clauses may be nested arbitrarily.
718 It is illegal to jump out of a SM_TRY or SM_FINALLY clause
719 using goto, break, continue, return or longjmp.
720 If you do this, you will corrupt the internal exception handling stack.
721 You can't use
<tt>break
</tt> or
<tt>continue
</tt> in an SM_EXCEPT clause;
722 these are reserved for use by the implementation.
723 It is legal to jump out of an SM_EXCEPT clause using goto or return;
724 however, in this case, you must take responsibility
725 for freeing the exception object.
728 The SM_TRY and SM_FINALLY macros contain calls to setjmp,
729 and consequently, they suffer from the limitations imposed on setjmp
731 Suppose you declare an auto variable
<tt>i
</tt> outside of a
732 SM_TRY ... SM_END_TRY statement, initializing it to
0.
733 Then you modify
<tt>i
</tt> inside of a SM_TRY or SM_FINALLY clause,
735 If you reference
<tt>i
</tt> in a different SM_FINALLY clause, or in
736 an SM_EXCEPT clause, then it is implementation dependent whether
<tt>i
</tt>
737 will be
0 or
1, unless you have declared
<tt>i
</tt> to be
<tt>volatile
</tt>.
746 /* the following reference to i only works if i is declared volatile */
750 /* the following reference to i only works if i is declared volatile */