Abstract

The Design By Contract framework for C/C++ allows developers to design software that accomplish the specification requirements. Obscure bugs in software are shown quickly due to its preconditions, postconditions, class/loop invariants and new/delete runtime analysis.

dbcxx -- Design By Contract framework for C/C++

1. Introduction

The Design By Contract framework for C/C++ is a library that has a small debugger built-in. This is not an interactive debugger, so when the piece of software crashes it does a stack frame dump, shows the last stack frame variables content and prints a small text showing the reason of the crash.

Numerous reasons could cause software to crash. Typical reasons are 'Segmentation Fault' or 'Exception Abort'. Without a debugger these situations are difficult to identify and repair. But a typical interactive debugger is so clumsy to be run in each program execution and test. We need an 'always-present' debugger that only appears when there are problems and is compiled within the piece of software.

A small program can also hang. This is usually caused by an endless loop. Even with a debugger it is difficult to detect where is the source of the problem. When the small program turns into a huge piece of software, the problem is almost impossible to detect. This can be avoided placing a variant check in each loop o recursive function. When the framework detects that there is no progress inside the loop due to the variant, the program ends with all the stack frame dump and source file and line that caused this error. Programming errors can be easily corrected.

Checks that can be performed by the framework are:

Dr. Bertrand Meyer designed the Eiffel language with these goals in mind and all of these are built in the language. He called this kind of programming 'Design By Contract'. But for some reason, Eiffel lacks the versatility and efficiency that C/C++ can reach. Also, in some cases Eiffel is not a good choice if manually managed memory is needed. Sometimes one cannot wait until the garbage collector frees out unreferenced memory.

In this approach, the framework must also check memory leaks automatically. News/deletes are counted so the number of calls match. Also new[]/delete[] must also be checked so no memory leaks are present. Regarding C language, malloc() and free() calls must be accounted.

Programmers must have the discipline to write software 'designed by contract'. This is not difficult, so only few checks can simplify the task of finding a bug a lot. Also, when the software is well tested, one could simply not link the framework library and undefine the DEBUG symbol and software would run without efficiency penalties due to checks.

2. Details of variable content printing

GNU SmartEiffel has an special manner to print out variable content. Integers, floats, strings and booleans are printed in text mode as normal text and class objects are printed with all its attibute contents. This information is printed in all stack frames for all local variables and function parameters, so a lot of output is printed out when program crashes.

I particulary find this approach very useful but it could be undoubtedly improved. I can think of graphical variable printing in some GUI, maybe on GTK or similar.

3. C++ STL Contracts

The Standard Template Library is the main C++ library where all programs rely on. There are such many implementations of the STL. But I will remark two of them (those which are important to me). These two are free software so modifications are permited.

When selecting the STL some decisions must be made. libstdc++ or libstlport?? For our project it seems that stlport is better so contracts are already written in the sources. But these contracts have some deficiencies. First of all, let's examine some examples extracted from their web page ( http://www.stlport.org/doc/debug_mode.html) with corresponding runtime output:

char string[23] = "A string to be copied.";
char result[23];
copy(string+20, string+10, result);

_debug.h:168 STL error : Range [first,last) is invalid
algobase.h:369 STL assertion failure: __check_range(first, last)

vector<char>::iterator i;
char ii = *i;
stldebug.h:152 STL error : Uninitialized or invalidated (by mutating operation) iterator used
vector.h:158 STL assertion failure: __check_dereferenceable(*this)

vector<char>::iterator i=v2.begin();
v2.insert(v2.begin(),'!');
char ii = *i;
_debug.h:152 STL error : Uninitialized or invalidated (by mutating operation) iterator used
vector.h:158 STL assertion failure: __check_dereferenceable(*this)

vector<int> v; v.pop_back();
vector.h:482 STL error : Trying to extract an object out from empty container
vector.h:482 STL assertion failure: !empty()

vector <int> v1(10);
for(int i = 0; i < v1.size(); i++)
v1[i] = i; vector <int> v2(10);
copy(v1.begin(), v2.end(), v2.begin());
_debug.h:61 STL error : Iterators used in expression are from different owners
vector.h:182 STL assertion failure: __check_same_owner(*this,y)

list<int> l1(array1, array1 + 3);
l1.erase(l1.end());

list.h:398 STL error : Past-the-end iterator could not be erased
list.h:398 STL assertion failure: position.node!=node

list<int> l1(array1, array1 + 3);
list<int> l2(array2, array2 + 2);
l1.erase(l2.begin());

_debug.h:70 STL error : Container doesn't own the iterator
list.h:397 STL assertion failure: __check_if_owner(node,position)

We can see that error reports are from stl code not from our program code. So, if our program is very big we can detect that such error exists but we can't find it easily in our sources. I can think of 2 possible solutions for this:

I'm inclined towards the last one because more flexibility could be achieved. For example, consider we need to print local function variables for each stack frame. Doing this, at a glance one can detect where is the problem.

So whatever STL library we choose, some changes must be made. I think a correct decision is to modify de Standard STL library from GNU adding some dbcxx contracts, taking some of them from the stlport library which provides safe containers and a lot of runtime checks.

So, if we choose to modify the GNU STL, it must be wrapped with other classes that enable a contract interface to developers so incorrect access to the library could be reported. Imagine for example out of range accesses into a vector<>. Or some other incorrect uses such the examples above.

STL Algorithms such as copy() or find() must be also wrapped out so incorrect use can be stated.

Zeus Gómez Marmolejo.

This project is hosted thanks to SourceForge.net: SourceForge.net Logo