[comp.software-eng] assert

cox@stpstn.UUCP (Brad Cox) (04/09/90)

In article <47859@lanl.gov> u096000@lanl.gov (Roger A. Cole) writes:
>Various articles have discussed the value of assert() in producing quality
>software.  As a long-time programmer with a relatively recent conversion
>to C, I am intrigued.  I am also, in keeping with observations in various
>articles, ignorant about assert().  (So are other programmers at my site.)

Although this paper doesn't mention assert() by name, you might start
with my paper, now being reviewed, for the Novembmer 1990 issue of
IEEE Software Magazine, "Planning the Software Industrial Revolution;
The Impact of OO Technologies".

One section of this paper, "Technologies: Programming Languages are not
sufficient", derives from Stepstone's experiences in building and supporting
commercial libraries of reusable object-oriented code libraries.  Since a
commercial operation is continually faced with extending code that already
has large numbers of clients, porting code to new platforms, and repairing 
defects, we were continually plagued by the problem of verifying that old 
client interfaces were not being changed while independently verifying that 
the new changes in fact complied to the new specification.

The system that we presently use to do this is entirely based on programmer
supplied assert() statements, of two distinct kinds: (1) structural (white
box) testing, with assert()s embedded inside the product, and (2) functional
(black box) testing, with assert()s contained in independent files that I
call Gauges, by analogy with the gauges that played such an important part
in Eli Whitney and his successors' vision of interchangeable parts.  The
article sketches a more ambitious system that deploys a knowledge 
reprersentation language as a specification/testing language.

If you're interested, send me a mailing address and I'll mail a copy.
I have no additional written information about how we use asserts() at this
point. However I'll be teaching this approach to specification/testing as 
part of a Objective-C course at the UC Santa Clara Techmart Extension the
last week of July.

u096000@lanl.gov (Roger A. Cole) (04/12/90)

This article presents some information about using assert() in developing
C software.  This information is based in part on responses from
comp.software-eng (see end of article for credits).  ** The perspective
presented below is one of improving code quality, not of program proving. **

assert(expr) is a macro which checks the 'expr'.  If 'expr' is false, an
error message is printed and 'evasive action' (often just exit(1); ) is taken.
The assert() macro typically is disabled if the preprocessor symbol NDEBUG
is defined.

Guidelines for using assert() include:
1.  assert()'s should be enabled only in development code, and never in
    production versions.
2.  Do not use assert() for checking user input or other 'unpredictable'
    data.  Use other checking mechanisms for these types of conditions.
3.  Use assert() for checking conditions which, if violated, would result
    in program malfunctions.

Valuable aspects of using assert() include:
1.  assert() provides a compact method for checking important conditions at
    run time.
2.  assert()'s are not present in production code, so they don't affect size
    and speed of production code.
3.  Since the assert()'s aren't actually removed from the source code, they
    a.  provide a method for trouble-shooting when a problem occurs; and
    b.  provide 'documentation' within the code of important conditions.

Weaknesses of using assert() include:
1.  Some conditions are difficult to check.  For example, the desirable check
    for a pointer argument would be "it's a valid pointer"; generally, a
    subset of this condition is checked--"it's not a NULL pointer".
2.  assert() operates at run time, and thus has limited capability for doing
    compile time checks.
3.  assert() can't be used to actually validate most algorithms--just
    conditions during the execution of an algorithm.
