Last update PvD

Assert Statement


An Assert Statement specifies a rule which should be true at that particular point in an algorithm.  The rule acts as an invariant relation (predicate) and is expressed as a condition.
The Assert statement provides an explicit way to improve software (and system) design and to verify system integrity at run-time.  For robust systems it has to be combined with some replacement/­fall-back mechanism (non-trivial, depends on context;  not discussed here).



At design time the Assert indicates which preconditions are assumed and required for proper processing.  It explicitly states what a good designer takes into account when designing (it makes manifest what is latent).  Because of the precondition, a more efficient algorithm may be applied.

The Assert statement should be distinguished from (input) validation or any error recovery:  those types of errors should be accidental and recoverable, whereas failing an Assert implies an internal software error (potentially system design error).  The Assert checks for conditions that should be true no matter what input a user types in, or what state the (hardware) system is in: it verifies system consistency !
High level languages usually provide some assertions already at compile time (e.g. in particular type checks).  Most are only static, whereas dynamic checks are also required (an array bound check is in fact a perfect example of an Assert).


At run-time the Assert statement will check whether the specified condition holds;  the result of the test can be used as follows:

As the intention of the Assert statement is never to take corrective actions during run-time, but to invoke actions to ease locating the bug (e.g. break, dump).  The earlier processing is stopped, the less confusion is created and the easier the bug can be found (it avoids looking at bug effects rather than bug causes).  On the other hand, if the Assert holds, conditions for further correct processing are satisfied and results should be reliable.


The Assert rule or condition is expressed as a boolean expression.  It implies that logical predicates like 'for all' or 'there exists' are not available (without additional coding).  If the expression becomes very complicated one can split the boolean expression over multiple Assert statements (helps in identifying the failing condition).


As there will be many Assert-statements, one needs some form of identification for each Assert, in particular to identify the one that fails.  Any form of identification will do, but a unique number proves to be most effective.  A simple numbering scheme, for example a four-digit number 'RRAA' where 'RR' stands for the routine number and 'AA' stands for an individual identification number within each routine, is sufficient.  You can also let a preprocessor generate numbers for you, but that implies that insertion of a new Assert statement causes a renumbering.
Note that:


Assert statements should be inserted at all critical points in design documents, pseudo code, programs and subroutines.  Examples of such points are the entry and exit in subroutines (modules), at reception and transmission of messages and at the start and exits of repetitive constructs.
Also, any program location which should never be reached by program control ('otherwise'-clauses in 'case' statements, the last 'else' in 'else if' constructs):  code an Assert fail there.


The Assert statement can also be used for tracing:  by storing the Assert Identification in a buffer even when the Assert holds (i.e. the Assert condition resulted in 'true') one gets a list of the last Assert statements successfully passed.  When printing the Assert Ids from the buffer at an Assert fail, the Ids reflect the path of control leading to the Assert failure.
Or one can just do tracing by printing the buffer when it is full.


At development time it is advisable to include Assert statements in all (even low level) routines.
For operational systems, Assert statements could be 'commented out' (never delete the Assert statements completely from the source;  they provide valuable information for maintenance).  However, if the system has been tested sufficiently, it should be no problem to leave Assert statements in:  they may serve as a built-in trace.  Alternatively, one may alter the Assert routine to write to an error log, and continue processing (with undefined results).


The preferred implementation of an Assert statement is a routine accepting an identification (number) and a boolean expression.  This allows easy adaptability.  Something like the following (pseudo code example):

Const ASSERT_LEN = 200;
Type ASSERT_ID = integer;
Var ASSERT_BUFFER: array[0..ASSERT_LEN-1] of ASSERT_ID;  {Static Global vars}
Function ASSERT( ID :assert_id, COND :boolean );
  ASSERT_BUFFER[ ASSERT_INDEX ] := ID;   {Store ID first}
  ASSERT_BUFFER[ ASSERT_INDEX ] := 0;    {Store reference}
  if not COND then DUMP_AND_STOP;        {Assert Fail}
end; {Func ASSERT}
begin {program start}
ASSERT( 273, (i >= 1) and (i <= N) );
.. else ASSERT( 365, False );
The mechanism with the circular buffer provides the last (199) Assert (trace) points (of which 198 were Ok).  When the assert fails, scan the buffer for the reference value 0, and start printing Assert_Ids from there (modulo Assert_Len).
This mechanism is also easy adaptable:  you may increase the buffer size, dump the buffer every time when the index wraps, call specific data dump routines on assert fail, write to log-file and continue processing, etc.


There is very good operational experience using the Assert statements.  For example in new, large and complex real-time software:  during development and initial field use there were a great many 'Assert-fails' (sometimes because the Asserts were a bit too simple);  however, the dumps enabled quick location of bugs and after some months the system became very reliable (99.9999% availability for over 10 years).

This mechanism has proven to be very effective for finding sporadic errors (non-reproducible conditions).  And the Assert statement is a cheap mechanism. 

The Assert method has also been used several times to debug and trace software where no I/O is acceptable, like in 'critical sections' and/or real-time parts in software and in system software (device drivers).  Of course you have to make sure to enter such a section with an empty Assert_Buffer which is also sufficiently large.