C-DbC

This module implements Design by Contract and a disciplined exception mechanism for the C language.

1. Install

To install all that there is required is to copy dbc.h to a standard include directory (e.g. /usr/local/include), and both libraries files (the sequential and the POSIX thread-safe version) to a standard library directory (e.g. /usr/local/lib).

2. Using

To use this library you should include dbc.h (#include <dbc.h>) and link the program with the appropriate library version (-ldbc or -lpthreaddbc). Please note that in this library DbC is defined at compile time, hence a change in the detection of assertion failures requires the recompilation of the applicable modules.

3. Design by Contract (DbC)

Using DbC is simply a matter of using the appropriate function -- require, ensure, invariant, check -- in the places where that meaning is expected to be verified.

This library allows also the possibility of attaching an error code (other than the internal assertion type code) to an assertion failure.  Such functions are suffixed with '_e'.

An assertion failure throws an exception (see next section), being the default behavior to create a core file (which can be read with a debugger such as gdb, to view the complete function stack trace at the time of the failure). Please note, that to create core files it might be required execute appropriate commands in the console shell (see dbc.h).

3.1. Fine-tuning assertion activation/deactivation

It is possible to fine-tune assertion verification using C macros (preprocessor) mechanism. To that goal, the programmer might (at a unit of compilation scope), define the following macros:

NDEBUG deactivate all assertions (regardless of the definition of DBC_* macros)
DBC_ALL activate all assertions (default behavior equivalent as to when NDEBUG is not defined)
DBC_PRECONDITIONS activate precondition checking
DBC_POSTCONDITIONS activate postcondition checking
DBC_INVARIANT activate invariant checking
DBC_CHECK activate check checking

For example, to only activate preconditions, the compilation module should be compiled with DBC_PRECONDITIONS macro defined (-DDBC_PRECONDITIONS).

4. Exception mechanism

It is also possible to directly throw (and catch) exceptions (other than those resulting from an assertion failure). An exception is also attached to an integer value (that can be used to identify the failure source). Assertion failures also generate exception throwing (being the type of assertion part of the exception integer value).

5. Fault tolerance: a safe try/catch/finally mechanism

The disciplined exception mechanism (see Eiffel) is based on a very simply (but powerful) idea: either a function succeeds or it fails, no "limbo" ambiguous state is allowed. No such simple assurance is present in try-catch exception blocks used in many programming languages (Java, C++, C#, Python, etc.), because catch blocks are allowed to filter exceptions (without any consequence on on logical control path of the algorithm as if the try block was executed normally).
One can even find such potentially disastrous usage from Java Language Specification books (and many others). The following code excerpt (wisely named BlewIt), exemplifying a first usage of the mechanism, was taken from The Java Language Specification, Java SE 7 Edition:
   class BlewIt extends Exception {
       BlewIt() { }
       BlewIt(String s) { super(s); }
   }
   class Test {
       static void blowUp() throws BlewIt { throw new BlewIt(); }

       public static void main(String[] args) {
           try {
               blowUp();
           } catch (RuntimeException r) {
               System.out.println("Caught RuntimeException");
           } catch (BlewIt b) {
               System.out.println("Caught BlewIt");
           }
       }
   }
The problem here is that one may catch an exception and, regardless of the try block intended meaning (implicitly or explicitly expressed by its postcondition), ignore it completely, allowing the program to continue to execute normally after the try/catch[/finally] instruction (in Java this problem is aggravated by the existence of checked exceptions).

If one considers that the presence of an exception (regardless of its cause) is a manifestation of a failure, then a safe try/catch[/finally] block should not be allowed to terminate normally (meaning: without an exception) if it does not execute normally its try code (which is the desired behavior). Formally, one may express that either a safe try block terminates meeting its postcondition, or it fails with an exception.

This library provides a disciplined exception handling mechanism in which it is not possible to ignore an exception. Its behavior is very simple: Either a function succeeds (meaning that it does not throw any exception), and the program is allowed to proceed; or the function fails (meaning that it throws an exception), to which the programmer has the choice of retrying the try block (in this library such a block is always a function), or (re)propagate the exception.

This behavior is implemented in C through function pointers, long-jumps, and variable arguments.

Several functions -- safe_try_catch, safe_try_catch_finally and safe_try_finally -- are provided that implement this mechanism. Depending on the function, appropriate function pointers must be passed pointing to try, catch and finally blocks (implemented as functions). Since the variable arguments C mechanism is used, other arguments (of any type desired) could also exist. Such extra arguments, when present, are passed (and shared) to all try/catch/finally functions used (which implement a simple functional closure). Unlike the try and finally functions, catch function must be augmented with a first argument in which the exception error code is (automatically) passed (to allow programming of different algorithms depending on the exception detected).

In order to this mechanism to work properly, it is necessary to encapsulate try and catch functions in equivalent variable argument functions. For example, the following body (try) and catch functions:
   void try(int i) {...}
   void catch(int value, int i) { ...}

   void try_va(va_list va)
   {
       int i = va_arg(va, int);

       try(i);
   }

   void catch_va(int value, va_list va)
   {
       int i = va_arg(va, int);

       catch(value, i);
   }
A fault tolerant scheme can then be executed simply by the invocation:
   safe_try_catch(try_va, catch_va, an_integer_expression); 
As mentioned, catch code can take into consideration the exception integer value, the type of assertion failure (if any), and even both. From an exception integer value one can extract the integer value that might be attached to it (other than the assertion type), through the macro error_mask. (Other macros exist in dbc.h to identify the assertion type and for others uses.)

To aid on the creation of va versions of try/catch/finally functions a Java's jar program -- func2va.jar -- is provided that generates them from prototyped function versions (this program was implemented with ANTLR 4).