******************************************************************************
A simple example.  Assume 'pStruct' points to a structure containing several
items, with items tied in some way to a number.  Based on a user-supplied
item number, obtain in 'pItem' a pointer to the corresponding item.  If the
user supplies an invalid item number, keep retrying until a valid item number
is obtained; if an invalid item number is supplied to the look-up routine,
abort.  (This example is focused on using assert(), not on "proper" C--no
flames please :-) .)

    while (menuItem < 0 && menuItem > maxItem) {
       code to get a menu item number from a user;
       if (menuItem < 0 || menuItem > maxItem)  /* don't use assert() here */
	   printf("illegal item number\n"
    }
    pItem = lookUp(pStruct, menuItem);


ITEM *
lookUp(pStruct, itemNum)
STRUCT *pStruct;
int	itemNum;
{
    ITEM *pItem;

    assert(pStruct != NULL);
    assert(itemNum >= 0);
    assert(itemNum <= maxItem);

    code to get address of desired item;

    assert(pItem != NULL);
    return pItem;
}
******************************************************************************
My goals for implementing assert():
1.  The default method of 'make'ing the code for production must disable the
    checks.
2.  Even during development, minimize the impact on program size of using
    assert().
3.  Allow easily setting breakpoints to catch assertion failures.
4.  Impose as few restrictions as possible on the use of assert().

With these goals in mind, I have settled on the following form for assert(),
which I'm using with the Sun3 C compiler.  There is a xxx.h part and a
xxx.c part.

#ifdef DEBUG
/*----------------------------------------------------------------------------
* assert()
*
* DESCRIPTION
*	assert() evaulates an expression.  If the expression is non-zero
*	(i.e., "true"), then no action is taken.  If the expression is zero,
*	then the file name and line number are printed to
*	stderr and an exit(1); is done.  If a #define DEBUG hasn't been
*	done, then assert() does nothing.
*
* EXAMPLES
*	assert(pBuf != NULL);
*	assert(strlen(name) < 20), printf("%s\n", name);
*
* NOTES
* 1.	this uses a C 'trick'--if 'expr' is TRUE, then C doesn't execute what
*	follows the || .
* 2.	this macro definition comes from "C Traps and Pitfalls", Andrew
*	Koenig, Addison-Wesley, 1989, page 82.
*---------------------------------------------------------------------------*/
#   define assert(expr) \
		((void)((expr) || assertFail(__FILE__, __LINE__)))
#else
#    define assert(expr)
#endif



assertFail(fileName, lineNum)
char *fileName;
int lineNum;
{
    (void)fprintf(stderr, "assertFail: in file %s line%d\n",
						fileName, lineNum);
    exit(1);
}
******************************************************************************
Some discussion:

One suggestion was to print the expression which failed.  Although this can be
done in a relatively portable way, for either 'old-timey' or ANSI C
pre-processors, a potential pitfall made me decide not to implement this:  If
'expr' contains a "string", then macro expansion could get confusing.

My normal development and testing scheme uses -DDEBUG, so I generate checks
only if that symbol is defined.  I prefer this to the common strategy of
having using -DNDEBUG to disable generating checks.

Many other uses exist for using assert().  Some possible conditions are:
    "Milestones" during a complicated algorithm
    elapsed time for an algorithm doesn't violate a time constraint

******************************************************************************
My thanks to the following for their helpful input:

bobtl%toolbox.wv.tek.com@RELAY.CS.NET
D. Richard Hipp <drh@cs.duke.edu>
Peter Montgomery <pmontgom@math.ucla.edu>
john@jupiter.nmt.edu (John Shipman)
madd@world.std.com (jim frost)
joshua@Atherton.COM (Flame Bait)
cox@stpstn.UUCP (Brad Cox)
******************************************************************************

Roger Cole, Los Alamos National Laboratory  Internet: cole@luke.lanl.gov

phg@cs.brown.edu (Peter H. Golde) (04/13/90)

In article <48461@lanl.gov> u096000@lanl.gov (Roger A. Cole) writes:
>With these goals in mind, I have settled on the following form for assert(),
>which I'm using with the Sun3 C compiler.  There is a xxx.h part and a
>xxx.c part.
>
>#ifdef DEBUG
>#   define assert(expr) \
>		((void)((expr) || assertFail(__FILE__, __LINE__)))
>#else
>#    define assert(expr)
>#endif

Many C compilers will not put identical string literals into
the same piece of storage.  Using this method results in
many copies of the file name in your data space.  You
by be more efficient by using:

#ifdef DEBUG
static char _CurrentFileName = __FILE__;

    #define assert(expr) \
		((void)((expr) || assertFail(_CurrentFileName, __LINE__)))
#else

    #define assert(expr)

#endif

When doing this, you have to guard against including the .h file
multiple times.

--Peter Golde (phg@cs.brown.edu)

ka@cs.washington.edu (Kenneth Almquist) (04/14/90)

An implementation of assert() for UNIX should call abort() rather than
exit() when the assertion fails.  This way, a core dump will be generated
and you can debug the code without having to reproduce the error condition.
				Kenneth Almquist