48cecd8a58
MFC after: 3 days
758 lines
23 KiB
HTML
758 lines
23 KiB
HTML
<html>
|
|
<head>
|
|
<title>libsm : Exception Handling</title>
|
|
</head>
|
|
<body>
|
|
|
|
<a href="index.html">Back to libsm overview</a>
|
|
|
|
<center>
|
|
<h1> libsm : Exception Handling </h1>
|
|
<br> $Id: exc.html,v 1.13 2006-06-20 17:18:16 ca Exp $
|
|
</center>
|
|
|
|
<h2> Introduction </h2>
|
|
|
|
The exception handling package provides the facilities that
|
|
functions in libsm use to report errors.
|
|
Here are the basic concepts:
|
|
|
|
<ol>
|
|
<li>
|
|
When a function detects an exceptional condition at the library level,
|
|
it does not print an error message, or call syslog, or
|
|
exit the program. Instead, it reports the error back to its
|
|
caller, and lets the caller decide what to do.
|
|
This improves modularity, because error handling is separated
|
|
from error reporting.
|
|
<p>
|
|
<li>
|
|
Errors are not represented by a single integer error code,
|
|
because then you can't represent everything that an error handler
|
|
might need to know about an error by a single integer.
|
|
Instead, errors are represented by exception objects.
|
|
An exception object contains an exception code and an array
|
|
of zero or more exception arguments.
|
|
The exception code is a string that specifies what kind of exception
|
|
this is, and the arguments may be integers, strings or exception objects.
|
|
<p>
|
|
<li>
|
|
Errors are not reported using a special return value,
|
|
because if you religiously check for error returns from every
|
|
function call that could fail, then most of your code ends up being
|
|
error handling code. Errors are reported by raising an exception.
|
|
When an exception is raised, we unwind the call stack
|
|
until we find an exception handler. If the exception is
|
|
not handled, then we print the exception on stderr and
|
|
exit the program.
|
|
</ol>
|
|
|
|
<h2> Synopsis </h2>
|
|
|
|
<pre>
|
|
#include <sm/exc.h>
|
|
|
|
typedef struct sm_exc_type SM_EXC_TYPE_T;
|
|
typedef struct sm_exc SM_EXC_T;
|
|
typedef union sm_val SM_VAL_T;
|
|
|
|
/*
|
|
** Exception types
|
|
*/
|
|
|
|
extern const char SmExcTypeMagic[];
|
|
|
|
struct sm_exc_type
|
|
{
|
|
const char *sm_magic;
|
|
const char *etype_category;
|
|
const char *etype_argformat;
|
|
void (*etype_print)(SM_EXC_T *exc, SM_FILE_T *stream);
|
|
const char *etype_printcontext;
|
|
};
|
|
|
|
extern const SM_EXC_TYPE_T SmEtypeOs;
|
|
extern const SM_EXC_TYPE_T SmEtypeErr;
|
|
|
|
void
|
|
sm_etype_printf(
|
|
SM_EXC_T *exc,
|
|
SM_FILE_T *stream);
|
|
|
|
/*
|
|
** Exception objects
|
|
*/
|
|
|
|
extern const char SmExcMagic[];
|
|
|
|
union sm_val
|
|
{
|
|
int v_int;
|
|
long v_long;
|
|
char *v_str;
|
|
SM_EXC_T *v_exc;
|
|
};
|
|
|
|
struct sm_exc
|
|
{
|
|
const char *sm_magic;
|
|
size_t exc_refcount;
|
|
const SM_EXC_TYPE_T *exc_type;
|
|
SM_VAL_T *exc_argv;
|
|
};
|
|
|
|
SM_EXC_T *
|
|
sm_exc_new_x(
|
|
const SM_EXC_TYPE_T *type,
|
|
...);
|
|
|
|
SM_EXC_T *
|
|
sm_exc_addref(
|
|
SM_EXC_T *exc);
|
|
|
|
void
|
|
sm_exc_free(
|
|
SM_EXC_T *exc);
|
|
|
|
bool
|
|
sm_exc_match(
|
|
SM_EXC_T *exc,
|
|
const char *pattern);
|
|
|
|
void
|
|
sm_exc_print(
|
|
SM_EXC_T *exc,
|
|
SM_FILE_T *stream);
|
|
|
|
void
|
|
sm_exc_write(
|
|
SM_EXC_T *exc,
|
|
SM_FILE_T *stream);
|
|
|
|
void
|
|
sm_exc_raise_x(
|
|
SM_EXC_T *exc);
|
|
|
|
void
|
|
sm_exc_raisenew_x(
|
|
const SM_EXC_TYPE_T *type,
|
|
...);
|
|
|
|
/*
|
|
** Ensure that cleanup code is executed,
|
|
** and/or handle an exception.
|
|
*/
|
|
SM_TRY
|
|
Block of code that may raise an exception.
|
|
SM_FINALLY
|
|
Cleanup code that may raise an exception.
|
|
This clause is guaranteed to be executed even if an exception is
|
|
raised by the SM_TRY clause or by an earlier SM_FINALLY clause.
|
|
You may have 0 or more SM_FINALLY clauses.
|
|
SM_EXCEPT(exc, pattern)
|
|
Exception handling code, triggered by an exception
|
|
whose category matches 'pattern'.
|
|
You may have 0 or more SM_EXCEPT clauses.
|
|
SM_END_TRY
|
|
</pre>
|
|
|
|
<h2> Overview </h2>
|
|
|
|
An exception is an object which represents an exceptional condition,
|
|
which might be an error condition like "out of memory", or might be
|
|
a condition like "end of file".
|
|
<p>
|
|
Functions in libsm report errors and other unusual conditions by
|
|
raising an exception, rather than by returning an error code or
|
|
setting a global variable such as errno. If a libsm function is
|
|
capable of raising an exception, its name ends in "_x".
|
|
(We do not raise an exception when a bug is detected in the
|
|
program; instead, we terminate the program using <tt>sm_abort</tt>.
|
|
See <a href="assert.html">the assertion package</a>
|
|
for details.)
|
|
<p>
|
|
When you are using the libsm exception handling package,
|
|
you are using a new programming paradigm.
|
|
You will need to abandon some of the programming idioms
|
|
you are accustomed to, and switch to new idioms.
|
|
Here is an overview of some of these idioms.
|
|
<ol>
|
|
<li>
|
|
When a function is unable to complete its task because
|
|
of an exceptional condition, it reports this condition
|
|
by raising an exception.
|
|
<p>
|
|
Here is an example of how to construct an exception object
|
|
and raise an exception.
|
|
In this example, we convert a Unix system error into an exception.
|
|
<blockquote><pre>
|
|
fd = open(path, O_RDONLY);
|
|
if (fd == -1)
|
|
sm_exc_raise_x(sm_exc_new_x(&SmEtypeOs, errno, "open", "%s", path));
|
|
</pre></blockquote>
|
|
|
|
Because the idiom <tt>sm_exc_raise_x(sm_exc_new_x(...))</tt>
|
|
is so common, it can be abbreviated as <tt>sm_exc_raisenew_x(...)</tt>.
|
|
<p>
|
|
<li>
|
|
When you detect an error at the application level,
|
|
you don't call a function like BSD's <tt>errx</tt>,
|
|
which prints an error message on stderr and exits the program.
|
|
Instead, you raise an exception.
|
|
This causes cleanup code in surrounding exception handlers
|
|
to be run before the program exits.
|
|
For example, instead of this:
|
|
<blockquote><pre>
|
|
errx(1, "%s:%d: syntax error", filename, lineno);
|
|
</pre></blockquote>
|
|
|
|
use this:
|
|
|
|
<blockquote><pre>
|
|
sm_exc_raisenew_x(&SmEtypeErr, "%s:%d: syntax error", filename, lineno);
|
|
</pre></blockquote>
|
|
|
|
The latter code raises an exception, unwinding the call stack
|
|
and executing cleanup code.
|
|
If the exception is not handled, then the exception is printed
|
|
to stderr and the program exits.
|
|
The end result is substantially the same as a call to <tt>errx</tt>.
|
|
<p>
|
|
<li>
|
|
The SM_TRY ... SM_FINALLY ... control structure
|
|
ensures that cleanup code is executed and resources are released
|
|
in the presence of exceptions.
|
|
<p>
|
|
For example, suppose that you have written the following code:
|
|
|
|
<blockquote><pre>
|
|
rpool = sm_rpool_new_x(&SmRpoolRoot, 0);
|
|
... some code ...
|
|
sm_rpool_free_x(rpool);
|
|
</pre></blockquote>
|
|
|
|
If any of the functions called within "... some code ..." have
|
|
names ending in _x, then it is possible that an exception will be
|
|
raised, and if that happens, then "rpool" will not be freed.
|
|
And that's a bug. To fix this bug, change your code so it looks
|
|
like this:
|
|
|
|
<blockquote><pre>
|
|
rpool = sm_rpool_new_x(&SmRpoolRoot, 0);
|
|
SM_TRY
|
|
... some code that can raise an exception ...
|
|
SM_FINALLY
|
|
sm_rpool_free_x(rpool);
|
|
SM_END_TRY
|
|
</pre></blockquote>
|
|
|
|
<li>
|
|
The SM_TRY ... SM_EXCEPT ... control structure handles an exception.
|
|
Unhandled exceptions terminate the program.
|
|
For example, here is a simple exception handler
|
|
that traps all exceptions, and prints the exceptions:
|
|
|
|
<blockquote><pre>
|
|
SM_TRY
|
|
/* code that can raise an exception */
|
|
...
|
|
SM_EXCEPT(exc, "*")
|
|
/* catch all exceptions */
|
|
sm_exc_print(exc, stderr);
|
|
SM_END_TRY
|
|
</pre></blockquote>
|
|
|
|
Exceptions are reference counted. The SM_END_TRY macro contains a
|
|
call to sm_exc_free, so you don't normally need to worry about freeing
|
|
an exception after handling it. In the rare case that you want an
|
|
exception to outlive an exception handler, then you increment its
|
|
reference count by calling sm_exc_addref.
|
|
<p>
|
|
<li>
|
|
The second argument of the SM_EXCEPT macro is a glob pattern
|
|
which specifies the types of exceptions that are to be handled.
|
|
For example, you might want to handle an end-of-file exception
|
|
differently from other exceptions.
|
|
Here's how you do that:
|
|
|
|
<blockquote><pre>
|
|
SM_TRY
|
|
/* code that might raise end-of-file, or some other exception */
|
|
...
|
|
SM_EXCEPT(exc, "E:sm.eof")
|
|
/* what to do if end-of-file is encountered */
|
|
...
|
|
SM_EXCEPT(exc, "*")
|
|
/* what to do if some other exception is raised */
|
|
...
|
|
SM_END_TRY
|
|
</pre></blockquote>
|
|
</ol>
|
|
|
|
<h2> Exception Values </h2>
|
|
|
|
In traditional C code, errors are usually denoted by a single integer,
|
|
such as errno. In practice, errno does not carry enough information
|
|
to describe everything that an error handler might want to know about
|
|
an error. And the scheme is not very extensible: if several different
|
|
packages want to add additional error codes, it is hard to avoid
|
|
collisions.
|
|
|
|
<p>
|
|
In libsm, an exceptional condition is described
|
|
by an object of type SM_EXC_T.
|
|
An exception object is created by specifying an exception type
|
|
and a list of exception arguments.
|
|
|
|
<p>
|
|
The exception arguments are an array of zero or more values.
|
|
The values may be a mixture of ints, longs, strings, and exceptions.
|
|
In the SM_EXC_T structure, the argument vector is represented
|
|
by <tt>SM_VAL_T *exc_argv</tt>, where <tt>SM_VAL_T</tt>
|
|
is a union of the possible argument types.
|
|
The number and types of exception arguments is determined by
|
|
the exception type.
|
|
|
|
<p>
|
|
An exception type is a statically initialized const object
|
|
of type SM_EXC_TYPE_T, which has the following members:
|
|
|
|
<dl>
|
|
<dt>
|
|
<tt> const char *sm_magic </tt>
|
|
<dd>
|
|
A pointer to <tt>SmExcTypeMagic</tt>.
|
|
<p>
|
|
<dt>
|
|
<tt> const char *etype_category </tt>
|
|
<dd>
|
|
This is a string of the form
|
|
<tt>"</tt><i>class</i><tt>:</tt><i>name</i><tt>"</tt>.
|
|
<p>
|
|
The <i>class</i> is used to assign the exception type to
|
|
one of a number of broad categories of exceptions on which an
|
|
exception handler might want to discriminate.
|
|
I suspect that what we want is a hierarchical taxonomy,
|
|
but I don't have a full design for this yet.
|
|
For now, I am recommending the following classes:
|
|
<dl>
|
|
<dt><tt>"F"</tt>
|
|
<dd>A fatal error has occurred.
|
|
This is an error that prevents the application
|
|
from making any further progress, so the only
|
|
recourse is to raise an exception, execute cleanup code
|
|
as the stack is unwound, then exit the application.
|
|
The out-of-memory exception raised by sm_malloc_x
|
|
has category "F:sm.heap" because sendmail commits suicide
|
|
(after logging the error and cleaning up) when it runs out
|
|
of memory.
|
|
|
|
<dt><tt>"E"</tt>
|
|
<dd>The function could not complete its task because an error occurred.
|
|
(It might be useful to define subclasses of this category,
|
|
in which case our taxonony becomes a tree, and 'F' becomes
|
|
a subclass of 'E'.)
|
|
|
|
<dt><tt>"J"</tt>
|
|
<dd>This exception is being raised in order to effect a
|
|
non-local jump. No error has occurred; we are just
|
|
performing the non-local equivalent of a <tt>continue</tt>,
|
|
<tt>break</tt> or <tt>return</tt>.
|
|
|
|
<dt><tt>"S"</tt>
|
|
<dd>The function was interrupted by a signal.
|
|
Signals are not errors because they occur asynchronously,
|
|
and they are semantically unrelated to the function that
|
|
happens to be executing when the signal arrives.
|
|
Note that it is extremely dangerous to raise an exception
|
|
from a signal handler. For example, if you are in the middle
|
|
of a call to malloc, you might corrupt the heap.
|
|
</dl>
|
|
Eric's libsm paper defines <tt>"W"</tt>, <tt>"D"</tt> and <tt>"I"</tt>
|
|
for Warning, Debug and Informational:
|
|
I suspect these categories only make sense in the context of
|
|
Eric's 1985 exception handling system which allowed you to
|
|
raise conditions without terminating the calling function.
|
|
<p>
|
|
The <i>name</i> uniquely identifies the exception type.
|
|
I recommend a string of the form
|
|
<i>library</i><tt>.</tt><i>package</i><tt>.</tt><i>detail</i>.
|
|
<p>
|
|
<dt>
|
|
<tt> const char *etype_argformat </tt>
|
|
<dd>
|
|
This is an array of single character codes.
|
|
Each code indicates the type of one of the exception arguments.
|
|
<tt>sm_exc_new_x</tt> uses this string to decode its variable
|
|
argument list into an exception argument vector.
|
|
The following type codes are supported:
|
|
<dl>
|
|
<dt><tt>i</tt>
|
|
<dd>
|
|
The exception argument has type <tt>int</tt>.
|
|
<dt><tt>l</tt>
|
|
<dd>
|
|
The exception argument has type <tt>long</tt>.
|
|
<dt><tt>e</tt>
|
|
<dd>
|
|
The exception argument has type <tt>SM_EXC_T*</tt>.
|
|
The value may either be <tt>NULL</tt> or a pointer
|
|
to an exception. The pointer value is simply copied
|
|
into the exception argument vector.
|
|
<dt><tt>s</tt>
|
|
<dd>
|
|
The exception argument has type <tt>char*</tt>.
|
|
The value may either be <tt>NULL</tt> or a pointer
|
|
to a character string. In the latter case,
|
|
<tt>sm_exc_new_x</tt> will make a copy of the string.
|
|
<dt><tt>r</tt>
|
|
<dd>
|
|
The exception argument has type <tt>char*</tt>.
|
|
<tt>sm_exc_new_x</tt> will read a printf-style
|
|
format string argument followed by a list of printf
|
|
arguments from its variable argument list, and convert
|
|
these into a string.
|
|
This type code can only occur as the last element
|
|
of <tt>exc_argformat</tt>.
|
|
</dl>
|
|
<p>
|
|
<dt>
|
|
<tt> void (*etype_print)(SM_EXC_T *exc, SM_FILE_T *stream) </tt>
|
|
<dd>
|
|
This function prints an exception of the specified type
|
|
onto an output stream.
|
|
The final character printed is not a newline.
|
|
</dl>
|
|
|
|
<h2> Standard Exceptions and Exception Types </h2>
|
|
|
|
Libsm defines one standard exception value, <tt>SmHeapOutOfMemory</tt>.
|
|
This is a statically initialized const variable, because it seems
|
|
like a bad idea to dynamically allocate an exception object to
|
|
report a low memory condition.
|
|
This exception has category <tt>"F:sm.heap"</tt>.
|
|
If you need to, you can explicitly raise this exception
|
|
with <tt>sm_exc_raise_x(&SmHeapOutOfMemory)</tt>.
|
|
|
|
<p>
|
|
Statically initialized exception values cannot contain any
|
|
run-time parameters, so the normal case is to dynamically allocate
|
|
a new exception object whenever you raise an exception.
|
|
Before you can create an exception, you need an exception type.
|
|
Libsm defines the following standard exception types.
|
|
|
|
<dl>
|
|
<dt>
|
|
<tt> SmEtypeOs </tt>
|
|
<dd>
|
|
This represents a generic operating system error.
|
|
The category is <tt>"E:sm.os"</tt>.
|
|
The argformat is <tt>"isr"</tt>,
|
|
where argv[0] is the value of <tt>errno</tt>
|
|
after a system call has failed,
|
|
argv[1] is the name of the function (usually a system call) that failed,
|
|
and argv[2] is either <tt>NULL</tt>
|
|
or a character string which describes some of the arguments
|
|
to the failing system call (usually it is just a file name).
|
|
Here's an example of raising an exception:
|
|
|
|
<blockquote><pre>
|
|
fd = open(filename, O_RDONLY);
|
|
if (fd == -1)
|
|
sm_exc_raisenew_x(&SmEtypeOs, errno, "open", "%s", filename);
|
|
</pre></blockquote>
|
|
|
|
If errno is ENOENT and filename is "/etc/mail/snedmail.cf",
|
|
then the exception raised by the above code will be printed as
|
|
|
|
<blockquote><pre>
|
|
/etc/mail/snedmail.cf: open failed: No such file or directory
|
|
</pre></blockquote>
|
|
|
|
<dt>
|
|
<tt> SmEtypeErr </tt>
|
|
<dd>
|
|
This represents a generic error.
|
|
The category is <tt>"E:sm.err"</tt>,
|
|
and the argformat is <tt>"r"</tt>.
|
|
You can use it
|
|
in application contexts where you are raising an exception
|
|
for the purpose of terminating the program.
|
|
You know the exception won't be handled,
|
|
so you don't need to worry about packaging the error for
|
|
later analysis by an exception handler.
|
|
All you need to specify is the message string that
|
|
will be printed to stderr before the program exits.
|
|
For example,
|
|
|
|
<blockquote><pre>
|
|
sm_exc_raisenew_x(&SmEtypeErr, "name lookup failed: %s", name);
|
|
</pre></blockquote>
|
|
</dl>
|
|
|
|
<h2> Custom Exception Types </h2>
|
|
|
|
If you are writing a library package, and you need to raise
|
|
exceptions that are not standard Unix system errors,
|
|
then you need to define one or more new exception types.
|
|
|
|
<p>
|
|
Every new exception type needs a print function.
|
|
The standard print function <tt>sm_etype_printf</tt>
|
|
is all you need in the majority of cases.
|
|
It prints the <tt>etype_printcontext</tt> string of the exception type,
|
|
substituting occurrences of %0 through %9 with the corresponding
|
|
exception argument.
|
|
If exception argument 3 is an int or long,
|
|
then %3 will print the argument in decimal,
|
|
and %o3 or %x3 will print it in octal or hex.
|
|
|
|
<p>
|
|
In the following example, I will assume that your library
|
|
package implements regular expressions, and can raise 5 different exceptions.
|
|
When compiling a regular expression, 3 different syntax errors
|
|
can be reported:
|
|
<ul>
|
|
<li>unbalanced parenthesis
|
|
<li>unbalanced bracket
|
|
<li>missing argument for repetition operator
|
|
</ul>
|
|
Whenever one of these errors is reported, you will also report
|
|
the index of the character within the regex string at which the
|
|
syntax error was detected.
|
|
The fourth exception is raised if a compiled regular expression
|
|
is invalid: this exception has no arguments.
|
|
The fifth exception is raised if the package runs out of memory:
|
|
for this, you use the standard <tt>SmHeapOutOfMemory</tt> exception.
|
|
|
|
<p>
|
|
The obvious approach is to define 4 separate exception types.
|
|
Here they are:
|
|
|
|
<blockquote><pre>
|
|
/* print a regular expression syntax error */
|
|
void
|
|
rx_esyntax_print(SM_EXC_T *exc, SM_FILE_T *stream)
|
|
{
|
|
sm_io_fprintf(stream, "rx syntax error at character %d: %s",
|
|
exc->exc_argv[0].v_int,
|
|
exc->exc_type->etype_printcontext);
|
|
}
|
|
SM_EXC_TYPE_T RxSyntaxParen = {
|
|
SmExcTypeMagic,
|
|
"E:mylib.rx.syntax.paren",
|
|
"i",
|
|
rx_esyntax_print,
|
|
"unbalanced parenthesis"
|
|
};
|
|
SM_EXC_TYPE_T RxSyntaxBracket = {
|
|
SmExcTypeMagic,
|
|
"E:mylib.rx.syntax.bracket",
|
|
"i",
|
|
rx_esyntax_print,
|
|
"unbalanced bracket"
|
|
};
|
|
SM_EXC_TYPE_T RxSyntaxMissingArg = {
|
|
SmExcTypeMagic,
|
|
"E:mylib.rx.syntax.missingarg",
|
|
"i",
|
|
rx_esyntax_print,
|
|
"missing argument for repetition operator"
|
|
};
|
|
|
|
SM_EXC_TYPE_T RxRunCorrupt = {
|
|
SmExcTypeMagic,
|
|
"E:mylib.rx.run.corrupt",
|
|
"",
|
|
sm_etype_printf,
|
|
"rx runtime error: compiled regular expression is corrupt"
|
|
};
|
|
</pre></blockquote>
|
|
|
|
<p>
|
|
With the above definitions, you can raise a syntax error reporting
|
|
an unbalanced parenthesis at string offset <tt>i</tt> using:
|
|
<blockquote><pre>
|
|
sm_exc_raisenew_x(&RxSyntaxParen, i);
|
|
</pre></blockquote>
|
|
|
|
If <tt>i==42</tt> then this exception will be printed as:
|
|
<blockquote><pre>
|
|
rx syntax error at character 42: unbalanced parenthesis
|
|
</pre></blockquote>
|
|
|
|
An exception handler can provide special handling for regular
|
|
expression syntax errors using this code:
|
|
<blockquote><pre>
|
|
SM_TRY
|
|
... code that might raise an exception ...
|
|
SM_EXCEPT(exc, "E:mylib.rx.syntax.*")
|
|
int i = exc->exc_argv[0].v_int;
|
|
... handle a regular expression syntax error ...
|
|
SM_END_TRY
|
|
</pre></blockquote>
|
|
|
|
<p>
|
|
External requirements may force you to define an integer code
|
|
for each error reported by your package. Or you may be wrapping
|
|
an existing package that works this way. In this case, it might
|
|
make sense to define a single exception type, patterned after SmEtypeOs,
|
|
and include the integer code as an exception argument.
|
|
|
|
<p>
|
|
Your package might intercept an exception E generated by a lower
|
|
level package, and then reclassify it as a different expression E'.
|
|
For example, a package for reading a configuration file might
|
|
reclassify one of the regular expression syntax errors from the
|
|
previous example as a configuration file syntax error.
|
|
When you do this, the new exception E' should include the original
|
|
exception E as an exception parameter, and the print function for
|
|
exception E' should print the high level description of the exception
|
|
(eg, "syntax error in configuration file %s at line %d\n"),
|
|
then print the subexception that is stored as an exception parameter.
|
|
|
|
<h2> Function Reference </h2>
|
|
|
|
<dl>
|
|
<dt>
|
|
<tt> SM_EXC_T *sm_exc_new_x(const SM_EXC_TYPE_T *type, ...) </tt>
|
|
<dd>
|
|
Create a new exception. Raise an exception on heap exhaustion.
|
|
The new exception has a reference count of 1.
|
|
<p>
|
|
|
|
A list of zero or more exception arguments follows the exception type;
|
|
these are copied into the new exception object.
|
|
The number and types of these arguments is determined
|
|
by <tt>type->etype_argformat</tt>.
|
|
<p>
|
|
|
|
Note that there is no rpool argument to sm_exc_new_x.
|
|
Exceptions are allocated directly from the heap.
|
|
This is because exceptions are normally raised at low levels
|
|
of abstraction and handled at high levels. Because the low
|
|
level code typically has no idea of how or at what level the
|
|
exception will be handled, it also has no idea of which resource
|
|
pool, if any, should own the exception.
|
|
<p>
|
|
<dt>
|
|
<tt> SM_EXC_T *sm_exc_addref(SM_EXC_T *exc) </tt>
|
|
<dd>
|
|
Increment the reference count of an exception.
|
|
Return the first argument.
|
|
<p>
|
|
<dt>
|
|
<tt> void sm_exc_free(SM_EXC_T *exc) </tt>
|
|
<dd>
|
|
Decrement the reference count of an exception.
|
|
If it reaches 0, free the exception object.
|
|
<p>
|
|
<dt>
|
|
<tt> bool sm_exc_match(SM_EXC_T *exc, const char *pattern) </tt>
|
|
<dd>
|
|
Compare the exception's category to the specified glob pattern,
|
|
return true if they match.
|
|
<p>
|
|
<dt>
|
|
<tt> void sm_exc_print(SM_EXC_T *exc, SM_FILE_T *stream) </tt>
|
|
<dd>
|
|
Print the exception on the stream
|
|
as a sequence of one or more newline terminated lines.
|
|
<p>
|
|
<dt>
|
|
<tt> void sm_exc_write(SM_EXC_T *exc, SM_FILE_T *stream) </tt>
|
|
<dd>
|
|
Write the exception on the stream without a terminating newline.
|
|
<p>
|
|
<dt>
|
|
<tt> void sm_exc_raise_x(SM_EXC_T *exc) </tt>
|
|
<dd>
|
|
Raise the exception. This function does not return to its caller.
|
|
<p>
|
|
<dt>
|
|
<tt> void sm_exc_raisenew_x(const SM_EXC_TYPE_T *type, ...) </tt>
|
|
<dd>
|
|
A short form for <tt>sm_exc_raise_x(sm_exc_new_x(type,...))</tt>.
|
|
</dl>
|
|
|
|
<h2> Macro Reference </h2>
|
|
|
|
The SM_TRY ... SM_END_TRY control structure
|
|
ensures that cleanup code is executed in the presence of exceptions,
|
|
and permits exceptions to be handled.
|
|
|
|
<blockquote><pre>
|
|
SM_TRY
|
|
A block of code that may raise an exception.
|
|
SM_FINALLY
|
|
Cleanup code that may raise an exception.
|
|
This code is guaranteed to be executed whether or not
|
|
an exception was raised by a previous clause.
|
|
You may have 0 or more SM_FINALLY clauses.
|
|
SM_EXCEPT(e, pat)
|
|
Exception handling code, which is triggered by an exception
|
|
whose category matches the glob pattern 'pat'.
|
|
The exception value is bound to the local variable 'e'.
|
|
You may have 0 or more SM_EXCEPT clauses.
|
|
SM_END_TRY
|
|
</pre></blockquote>
|
|
|
|
First, the SM_TRY clause is executed, then each SM_FINALLY clause is
|
|
executed in sequence.
|
|
If one or more of these clauses was terminated by an exception,
|
|
then the first such exception is remembered, and the other exceptions
|
|
are lost.
|
|
|
|
If no exception was raised, then we are done.
|
|
|
|
Otherwise, each of the SM_EXCEPT clauses is examined in sequence.
|
|
and the first SM_EXCEPT clause whose pattern argument matches the exception
|
|
(see <tt>sm_exc_match</tt>) is executed.
|
|
If none of the SM_EXCEPT clauses matched the exception, or if there are
|
|
no SM_EXCEPT clauses, then the remembered exception is re-raised.
|
|
|
|
<p>
|
|
SM_TRY .. SM_END_TRY clauses may be nested arbitrarily.
|
|
|
|
<p>
|
|
It is illegal to jump out of a SM_TRY or SM_FINALLY clause
|
|
using goto, break, continue, return or longjmp.
|
|
If you do this, you will corrupt the internal exception handling stack.
|
|
You can't use <tt>break</tt> or <tt>continue</tt> in an SM_EXCEPT clause;
|
|
these are reserved for use by the implementation.
|
|
It is legal to jump out of an SM_EXCEPT clause using goto or return;
|
|
however, in this case, you must take responsibility
|
|
for freeing the exception object.
|
|
|
|
<p>
|
|
The SM_TRY and SM_FINALLY macros contain calls to setjmp,
|
|
and consequently, they suffer from the limitations imposed on setjmp
|
|
by the C standard.
|
|
Suppose you declare an auto variable <tt>i</tt> outside of a
|
|
SM_TRY ... SM_END_TRY statement, initializing it to 0.
|
|
Then you modify <tt>i</tt> inside of a SM_TRY or SM_FINALLY clause,
|
|
setting it to 1.
|
|
If you reference <tt>i</tt> in a different SM_FINALLY clause, or in
|
|
an SM_EXCEPT clause, then it is implementation dependent whether <tt>i</tt>
|
|
will be 0 or 1, unless you have declared <tt>i</tt> to be <tt>volatile</tt>.
|
|
|
|
<blockquote><pre>
|
|
int volatile i = 0;
|
|
|
|
SM_TRY
|
|
i = 1;
|
|
...
|
|
SM_FINALLY
|
|
/* the following reference to i only works if i is declared volatile */
|
|
use(i);
|
|
...
|
|
SM_EXCEPT(exc, "*")
|
|
/* the following reference to i only works if i is declared volatile */
|
|
use(i);
|
|
...
|
|
SM_END_TRY
|
|
</pre></blockquote>
|
|
|
|
</body>
|
|
</html>
|