diff options
Diffstat (limited to 'contrib/sendmail/libsm/assert.html')
-rw-r--r-- | contrib/sendmail/libsm/assert.html | 359 |
1 files changed, 359 insertions, 0 deletions
diff --git a/contrib/sendmail/libsm/assert.html b/contrib/sendmail/libsm/assert.html new file mode 100644 index 0000000..a325199 --- /dev/null +++ b/contrib/sendmail/libsm/assert.html @@ -0,0 +1,359 @@ +<html> +<head> + <title> libsm : Assert and Abort </title> +</head> +<body> + +<a href="index.html">Back to libsm overview</a> + +<center> + <h1> libsm : Assert and Abort </h1> + <br> $Id: assert.html,v 1.6 2001/08/27 21:47:03 ca Exp $ +</center> + +<h2> Introduction </h2> + +This package contains abstractions +for assertion checking and abnormal program termination. + +<h2> Synopsis </h2> + +<pre> +#include <sm/assert.h> + +/* +** abnormal program termination +*/ + +void sm_abort_at(char *filename, int lineno, char *msg); +typedef void (*SM_ABORT_HANDLER)(char *filename, int lineno, char *msg); +void sm_abort_sethandler(SM_ABORT_HANDLER); +void sm_abort(char *fmt, ...) + +/* +** assertion checking +*/ + +SM_REQUIRE(expression) +SM_ASSERT(expression) +SM_ENSURE(expression) + +extern SM_DEBUG_T SmExpensiveRequire; +extern SM_DEBUG_T SmExpensiveAssert; +extern SM_DEBUG_T SmExpensiveEnsure; + +#if SM_CHECK_REQUIRE +#if SM_CHECK_ASSERT +#if SM_CHECK_ENSURE + +cc -DSM_CHECK_ALL=0 -DSM_CHECK_REQUIRE=1 ... +</pre> + +<h2> Abnormal Program Termination </h2> + +The functions sm_abort and sm_abort_at are used to report a logic +bug and terminate the program. They can be invoked directly, +and they are also used by the assertion checking macros. + +<dl> +<dt> + void sm_abort_at(char *filename, int lineno, char *msg) +<dd> + This is the low level interface for causing abnormal program + termination. It is intended to be invoked from a + macro, such as the assertion checking macros. + + If filename != NULL then filename and lineno specify the line + of source code on which the logic bug is detected. These + arguments are normally either set to __FILE__ and __LINE__ + from an assertion checking macro, or they are set to NULL and 0. + + The default action is to print an error message to smioerr + using the arguments, and then call abort(). This default + behaviour can be changed by calling sm_abort_sethandler. +<p> +<dt> + void sm_abort_sethandler(SM_ABORT_HANDLER handler) +<dd> + Install 'handler' as the callback function that is invoked + by sm_abort_at. This callback function is passed the same + arguments as sm_abort_at, and is expected to log an error + message and terminate the program. The callback function should + not raise an exception or perform cleanup: see Rationale. + + sm_abort_sethandler is intended to be called once, from main(), + before any additional threads are created: see Rationale. + You should not use sm_abort_sethandler to + switch back and forth between several handlers; + this is particularly dangerous when there are + multiple threads, or when you are in a library routine. +<p> +<dt> + void sm_abort(char *fmt, ...) +<dd> + This is the high level interface for causing abnormal program + termination. It takes printf arguments. There is no need to + include a trailing newline in the format string; a trailing newline + will be printed if appropriate by the handler function. +</dl> + +<h2> Assertions </h2> + + The assertion handling package + supports a style of programming in which assertions are used + liberally throughout the code, both as a form of documentation, + and as a way of detecting bugs in the code by performing runtime checks. +<p> + There are three kinds of assertion: +<dl> +<dt> + SM_REQUIRE(expr) +<dd> + This is an assertion used at the beginning of a function + to check that the preconditions for calling the function + have been satisfied by the caller. +<p> +<dt> + SM_ENSURE(expr) +<dd> + This is an assertion used just before returning from a function + to check that the function has satisfied all of the postconditions + that it is required to satisfy by its contract with the caller. +<p> +<dt> + SM_ASSERT(expr) +<dd> + This is an assertion that is used in the middle of a function, + to check loop invariants, and for any other kind of check that is + not a "require" or "ensure" check. +</dl> + If any of the above assertion macros fail, then sm_abort_at + is called. By default, a message is printed to stderr and the + program is aborted. For example, if SM_REQUIRE(arg > 0) fails + because arg <= 0, then the message +<blockquote><pre> +foo.c:47: SM_REQUIRE(arg > 0) failed +</pre></blockquote> + is printed to stderr, and abort() is called. + You can change this default behaviour using sm_abort_sethandler. + +<h2> How To Disable Assertion Checking At Compile Time </h2> + + You can use compile time macros to selectively enable or disable + each of the three kinds of assertions, for performance reasons. + For example, you might want to enable SM_REQUIRE checking + (because it finds the most bugs), but disable the other two types. +<p> + By default, all three types of assertion are enabled. + You can selectively disable individual assertion types + by setting one or more of the following cpp macros to 0 + before <sm/assert.h> is included for the first time: +<blockquote> + SM_CHECK_REQUIRE<br> + SM_CHECK_ENSURE<br> + SM_CHECK_ASSERT<br> +</blockquote> + Or, you can define SM_CHECK_ALL as 0 to disable all assertion + types, then selectively define one or more of SM_CHECK_REQUIRE, + SM_CHECK_ENSURE or SM_CHECK_ASSERT as 1. For example, + to disable all assertions except for SM_REQUIRE, you can use + these C compiler flags: +<blockquote> + -DSM_CHECK_ALL=0 -DSM_CHECK_REQUIRE=1 +</blockquote> + + After <sm/assert.h> is included, the macros + SM_CHECK_REQUIRE, SM_CHECK_ENSURE and SM_CHECK_ASSERT + are each set to either 0 or 1. + +<h2> How To Write Complex or Expensive Assertions </h2> + + Sometimes an assertion check requires more code than a simple + boolean expression. + For example, it might require an entire statement block + with its own local variables. + You can code such assertion checks by making them conditional on + SM_CHECK_REQUIRE, SM_CHECK_ENSURE or SM_CHECK_ASSERT, + and using sm_abort to signal failure. +<p> + Sometimes an assertion check is significantly more expensive + than one or two comparisons. + In such cases, it is not uncommon for developers to comment out + the assertion once the code is unit tested. + Please don't do this: it makes it hard to turn the assertion + check back on for the purposes of regression testing. + What you should do instead is make the assertion check conditional + on one of these predefined debug objects: +<blockquote> + SmExpensiveRequire<br> + SmExpensiveAssert<br> + SmExpensiveEnsure +</blockquote> + By doing this, you bring the cost of the assertion checking code + back down to a single comparison, unless expensive assertion checking + has been explicitly enabled. + By the way, the corresponding debug category names are +<blockquote> + sm_check_require<br> + sm_check_assert<br> + sm_check_ensure +</blockquote> + What activation level should you check for? + Higher levels correspond to more expensive assertion checks. + Here are some basic guidelines: +<blockquote> + level 1: < 10 basic C operations<br> + level 2: < 100 basic C operations<br> + level 3: < 1000 basic C operations<br> + ... +</blockquote> + +<p> + Here's a contrived example of both techniques: +<blockquote><pre> +void +w_munge(WIDGET *w) +{ + SM_REQUIRE(w != NULL); +#if SM_CHECK_REQUIRE + /* + ** We run this check at level 3 because we expect to check a few hundred + ** table entries. + */ + + if (sm_debug_active(&SmExpensiveRequire, 3)) + { + int i; + + for (i = 0; i < WIDGET_MAX; ++i) + { + if (w[i] == NULL) + sm_abort("w_munge: NULL entry %d in widget table", i); + } + } +#endif /* SM_CHECK_REQUIRE */ +</pre></blockquote> + +<h2> Other Guidelines </h2> + + You should resist the urge to write SM_ASSERT(0) when the code has + reached an impossible place. It's better to call sm_abort, because + then you can generate a better error message. For example, +<blockquote><pre> +switch (foo) +{ + ... + default: + sm_abort("impossible value %d for foo", foo); +} +</pre></blockquote> + Note that I did not bother to guard the default clause of the switch + statement with #if SM_CHECK_ASSERT ... #endif, because there is + probably no performance gain to be had by disabling this particular check. +<p> + Avoid including code that has side effects inside of assert macros, + or inside of SM_CHECK_* guards. You don't want the program to stop + working if assertion checking is disabled. + +<h2> Rationale for Logic Bug Handling </h2> + + When a logic bug is detected, our philosophy is to log an error message + and terminate the program, dumping core if possible. + It is not a good idea to raise an exception, attempt cleanup, + or continue program execution. Here's why. +<p> + First of all, to facilitate post-mortem analysis, we want to dump core + on detecting a logic bug, disturbing the process image as little as + possible before dumping core. We don't want to raise an exception + and unwind the stack, executing cleanup code, before dumping core, + because that would obliterate information we need to analyze the cause + of the abort. +<p> + Second, it is a bad idea to raise an exception on an assertion failure + because this places unacceptable restrictions on code that uses + the assertion macros. + The reason is this: the sendmail code must be written so that + anywhere it is possible for an assertion to be raised, the code + will catch the exception and clean up if necessary, restoring + data structure invariants and freeing resources as required. + If an assertion failure was signalled by raising an exception, + then every time you added an assertion, you would need to check + both the function containing the assertion and its callers to see + if any exception handling code needed to be added to clean up properly + on assertion failure. That is far too great a burden. +<p> + It is a bad idea to attempt cleanup upon detecting a logic bug + for several reasons: +<ul> +<li>If you need to perform cleanup actions in order to preserve the + integrity of the data that the program is handling, then the + program is not fault tolerant, and needs to be redesigned. + There are several reasons why a program might be terminated unexpectedly: + the system might crash, the program might receive a signal 9, + the program might be terminated by a memory fault (possibly as a + side effect of earlier data structure corruption), and the program + might detect a logic bug and terminate itself. Note that executing + cleanup actions is not feasible in most of the above cases. + If the program has a fault tolerant design, then it will not lose + data even if the system crashes in the middle of an operation. +<p> +<li>If the cause of the logic bug is earlier data structure corruption, + then cleanup actions intended to preserve the integrity of the data + that the program is handling might cause more harm than good: they + might cause information to be corrupted or lost. +<p> +<li>If the program uses threads, then cleanup is much more problematic. + Suppose that thread A is holding some locks, and is in the middle of + modifying a shared data structure. The locks are needed because the + data structure is currently in an inconsistent state. At this point, + a logic bug is detected deep in a library routine called by A. + How do we get all of the running threads to stop what they are doing + and perform their thread-specific cleanup actions before terminating? + We may not be able to get B to clean up and terminate cleanly until + A has restored the invariants on the data structure it is modifying + and releases its locks. So, we raise an exception and unwind the stack, + restoring data structure invariants and releasing locks at each level + of abstraction, and performing an orderly shutdown. There are certainly + many classes of error conditions for which using the exception mechanism + to perform an orderly shutdown is appropriate and feasible, but there + are also classes of error conditions for which exception handling and + orderly shutdown is dangerous or impossible. The abnormal program + termination system is intended for this second class of error conditions. + If you want to trigger orderly shutdown, don't call sm_abort: + raise an exception instead. +</ul> +<p> + Here is a strategy for making sendmail fault tolerant. + Sendmail is structured as a collection of processes. The "root" process + does as little as possible, except spawn children to do all of the real + work, monitor the children, and act as traffic cop. + We use exceptions to signal expected but infrequent error conditions, + so that the process encountering the exceptional condition can clean up + and keep going. (Worker processes are intended to be long lived, in + order to minimize forking and increase performance.) But when a bug + is detected in a sendmail worker process, the worker process does minimal + or no cleanup and then dies. A bug might be detected in several ways: + the process might dereference a NULL pointer, receive a signal 11, + core dump and die, or an assertion might fail, in which case the process + commits suicide. Either way, the root process detects the death of the + worker, logs the event, and spawns another worker. + +<h2> Rationale for Naming Conventions </h2> + + The names "require" and "ensure" come from the writings of Bertrand Meyer, + a prominent evangelist for assertion checking who has written a number of + papers about the "Design By Contract" programming methodology, + and who created the Eiffel programming language. + Many other assertion checking packages for C also have "require" and + "ensure" assertion types. In short, we are conforming to a de-facto + standard. +<p> + We use the names <tt>SM_REQUIRE</tt>, <tt>SM_ASSERT</tt> + and <tt>SM_ENSURE</tt> in preference to to <tt>REQUIRE</tt>, + <tt>ASSERT</tt> and <tt>ENSURE</tt> because at least two other + open source libraries (libisc and libnana) define <tt>REQUIRE</tt> + and <tt>ENSURE</tt> macros, and many libraries define <tt>ASSERT</tt>. + We want to avoid name conflicts with other libraries. + +</body> +</html> |