diff options
Diffstat (limited to 'contrib/sendmail/libsm/assert.html')
-rw-r--r-- | contrib/sendmail/libsm/assert.html | 359 |
1 files changed, 0 insertions, 359 deletions
diff --git a/contrib/sendmail/libsm/assert.html b/contrib/sendmail/libsm/assert.html deleted file mode 100644 index a3251998db6b..000000000000 --- a/contrib/sendmail/libsm/assert.html +++ /dev/null @@ -1,359 +0,0 @@ -<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> |