A practical approach to programming with assertions

Embedded assertions have been recognized as a potentially powerful tool for automatic runtime detection of software faults during debugging, testing, maintenance and even production versions of software systems. Yet despite the richness of the notations and the maturity of the techniques and tools that have been developed for programming with assertions, assertions are a development tool that has seen little widespread use in practice. The main reasons seem to be that (1) previous assertion processing tools did not integrate easily with existing programming environments, and (2) it is not well understood what kinds of assertions are most effective at detecting software faults. This paper describes experience using an assertion processing tool that was built to address the concerns of ease-of-use and effectiveness. The tool is called APP, an Annotation PreProcessor for C programs developed in UNIX-based development environments, APP has been used in the development of a variety of software systems over the past five years. Based-on this experience, the paper presents a classification of the assertions that were most effective at detecting faults. While the assertions that are described guard against many common kinds of faults and errors, the very commonness of such faults demonstrates the need for an explicit, high-level, automatically checkable specification of required behavior. It is hoped that the classification presented in this paper will prove to be a useful first step in developing a method of programming with assertions. >


I. INTRODUCTION
A SSERTIONS are formal constraints on software system behavior that are commonly written as annotations of a source text. The primary goal in writing assertions is to specify what a system is supposed to do rather than how it is to do it. The idea of using embedded assertions as an aid to software development is not new. Indeed, more than 25 years ago Floyd demonstrated the need for loop assertions for verification of programs [l]. Luckham et al. elaborated the basic principles outlined by Floyd into an algorithm for mechanical program verification that was based on the generation and proof of simple assertions called veriJicatiun conditians [2]; this algorithm was implemented in the Stanford Pascal Verifier [3]. In addition to their use in formal verification, assertions have long been recognized as a potentially powerful tool for automatic runtime detection of software faults during debugging, testing and maintenance. More recently, assertions have been viewed Manuscript received February, 1993;revised October, 1994 as a permanent defensive programming mechanism for runtime fault detection in production versions of software systems [4]. As long ago as the 1975 International Conference on Reliable Software, several authors described systems for deriving runtime consistency checks from simple assertions [5]- [7]. For instance, in Stucki and Foshee's approach, the assertions were written as annotations of a FORTRAN source text, and a preprocessor was then used to convert the annotations to embedded self-checks that were invoked at appropriate times during the execution of the program.
Today, assertion features are available as programming language extensions, as programming language features, and in complete high-level formal specification languages. The C programming language [8] has traditionally provided a simple assertion facility in the form of the predefined macro assert, which is expanded inline into an if statement that aborts the program if the assertion expression evaluates to zero. Extensions have been proposed for other languages such as C++ [9] that originally provided no higher-level assertion capability [lo], [Ill. Still other programming languages, such as Turing [ 121 and Eiffel [ 131, provide assertion features as part of the language definition. Such languages can be used to specify system behavior at the design level. Many such languages are suitable not only for generating runtime consistency checks, but also for static analysis of semantic consistency, as in the Inscape environment [ 141. These uses of high-level formal specifications offer a practical alternative to mechanical proof of correctness.
Much of the recent research on automatic derivation of runtime consistency checks from assertions is exemplified by the work on Anna (ANNotated Ada), a high-level specification language for Ada [15]. This work includes (1) a method of generating consistency checks from annotations on types, variables, subprograms and exceptions [ 161, [ 171; (2) a method that uses incremental theorem proving to check algebraic specifications at runtime [ 181, [ 191; (3) a method of generating consistency checks that run in parallel with respect to the execution of the underlying system [20], [21]; and (4) a method of constructing large software systems based on algebraic specification of system modules [22].
Yet despite the richness of the notations and the maturity of the techniques and tools for programming with assertions, assertions are a development tool that has seen little widespread use in practice. There appear to be two reasons for this state of affairs: 1) The tools that have been developed to support programming with assertions fail to meet the needs of OO98-5589/95$04.00 0 1995 IEEE 2) the "average" software developer. They do not work well in conjunction with existing development tools, nor do they allow suitable flexibility in customizing assertion checks and in enabling or disabling checking at runtime. It is not yet well understood what kinds of assertions are most effective at detecting faults. This is due in part to a dearth of case studies that describe experience using assertions to build real systems. Consequently, most software developers have little idea of what information should be specified in assertions. This second point has left some programmers with the impression that writing assertions is akin to "writing the program twice". Yet an effective assertion does not merely restate something appearing in the program text; unlike the program, it succinctly and untibiguously states an important property of the program in a way that is understandable by anyone who reads it. Effectiveness of assertions is an especially important issue, as demonstrated by the wide variance in the fault detection capabilities of the self-checks that were written by participants in a case study conducted by Leveson et aZ. [23].
To address these concerns and begin making assertions a natural and practical aid to software development, I have been developing and using an assertion processing system called APP, an Annotation PreProcessor for C programs developed in UNIXI-based programming environments. APP has been designed to be easily integrated with other UNIX development tools. In particular, APP was designed as a replacement for the standard preprocessor pass of C compilers, making the process of creating and running self-checking programs as simple as building unannotated C programs. Furthermore, APP provides complete flexibility in specifying how violated assertions are handled at runtime and how much or how little checking is to be performed each time a self-checking program is executed. APP does not require complete specifications for its correct operation, and the assertions one writes for APP typically are not complete specifications in any formal sense.
An initial prototype of APP was completed five years ago. Since then, I have applied APP to the development of systems comprising around lO-20,000 lines of code. The assertion checks that were applied during the development of these systems automatically revealed a number of serious faults, and the diagnostic information they provided was often sufficient to quickly isolate and remove the faults. Based on this experience, it is possible to classify the kinds of assertions that were most effective at detecting faults.
The paper begins with a brief description of APP. The paper then presents a classification of assertions that is based on a retrospective analysis of the systems that were developed with APP and the faults that were detected by the generated checks.
To demonstrate the effectiveness of the proposed classes of assertions, the paper presents an analysis of the fault data from one of the systems that was developed. The paper concludes with a discussion of plans for future enhancements to and experimentation with APP. It is hoped that the classification ' UNIX is a registered trademark in the United States and other countries, licensed exclusively through X/OPEN corporation. presented in this paper will prove to be a useful first step in developing a method of programming with assertions.

II. APP
In Perry and Evangelist's empirical study, it was shown that most software faults are interface faults [24], [25]. Hence, APP was initially designed to process assertions on function interfaces, as well as simple assertions in function bodies.2 APP also supports a number of facilities for specifying the response to a failed assertion check and for controlling the amount of checking that is performed at runtime.

A. Assertion Constructs
APP recognizes assertions that appear as annotations of C source text. In particular, the assertions are written inside comment regions using the extended comment indicators /*@ . . . @*/. Informal comments can be written in an assertion region by writing each comment between the delimiter // and the end of the line.3 Each APP assertion specifies a constraint that applies to some state of a computation. The constraint is specified using C's expression language, with the C convention that an expression evaluating to zero is false, while a nonzero expression is true. To discourage writing assertion expressions that have side effects, APP disallows the use of C's assignment, increment and decrement operators in assertion expressions. Of course, functions that produce side effects can be invoked within assertion expressions, but such expressions should be avoided except in the rarest of circumstances (such as in the example of Section III-A.5 below), since assertions should simply provide a check on a computation rather than be an active part of it.
APP recognizes two enhancements to the C expression language within assertion regions: quantifiers and the operator in. Existential and universal quantification over finite domains can be specified using a syntax that is similar to C's syntax for for loops. The operator in can be used to indicate that an expression is to be evaluated in the entry state of the function that encloses the expression.4 These extensions are illustrated in the examples below. 'In keeping with the terminology of C, this paper uses the generic term "function" to refer to subprograms. A C function whose return type is void returns no result to its caller, while a non-void function in C always returns a result.
3The syntax of informal comments in assertion regions is the same as the comment syntax of C++.
4The operator in of APP is similar to the hook in VDM. whereby a hook is placed over a variable in a post-condition of an operation to refer to the value of the variable prior to the execution of the operation [26]. In a similar fashion, in the Z notation a variable can be unprimed or primed within an operation schema to refer, respectively, to the value of the variable in the state before or the state after the execution of the operation [27]. The operator in of APP differs from the operator in of Anna, in that the former always applies to the entry state of the enclosing function, while the latter applies to the observable state of the computation immediately prior to the assertion; in the case of in-annotations on function interfaces, this observable state is the entry state of the function. Thus, the operator has the same meaning in both APP and Anna within interface assertions and different meanings within body assertions. See the Anna Reference Manual for details [ZB]. APP recognizes four assertion constructs, each indicated by a different keyword: l assume-specifies a precondition on a function; l promise-specifies a postcondition on a function; l return-specifies a constraint on the return value of a function; and l assert-specifies a constraint on an intermediate state of a function body. The first three kinds of assertions are associated syntactically with function interface declarations, while the last kind is associated syntactically with single statements in function bodies. The assert construct corresponds to the assert macro found in many C implementations, in the sense that it constrains only the state of the program at the place of the assert. As was mentioned above, the choice of what assertion constructs to support in APP was governed primarily by a desire to provide a facility for specifying function interfaces. Having gained sufficient experience with these constructs, other constructs will be supported in future versions of APP, including assertions that apply over smaller regions of function bodies such as loops and nested blocks.
To illustrate these four constructs, consider first a function called square-root that returns the greatest positive integer less than or equal to the square root of its integer argument. Such a function can be specified as shown in Fig. 1. The first assertion is a precondition of square-root, as indicated by the keyword assume. It states that the implementation of the function assumes it is given a nonnegative argument; if this precondition is not satisfied at runtime, nothing can be guatanteed about the behavior of the function. The remaining two assertions are constraints on the return value of square-root, as indicated by the keyword return. Each return constraint declares a local variable (called y in the return constraints of this example) that is used to refer to the return value of the function within the constraint. The first return constraint states that the function returns positive roots. The second one states the required relationship between the argument and the return value. It is of course possible to conjoin these two return constraints into a single one; however, it is often useful to separate constraints not only for the sake of clarity, but especially when using APP'S severity level and violation action features (described below). Note that these assertions merely state what the function does, not how it does it.
Consider next a function called swap that swaps two integers without using a temporary variable. The function takes as arguments a pointer to each of the two integers, and it performs the swap through the pointers using a series of exclusive-or operations on the integer values. The function can be specified and implemented as shown in Fig. 2. The assumption states the precondition that the pointers x and y be non-null (and thus evaluate to true) and not equal to each other. The two postconditions, indicated by the keyword promise, use the operator in to relate the values of the integers upon exit from the function to their values upon entry. In particular, the first promise states that the exit value of the integer pointed to by x should equal the value pointed to by y upon entry, while the second promise states the reverse. The assertion in  the body of swap, indicated by the keyword assert, states an intermediate constraint on the integers at the point where one of the promises must become satisfied. As a final example, consider a function sort that sorts two arrays of integers. The specification of this function in Fig. 3 describes its required behavior at a level of abstraction that allows the use of any sorting algorithm to implement the function body. In this function, x is the unsorted input array, and size is the number of elements in the array. The function returns a pointer to the sorted result. The specification of sort int* sort(x,size) int* x; int size ; uses quantifiers to state both the obvious ordering requirement of the result (but disallowing duplicate elements) as well as the requirement that the result must be a permutation of the input array; note that informal comments are used in the figure to show exactly where these requirements are stated. An APP quantifier can be thought of as a sequential iterator over a set of values, with the quantified expression evaluated for each element in the set; these individual evaluations are combined in the obvious way for the particular kind of quantifier. Syntactically, a quantified expression resembles a for loop in C. Indeed, as will be described further in Section II-C, APP translates each quantified expression into a for loop, with nested quantifiers translated into appropriately nested for loops.
As shown in Fig. 3, an APP quantifier specification contains the existential specifier some or universal specifier all, followed by a parenthesized sequence of three fields separated by semicolons. The first field is a declaration of the variable over which quantification is to be performed, including its name, type and the initial value of the set. The second field is a condition that must be true in order for the iteration to continue. The third field is an expression that describes how to compute the next value in the set. Thus, the first universally quantified expression in the return annotation says that each element but the last of the result must be less than its successor element. The second universally quantified expression contains a nested existentially quantified expression to state that for all elements of the input array x, there exists an equal element of the result array.
Quantifiers can be used to quantify over any finite set of values, not just a range of integers. For instance, a quantifier over the elements of a linked list would specify the head of the list as the initial value, a non-null next pointer as the continuation condition, and a dereference of the next pointer to retrieve the next value in the set. Since it is considered impractical for runtime checks to quantify over large domains such as the full range of integers, the syntax of quantifiers was designed to encourage careful quantification over reasonably sized domains. Experience has shown that the syntax achieves this aim without reducing the expressive power of traditional quantifiers of first-order predicate logic.

B. Violation Actions, PredeJned Macros and Severity Levels
APP converts each assertion to a runtime check, which tests for the violation of the constraint specified in the assertion. If the check fails at runtime, then additional code generated with the check is executed in response to the failure. The default response code generated by APP prints out a simple diagnostic message such as the following, which indicates the violation of the first promise of function swap: promise violated: file swap.c, line 6, function swap The default response provides a minimal amount of information needed to isolate the fault that the failed check reveals. However, the response to a violated assertion can be customized to provide diagnostic information that is unique to the context of the assertion. This customization is accomplished by attaching a violation action to the assertion, written in C.
For instance, in order to determine what argument values cause the first promise of swap to be violated, the promise can be supplied with a violation action as shown in Fig. 4 (using C's library function printf for formatted output). Using some preprocessor macros that are predefined by APP, this violation action can be enhanced as shown in Fig out the same information that is printed out by the default violation action. The macro --ANNONAME--expands to the keyword of the enclosing assertion. The macro --FILE-expands to the name of the source file in which the enclosing assertion is specified. The macro --ANNOLINE--expands to the starting line number of the enclosing assertion. The macro --FUNCTION--expands to the name of the function in which the assertion is specified.
In addition to violation actions, APP supports the specification of an optional severity level for each assertion, with 1 being the default and indicating the highest severity. A severity level indicates the relative importance of an assertion and determines whether or not the assertion will be checked at runtime. Severity levels can be used to control the amount of assertion checking that is performed at runtime without recompiling the program to add or remove checks. For example, the assertions on square-root can be given severity levels as shown in Fig. 6. Under level-l checking at runtime, only the assumption and the second return constraint would be checked. If one of these assertions were violated at runtime, it might then be desirable to re-execute the program under level-2 checking, in order to additionally enable checking of the first return constraint and obtain more information about the cause of the assertion violation. Level-O checking disables all checking at runtime. Severity levels are useful for implementing the "two-dimensional pinpointing" method of debugging described by Luckham,Sankar,and Takahashi [29]. The mechanism for controlling the checking level at runtime is described below. The macro --DEFAULTACTION--expands to the default violation action, while the macro --DEFAULTLEVEL-expands to the default severity level. Both of these macros can be redefined to alter the default processing of APP.
C. Generating and Running Self-Checking Programs APP translates an input annotated C program into an equivalent C program with embedded assertion checks. APP has the same command-line interface as cpp, the standard preprocessor pass of C compilers (which are usually called cc). In particular, APP accepts the macro definition options -D and -U and the interface or "header" file directory option -1, and it performs all of the macro preprocessing of cpp in addition to its assertion processing. Hence, to compile an annotated C source file, APP is simply invoked through cc by using appropriate command-line options that tell cc to use APP as its preprocessor pass; such options are a standard feature of every C compiler. Furthermore, standard build tools such as make [30] and nmuke [3 l] can be used to build executable selfchecking programs, with only slight modifications to existing makefiles or build scripts. These build techniques are illustrated in Fig. 7, which depicts nmake compiling the n source files of some program with APP and then linking the resulting object files together into a self-checking executable. This method of integrating assertion processing with standard C development tools greatly simplifies the generation of selfchecking programs and requires almost no change to one's customary use of UNIX and C programming environments.
Execution of a self-checking program proceeds with checking performed at the severity level specified by the user in the UNIX shell environment variable APP-OPTIONS (or at the default level if the environment variable is undefined). Note that a self-checking program can be treated like any other program in a C programming environment. For instance, a self-checking program can be run inside a symbolic debugger such as dbn. The debugger can be used to set breakpoints at assertions, single-step through them, trace their execution, and so on, all relative to the contents and line numbering of the original source files in which the assertions were specified.
APP translates its input in a single pass without building an internal parse tree. However, APP uses internal text buffers and buffer stacks for temporary storage of certain regions of the translated source text. This use of buffers arises from the need to order some regions differently from the order in which they appear in the input source text: l Function pre-and postconditions we= syntactically before. their associated function body. Yet the precondition checks must be inserted immediately after the declarations in the function body, while the postcondition checks must be inserted at the end of the function body (with return statements in the original source translated to gotos to the postcondition checks). l In expressions require the generation of temporary variables for their evaluation, and these temporary variables must be placed at the very beginning of the function body to ensure that the entry values of the expressions are captured before any other computation takes place within the body. l Each quantified expression is translated into a loop that evaluates the quantified expression into a temporary variable, followed by a usage of the temporary variable in the surrounding context. Multiply-nested quantified expressions, such as the one shown in Fig. 3, are translated in such a way that the loop/usage pair for an inner quantified expression appears between the loop and the usage for the next outer quantified expression. These regions of translated source text are saved in buffers until it is appropriate to output them.

III. A CLASSIFICATION OF ASSERTIONS
I have been using APP for five years in the development of a number of software systems, including APP itself. The assertions written for these systems have proven effective at discovering faults. Indeed, the effort of constructing the assertions has repeatedly paid off in quick, automatic detection and isolation of faults that otherwise would have consumed several hours of effort using more primitive debugging tools such as core dumps and symbolic debuggers. Not only have the assertions provided a powerful fault detection capability, but the process of writing the assertions in the first place appears to have resulted in much more careful development of implementation components.
Based on this experience, it now would be fruitful to examine these systems and to characterize the kinds of assertions that were most effective in uncovering faults. The categories of assertions described in this section guard against many common kinds of faults and errors. Yet the very commonness of such faults demonstrates the need for an explicit, high-level, automatically checkable specification of required behavior. Table I summarizes the assertion classification, which is or-ganized according to the kind of system behavior each class of assertion is intended to capture.
The examples provided for each category are abstracted from actual assertions. A few of the assertions are used to overcome inherent weaknesses in the type system of C; in certain cases such assertions would not be needed in programs written in languages that provide a strong type model, such as AdaS However, most of the assertions described below express constraints that are too complex to express in the type or data model of common programming languages. For instance, programming languages rarely, if ever, provide features for explicit specification of data consistency at the level of a function interface.
When using this classification, it should be remembered that the general goal in writing any assertion should be to specify some required constraint or relationship of the system succinctly and at a relatively high level of abstraction. It is not necessary that this specification be complete in any formal sense; a specification of only the most important aspects of a constraint or relationship can provide a high degree of fault detection ability. Given a particular informal constraint on a function, it may be difficult sometimes to develop a formal assertion of the constraint that is less complex than the function implementation itself. Even so, the redundancy provided by such an assertion may prove useful, in the sense that an inconsistency between the assertion and the function implementation would be symptomatic of some incompleteness in one's understanding of the informal constraint on which they are both based.

A. Speci$cation of Function Interfaces
The primary goal of specifying a function interface is to ensure that the arguments, return value and global state are valid with respect to the intended behavior of the function. The common characteristic of all function interface constraints is that they are stated independently of any implementation for the function. That is, they describe function behavior at the level of abstraction seen by the callers of the function. The constraints described in this section are special forms of traditional preconditions and postconditions. 1) Consistency Between Arguments: For each function in the system, specify how the value of each of its arguments depends on the values of its other arguments.
Function arguments are often interdependent, even though such mutual dependencies cannot be specified directly in the programming language. Assertions can be used to specify mutual consistency constraints. In most cases these assertions will be preconditions on arguments passed by value. For instance, consider a language processing system that uses a function called store-token to store unique copies of the tokens found in an input stream. As shown in Fig. 8, the function takes as arguments an enumeration value specifying 5Flater et aZ., describe a system called Robusr C that automatically instruments C programs for runtime detection of the most common classes of C coding faults, such as violation of array bounds [32]. And a number of research techniques and commercial tools are available for automatically detecting memory-related coding faults in C programs at runtime (e.g., see Austin  the kind of token and a pointer to the token string. The Functions in procedural languages often have side effects. Assertions can be used to specify the key ways in which a function changes the global program state. For instance, consider a language processing system that uses a routine called deletename to remove entries from a global symbol table called symbols. The specification of delete-name is shown in Fig. 9; assume that symbols is a hash table that is searched using the routine hashget, which returns a nonzero pointer to a table entry if successful and zero if unsuccessful. The assumption states that the argument to delete-name should have an entry in symbols. In particular, upon entry to deletename a call to hashget with the name argument must return a nonzero or "true" result. The promise states that delete-name removes the record for its argument from symbols, so that upon exit from deletename, a call to hashget with the name argument must return zero, and thus the negation of the hashget result (obtained using the negation operator !) must be true.
'C arrays are always indexed starting at zero. 4) The Context in Which a Function is Called: For each function in the system, specify how the values of its arguments and the values of the global variables visible to it govern when it is valid for the function to be called.
Sometimes a function should be called only within certain processing contexts, even though the function may behave correctly within all contexts. Assertions can be used to ensure that functions are called in appropriate contexts. For instance, Fig. 10 shows a function print-warning that is used by a language processor to output detailed warning messages only if a certain command-line option has been given to the processor (as indicated by a nonzero value for the global variable warnings-on). The function always generates a correct warning message for any combination of code number, line number and file name. But the assumption is used to check that the function is called only when the appropriate command-line option has been specified. 5) Frame Specijcations: For each function in the system, specify each case when the value of an argument passed to the promise strcmp(in name, in strdup(name)) == 0; Fig. 11. Specifying a frame constraint for function deletename of Fig. 9. function by reference, or the value of a global variable visible to the function, is to be left unchanged by the function. Functions are often required to leave certain data unchanged. Such requirements, which are called frame specifications, are usually implicitly derived or assumed in proof-based reasoning systems, but for purposes of runtime checking they must be stated explicitly. Assertions can be used to state a system's frame specifications. For instance, to specify that the function deletename shown in Fig. 9 should not modify its argument (a string passed by reference), the promise shown in Fig. 11 can be added to its-interface assertions. The promise uses the standard C library function strcmp (which returns zero when its two string arguments are equal) to ensure that the values of the string upon entry to and exit from the function are equal. Notice that it is not sufficient to refer to the entry value of the string with the expression in name, since in name evaluates to the entry value of the pointer name, not the entry value of the string pointed to by name. The standard C library function strdup creates a heap-resident copy of a string, and thus the expression in strdup(name) can be used to provide a pointer to the entry value of the string pointed to by name.7 Notice also that because the function might modify the pointer value of name, the exit value of name may differ from its entry value. Thus, the strcmp expression checks mat the location in memory designated by name upon entry to the function (i.e., in name) still contains the value it had upon entry (i.e., in strdup(name)).
This example also illustrates a rare situation where it is desirable for an assertion expression to produce a side effect, in this case an allocation of heap memory. However, APP is able to compensate for this particular side effect, because it ensures that any heap memory that is dynamically allocated as a result of the evaluation of an assertion expression is deallocated upon exit from the function enclosing the assertion. 6) Subrange Membership of Data: For each function in the system, specify all subrange constraints on the values of its arguments, return value(s) and global variables that are of numeric type. Also specify all subrange constraints on the values used to index its array-valued arguments, return value(s) and global variables.
C does not allow the specification of subrange constraints on numeric types. This weakness can be overcome with simple assertions that specify appropriate bounds on the values of numeric data. However, this weakness becomes particularly troublesome in C's treatment of arrays, which are indexed by integers. C has a rather weak notion of array, which is just a region of memory that is referenced through a pointer. Overrunning array bounds in C is thus very common, especially when handling strings (character arrays), which in C require an additional string-termination character that is frequently overlooked. Assertions can be used to specify 7Note that it is nor necessary to use the expression in strdup(in name), since the operator in distributes across all subexpressions of the expression to which it is applied. constraints that guard against the mishandling of arrays. For example, Fig. 12 shows a function fill-and-truncate that is used to fill a global string buffer with a line of input text, truncating the line if it exceeds the size of the buffer. The promise states one of the constraints the function must satisfy, namely that according to C programming conventions, it must place the string-termination character '\O' at the end of the buffered text, but still within the bounds of the global buffer. That is, there must be a subrange (of size one) of the array buffer that contains the string-termination character.8 This constraint is expressed using an existentially-quantified expression to state that upon exit from the function some element of the buffer must contain the string terminator. In particular, the expression states that there exists some i between zero and BUFFSIZE-such that the ith character of buffer is the string-termination character. 7) Enumeration Membership of Data: For each function in the system, specify all membership constraints on the values of its arguments, return value(s) and global variables that are of enumeration type.
As is the case with arrays, enumeration types in C are also weak, in that they are type compatible with integers. In particular, enumeration literals are interchangeable with their internal integer values, and any integer can be used where an enumeration literal is required. Assertions can be used to ensure that variables of an enumeration type contain valid values of the type. For instance, the function store-token shown in Fig. 8 takes an argument whose value belongs to an enumeration type. The assumptions shown in Fig. 13 can be added to the function's interface assertions. The two assumptions are equivalent, and they both check that the function is given valid values of the enumeration type Token-Kind.' 8) Non-Null Pointers: For each function in the system, specify which pointer-valued arguments, return value(s) and global variables must not be null.
C programs make very extensive use of pointers to reference arrays and strings, to access dynamically allocated storage, and to pass arguments to functions by reference. Assertions can be used to specify when pointers should be non-null. Such 8Anything stored after the first string-termination character would be ignored by C's string-processing functions, so it is not necessary that the function fill the unused portion of the buffer with string-termination characters each time it is called.
9 Of course, the second form of assertion must be used for enumeration types whose literals are given explicit, noncontiguous internal values, such as enum I I (kind == string kk t.okenCOl == "")); Fig. 14. Specifying a pointer constraint for function store-token of Fig. 8 assertions are especially useful because the self-checks they generate can provide information prior to the aborted execution and core dump that usually result from dereferencing a null poititer. The assumption "assume x && y && x ! = y" specified on the function swap shown in Fig. 2 illustrates an assertion that constrains a pointer argument to be non-null. It is also necessary to first state that a pointer is non-null before specifying constraints on the data to which the pointer is pointing. For instance, the assumption on function store-token of Fig. 8 should be strengthened as shown in Fig. 14 to ensure that the string pointer token is non-null (and thus "true") before it is dereferenced in the array subscripting operations.

B. Specijcation of Function Bodies
Function bodies often contain long sequences of complex control statements, which offer many opportunities for introducing faults. Assertions that are stated in terms of a particular function implementation can be used as "enforced comments" to guard against such faults. 1) Condition of the Else Part of Complex If Statements: For each if statement in the system that contains a final else part, explicitly specify the implicit condition of the final else part as an initial assertion in that part.
The implicit condition of the default branch of an if statement (i.e., the final else part) is often intended to be stronger than the simple negation of the disjunction of the explicit, nondefault conditions. Assertions can be used to specify the intended default condition explicitly. Suppose that the function store-token shown in Fig. 8, rather than taking an argument indicating the kind of token it is given, instead makes that determination in its implementation. The function might use an lf statement like the one shown in Fig. 15. The final, default else branch of this if statement will be executed for all values of token whose first character is not a digit or lower-case letter. But since the function should only be processing string tokens in the default branch, the assertion restricts the execution of the default branch to those situations in which the first character of token is the double-quote character.
2) Condition of the Default Case of a Switch Statement: For each switch statement in the system that contains a default case, explicitly specify the implicit condition of the default case as an initial assertion in that case. For each switch statement without a default case, provide a default case containing an assertion that always evaluates to false.  As is true of if statements, switch statements often contain a default case that is intended to operate on only a subset of the possible domain of the default case, especially when the switch is performed on a value of an enumeration type. Assertions can be used to describe the limited domain, in a manner similar to the way the default else branch was constrained in the if statement of Fig. 1.5. Since it is wise to supply default cases for switch statements even if they should never be executed, a special form of this kind of assertion is an assertion that always evaluates to false, as shown in Fig. 16.
3) Consistency Between Related Data: For each function body in the system, specify consistency constraints on mutually dependent data at frequent intervals within the code that manipulates that data.
It is often necessary to process related data in different ways and ensure that the data remain consistent after processing. For instance, consider a furiction that creates an entry in a priority queue before performing other processing on the new entry. The function might first use a loop to find where in the queue the new entry belongs. The function might then use a separate check to determine if the new entry was placed at the end of the queue, in which case the queue's tail pointer would need to be updated. An assertion like the one shown in Fig. 17 can be used to ensure that the two parts of the insertion code have treated the tail pointer consistently. The assertion requires the tail pointer to point to the new entry if the new entry contains a null forward link after insertion. Assertions can be used to summarize periodically the effect of a complex function at key places in its body. The assertion in the body of function swap shown in Fig. 2 illustrates this kind of assertion. Because the manipulations of the integer arguments are unintuitive, the body assertion helps to identify the exact point at which one of the promises of swap must become satisfied.

IV. EXPERIENCE
The YeastlO event-action system [35] serves as an excellent example of a software system developed with APP. Yeast comprises roughly 12,000 lines of C, yacc and Zen: code.
"Yet another Event-Action Specification Tool. My half of the source code contains 116 assertions in 95 assertion regions. Of these 95 assertion regions, 39 are function interface specifications, which contain a total of 61 assertions. The self-checking executables are 3.7% larger than the nonself-checking executables, and they run with no discernible difference in speed.' ' Since first releasing Yeast to other people within AT&T, we have discovered and removed 19 faults, all of which were interface faults. Ten of these faults were located in my half of the code. In their empirical study, Perry and Evangelist identified 15 different kinds of interface faults in the software change request data that they analyzed [24], [25]; Table II summarizes their interface fault classification." Table III characterizes each of the 19 faults in Yeast according to the Perry and Evangelist fault classification of Table II. Table III also identifies which kinds of assertions in the classification of Table I revealed each fault. The faults are listed in increasing chronological order of discovery. Included in the description of each fault is an indication of whether or not the fault was in a function or functions that had been specified with assertions; this information indicates that there was no clear correlation between the location of a fault and the location of the assertions that revealed it. " In the version of this paper that appeared in the ICSE-14 conference proceedings, the size increase was erroneously reported as 12%. which included both assertion checks and code that was inserted by the compiler to support the use of a symbolic debugger.
"The reader is referred to Perry and Evangelist's papers for a more detailed description of their fault classification. Other useful fault classifications have been described by Ostrand     Of the 19 faults, 8 were discovered by one or more assertion violations. Of the 11 faults that were not detected by assertions, 6 could have been caught by assertions that were not written; Table IV shows which classes of assertions were needed to detect these faults. Of the remaining 5 faults that were not detected by assertions, faults 4 and 11 were detected by a dynamic storage certification routine, while faults 6, 10, and 17 could only be detected by assertion features more powerful than those currently supported by APP (such as event sequencing constraints).
Nearly 50 percent of the faults in the Perry and Evangelist study were faults of inadequate error processing (F8), con-struction (Fl) or inadequate functionality (F2). As Table III  and Table IV show, the assertions in the classification of Table  I can be used to guard against these common kinds of faults as well as many of the other kinds of faults described by Perry and Evangelist. Table V summarizes these observations  by noting which assertions from Table I are best-suited to detecting each kind of interface fault. In some cases the faults described by Perry and Evangelist call for broader classes of assertions, such as preconditions or postconditions; these broader classes are noted in Table V where applicable. V. CONCLUSION This paper has described an assertion processing system for C and UNIX called APP. APP provides a rich collection of features for specifying not only the assertions themselves but also the responses to failed runtime assertion checks. APP fits easily into the process of developing C programs, requiring minimal change to one's accustomed use of C and UNIX programming environments. APP can process approximately 20,000 lines of C code per CPU-minute on a Sun-4 workstation. The assertion checks generated by APP introduce negligible time and space overhead into the generated self-checking programs. APP is currently being licensed to universities for research use under certain terms and conditions. This paper has also described a classification of assertions that is based on experience using APP. Systems that are specified with assertions need not contain a complete specification of the system, in any sense of the word "complete". Incomplete specifications that capture the essence of the intended behavior are quite sufficient for reliably detecting software faults at runtime. Experience with APP has demonstrated that faults in reasonably well-annotated code (with at least every function interface supplied with assertions) often generate multiple assertion violations. One might think that diagnostic messages from multiple violations would be useless, since diagnostics generated by violations subsequent to the first violation might not provide reliable information. However, diagnostics from multiple violations have often provided useful information about the context of the revealed fault, making fault elimination in many cases a simple matter of interpreting the diagnostic messages without the aid of any other debugging tool.
The design of APP was influenced to a great extent by the previous work on Anna. Anna is a rich specification language, and its large number of features were a natural outgrowth of the large number of programming constructs provided in Ada. This is especially noticeable with respect to packages, which are arguably the most important feature of Ada, providing a powerful means for structuring a software system and encapsulating its data types. The availability of packages in Ada required a means for specifying the behavior of a package in totality, both as an algebraic data type and as an object with state. ConsequentIy, a significant subset of Anna deals with specification of package state and package state transitions, and with axiomatic specification of the behavior and result of combining package operations. Just as the richness of Anna derives from the richness of Ada, the simplicity of APP'S specification language is well-matched to the simplicity of C. In C the primary construct of interest is the function, and thus APP has been designed primarily to support the specification of function behavior.
What APP lacks in its diversity of specification constructs, it more than makes up for in the greater flexibility it provides to the developer of self-checking programs. In Anna, the response to a failed annotation check is defined by the Anna Reference Manual (which specifies the response to be the raising of the predefined exception ANNA-ERROR) and by the Anna Transformer and Anna Debugger tools (which add generic diagnostic information and a simple debugging interface for running self-checking programs). ANNA-ERROR provides some measure of programmability for defining the response to a failed check. However, it does not identify which particular annotation or annotations were violated, and handlers for ANNA-ERROR may not always have access to the context of a violated annotation (e.g., the values of relevant variables), depending on where the handlers are defined. In contrast, APP provides violation actions in order to allow the specifier complete flexibility in defining the response to a failed assertion check, allowing selection from a wide range of possible responses. The response to a failed assertion check can be tailored to the special nature of the application, to the development task at hand, to production versions of the system, or to other aspects of system development.
Furthermore, APP provides severity levels in order to give the specifier greater control over the amount of assertion checking that is performed at runtime, without having to modify the program or rebuild the self-checking executable. Finally, while Anna supports quantification only over types, APP provides a quantification syntax that is more convenient for describing a set of iterated values and that leads to more computationally-feasible runtime checks.
An interesting thing to note about the assertion classification described in this paper is the absence of certain classes of assertions that are important for program verification, such as loop invariants or inductive assertions. Inductive assertions can be notoriously difficult to construct and do not always capture one's intuitive understanding of intended system behavior. In contrast, the assertions described in this paper represent an attempt to formalize such intuitions. Thus, it remains to be seen whether or not assertions that are constructed especially for program verification are also useful for runtime fault detection.
APP will be extended to support other kinds of assertions and higher-level abstraction facilities. New features will include constraints on types and global variables, a richer abstraction of arrays and other abstract data types, and constructs for specifying interactions between program units that are larger than functions (such as specification of the behavior of sequences of function calls). In addition, a version of APP will be developed for C++. The C++ version of APP will provide additional specification constructs that are suited to specification of class behavior; a good starting point for the design of these constructs would be the package specification features of Anna.
Until verification and other sophisticated static analysis methods become practical for large systems comprising many modules and several thousand lines of code, developers of large systems must rely on alternative means of identifying and removing faults in their systems. Assertion checking is one such alternative-it is powerful, practical, scalable and simple to use. While it is hoped that others can benefit from the experience described in this paper, in the future more comprehensive, controlled experimental studies on larger systems with multiple developers will help to further reveal the most effective techniques for using assertions to improve the quality and reliability of software systems.