Click here to Skip to main content
15,880,392 members
Articles / Programming Languages / C++
Article

Design by Contract macros for C++ and link to Doxygen

Rate me:
Please Sign up or sign in to vote.
4.04/5 (37 votes)
27 Oct 2004CPOL8 min read 111.3K   908   43   26
Macros to write "Design by Contract" conditions in the header of the functions and inoculate it in your doxygen documentation automatically.

Table of content

Purpose of the provided macros

  1. Write pre/post conditions in one block before the body of your C++ function for a clear reading.
  2. Have them automatically inoculated in your doxygen documentation.
  3. Enable/Disable conditions checks at compile time (if you want to remove the code for release version).
  4. Breaks when condition fails or always ignore it at runtime.
  5. Having hook to handle condition failure.

Introduction

Design by Contract

"Design by Contract" is a very good method for programming. (Read Object Oriented Software Construction from Bertrand Meyer.)

For people who have never heard about DbC, I do a very simple and quick introduction to the principle.
The idea is that between a function and the call of a function, there is a contract.
To ensure that the function will do what it should do, every condition of the contract has to be ok.
Those conditions can involve the function or the caller.
When a condition fails, one of the two contractors have not done his job. So the programmer has to choose to change the function or the way the function has been called.

For example I choose to write a linear interpolation function.

/**@brief Float linear interpolation.<br>
 * @param _o lerp result.<br>
 * @param _t parametric value.<br>
 * @param _s start value.<br>
 * @param _e end value.<br>
**/<br>
void lerp( float& _o, float _t, float _s, float _e )

{
    _o = _s + ((_e - _s) * _t);
}

We can add some preconditions like :

  • _t has to be in [O..1].
  • _e has to be a float value.
  • _z has to be a float value.

We can add as postcondition :

  • _o has to be a float.

Imagine the same function but working with vectors or matrix. We should add conditions saying that the input parameters should not be changed by the function.
In C++ this condition can be check at compile time (in the case) using the const keyword.

But for the others conditions we should use the assert function.

void lerp( float& _o, const float _t, const float _s, const float _e )
{
    assert((_t >= 0.f)&&(_t <= 1.f));
    assert(!isnan(_s));
    assert(!isnan(_e));
    
    _o = _s + ((_e - _s) * _t);
    
    assert(!isnan(_o));
}

For more specialized lerp functions you would probably add more sophisticated postconditions like insure othogolalization on matrix ...

Little personal trick : Don't ignore "stupid" conditions, because those are often the ones which appears. Most of the time it is because the code changes, late in the night, by copy paste block of code..., and the "stupid" condition which has been set becomes really relevant.

The Digital Mars C/C++ compiler is DbC compliant.

DbC with no DbC compliant C++ compilators.

Usually, I write contract condition like this:

int foo( void* p )

{ 
     assert( p ); // precondition : p cannot be null. 
 
     ... code ...
 
     if(...) 
     {
        assert( post condition); // post condition documentation 
        return ... 
     } 
 
      ... code ... 
 
      if(...) 
      { 
         assert( post condition ); // doc 
         // this condition didn't have to be check in the last return
         assert( an other post condition); // documentation
         return ... 
      }

  ... code ...

  ... once again check all post conditions ...

  return ... ouf
}

So "debug" code, or "check" code really pollute my code, mainly because of repetitions. And I didn't want to have those stuff compiled for the the final release.

So I really want to be able to write the function in a better way .. like:

int foo( void* p )

{
     #ifdef contract_check 
     PRECONDITION( p, "p can't be 0" ); // the phrase is to display to the user
                                        // why the assertion fail.
     POSTCONDITION( postcondition, "description of my post condition" );
     POSTCONDITION( checkpostcondition2(), "an other post condition");
     #endif // contract_check
     
     ... code ... 
 
     if(...)  
         return ... 
 
     ... code ... 
 
     if(...)  
         return ... 
 
     ... code ...
 
     return ...
 }

And, because I'm using a super code documentation generator (Doxygen), I wanted to see the documentation of my assertions (conditions) in the documentation. Of course, without writing it twice ... Do you follow me? (redundancy means future problems)

So the little set of macro I wrote gives you the ability to write something like:

int AClass::Test( int argA, int argB )

{
    DBC_BEGIN    
    DBC_PRE_BEGIN
        DBC_PRE( argA >= 0, precond 1 documentation and message )
        DBC_PRE( argB != 0, precond 2 documentation and message )
    DBC_PRE_END

    DBC_POST_BEGIN
        DBC_POST( argA + argB >= 0, post condition 1 documentation and message )
        DBC_POST( ChechDataFct( argA, argB ), post cond 2 : cf. ChechDataFct() )
    DBC_POST_END
    DBC_END
    
    ... code ... 
    
    return ...;
}

And (if you use Doxygen) the documentation will be up to date.

Example:

Take a look at this picture taken from Doxygen documentation of the AClass::Test function:

Documentation screenshot

While compiling for WIN32, you have this following message when the condition fails:

Failure screenshot

Click "OK" to continue (break when using a debugger).

Click "Cancel" to ignore (means that it will not popup a message (or break) if the condition fails again).

Code

It works with VC6 and VC7.

The code is composed of 2 parts:

  1. Core stuff: macro definition ...
  2. Callback stuff: write the code you want to handle assertion.

For example, you can choose to send an exception when a condition fails.

The demo zip file is exactly the same as the source zip file, plus a compressed HTML doc.

This documentation file can be generated from the source zip file if you have Doxygen.

How it works?

Because 100% of the feedback I receive (...2) asks me for explanation about the trick I have used to make the tool working, I added this section to the article.

Doxygen documentation:

The Doxygen documentation works using the Doxygen preprocessor :

In source file, the line:

DBC_PRE( argA >= 0, precond 1 )

Using in the Doxygen preprocessor:

DBC_PRE(a,b) =\pre b \code a \endcode

the code becomes for Doxygen:

\pre precond 1 \code argA >= 0 \endcode

which is a valid command for Doxygen parser.

C++ code:

Note: I assume you know what are functions prolog and epilog, how the compilers use the stack in the calling function process.

The main problem is ... post condition of course. You see easily that calling the preconditions at the begin of a function is very easy ... because you have written it at the beginning :)

But I use a little trick to call the post conditions after returning the function.

The process is very simple: Meanwhile the epilog of the function (so when the function ends/returns/leaves), we go to the start of the postcondition, execute postcondition block, and finish the epilog.

Before continuing to explain the trick, you should see that there is another problem: do not call POSTCONDITION block before the end of the function. As it is written just before the body of the function, we have to skip it.

The algorithm to execute the function becomes:

  • execute precondition block
  • jump to body

when return :

  • goto postcondition block
  • execute postcondition block
  • return back to epilog of the function.

So the first step is to tag the poscondition block. It is very easy because it is enclosed by 2 macros: DBC_POST_BEGIN and DBC_POST_END.

We also need to know where the start of the body of the function is. The answer is ... after the "Design by Contract" block ... enclosed by DBC_BEGIN and DBC_END.

The pseudo macros become something like :

define DBC_BEGIN
// declare start of body label.
define DBC_END __dbc_body :
define DBC_PRE_BEGIN
define DBC_PRE_END
define DBC_POST_BEGIN  if(not in epilog) __asm je __dbc_body
define DBC_POST_END continue epilog (means jump after 
                           caller instruction in epilog ...)

Using C++ local variable declaration:

I often use C++ local variable declaration, or even local static variable declaration to do many things. I've plan to write something about that in another article because it is a powerful feature of C++, and I never read anything about taking benefits of that.

Here, I just use the fact that when we declare a local instance of a class, the destructor will be called during the epilog of the function. So declaring an instance of a class with destructor gives you an "epilog trap", which is the destructor of the class.

I also use the fact that constructors are called where the variable is declared... but it is not very relevant for this tool. I mean, it can be implemented differently I suppose.

Here is the code of the class declaration used by this tool:

class structDBC
{
public :
   structDBC();   // constructor
   ~structDBC();   // destructor
   // store caller address of post condition block 
   // (means instruction address which call destructor in epilog).
   long dbc_postblockRETaddr;
   // store start address of postcondition block.
   long dbc_postblockaddr;
};

Add our class in the macros:

define DBC_POST_BEGIN structDBC autoDBC; 
   if(!autoDBC.dbc_postblockRETaddr) goto __dbc_body;
define DBC_POST_END goto autoDBC.dbc_postblockRETaddr;

So post condition block is enclosed by:

  • IF dbc_postblockRETaddr is not set (we are not called by epilog) we goto the start of the body. ELSE (called by epilog) we execute the post condition block.
  • AT the end of the postcondition block, we jump (goto) the next instruction in the epilog (which is stored in dbc_postblockaddr).

The structDBC constructor and destructor implementation:

Note:

  • Constructor and destructor are implemented in a separate file, to ensure that they will be called with a call instruction (no jmp). According to compilers, it is possible to add some special declaration like __noinline to insure that. I think that usually compilers will implement call of those functions with call because they are implemented in a separate object.
  • __declspec(naked) indicates Visual C compiler to not create epilog et prolog to this function.

__declspec(naked) structDBC::structDBC()

ASM
{
   // standard prolog (__asm ENTER)
   __asm push ebp;
   __asm mov ebp, esp;

   // Put in eax the stack pointer where return address is store
   __asm mov eax, ebp;
   __asm add eax, 4; // because we have done a push on the first instruction.

   // store address effective return adress in dbc_postblockaddr
   // made in 2 lines because we can't write
   // __asm mov dword ptr [ecx+4], [eax]
   __asm mov eax, [eax];               // store effective address in eax.
   __asm mov dword ptr [ecx+4], eax;   // copy to dbc_postblockaddr

   // standard epilog (__asm LEAVE )
   __asm mov esp, ebp;
   __asm pop ebp;

   __asm mov dword ptr [ecx], 0        // reset dbc_postblockRETaddr
   __asm ret;
}

__declspec(naked) structDBC::~structDBC()

{
   // standard prolog
   __asm push ebp;
   __asm mov ebp, esp;

   // put return address in dbc_postblockRETaddr
   __asm pusha;
   __asm mov eax, ebp;
   __asm add eax, 4;
   __asm mov eax, [eax];
   __asm mov [ecx], eax; // copy to dbc_postblockRETaddr
   __asm popa;

   // standard epilog
   __asm mov esp, ebp;
   __asm pop ebp;

   __asm add esp, 4; // skip next line.
        // goto dbc_postblockaddr (start of post condition block).
   __asm jmp dword ptr [ecx+4];
}

Note: if you compare with the code of the source, you will notice that I don't use a class, but a struct ... This is just because I want to ensure that no virtual table will be implemented (or other stuff the C++ compiler wanted to add)... according to assembler code. Writing this explanation paragraph, I notice that I never use the attributes names of the class... It is just because ... I don't need it while coding in assembly.

Final Macros :

#define DBC_POST_BEGIN structDBC autoDBC;               \
                       __asm cmp dword ptr [autoDBC], 0 \
                       __asm je  __dbc_body
#define DBC_POST_END   __asm jmp dword ptr [autoDBC];

Conclusion

It is the first article I publish and ... sorry for my English.
I hope it will be useful to others.
Unfortunately, I haven't tested it very much, just on a few targets: the code depends on compiler.
But, the idea is here, everybody can adapt and improve the stuff (please share your add-on or improvements :) )

I tried to do those stuff using only C/C++, for example using longjump. But I didn't find a way to get what I wanted.
If somebody can do it without assembly: please send me your code :)

Visit

History

  • April 2004 : Add code documentation after Kandjar comment.
  • October 2004 : Add DbC introduction after Peterchen comment.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
France France
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
Generalgcc Pin
bitmk225-Sep-10 6:22
bitmk225-Sep-10 6:22 
GeneralGenerate docs by Doxygen Pin
James Duy Trinh (VietDoor)19-Aug-08 13:06
James Duy Trinh (VietDoor)19-Aug-08 13:06 
GeneralRe: Generate docs by Doxygen Pin
Antoine Tandin16-Oct-08 10:39
Antoine Tandin16-Oct-08 10:39 
GeneralNice work Pin
JasonReese18-May-06 0:46
JasonReese18-May-06 0:46 
GeneralRe: Nice work Pin
Antoine Tandin18-May-06 20:15
Antoine Tandin18-May-06 20:15 
Generaldeallocation order Pin
Paolo Martinoli16-Jun-05 23:32
professionalPaolo Martinoli16-Jun-05 23:32 
GeneralRe: deallocation order Pin
SuperKoko14-Oct-05 11:24
SuperKoko14-Oct-05 11:24 
GeneralRating going down Pin
Antoine Tandin15-Apr-04 5:25
Antoine Tandin15-Apr-04 5:25 
GeneralRe: Rating going down Pin
peterchen5-Jun-04 1:21
peterchen5-Jun-04 1:21 
GeneralRe: Rating going down Pin
Antoine Tandin27-Oct-04 22:13
Antoine Tandin27-Oct-04 22:13 
GeneralRe: Rating going down Pin
frugalmail7-Apr-05 13:14
frugalmail7-Apr-05 13:14 
GeneralFYI: Digital Mars C++ has design-by-contract built in Pin
Don Clugston29-Mar-04 12:40
Don Clugston29-Mar-04 12:40 
GeneralRe: FYI: Digital Mars C++ has design-by-contract built in Pin
Antoine Tandin29-Mar-04 20:21
Antoine Tandin29-Mar-04 20:21 
GeneralRe: FYI: Digital Mars C++ has design-by-contract built in Pin
Don Clugston30-Mar-04 12:27
Don Clugston30-Mar-04 12:27 
GeneralRe: FYI: Digital Mars C++ has design-by-contract built in Pin
Antoine Tandin15-Apr-04 5:56
Antoine Tandin15-Apr-04 5:56 
GeneralMore information requested... Pin
Kandjar17-Mar-04 22:54
Kandjar17-Mar-04 22:54 
GeneralRe: More information requested... Pin
Antoine Tandin18-Mar-04 7:44
Antoine Tandin18-Mar-04 7:44 
GeneralRe: More information requested... Pin
Kandjar18-Mar-04 22:50
Kandjar18-Mar-04 22:50 
GeneralRe: More information requested... Pin
frugalmail26-Mar-04 10:26
frugalmail26-Mar-04 10:26 
I think this is a great implementation, thank you for starting the article, more information on how your code works would be appreciated.

Don't be concerned about your English, it's getting across what you want to say just fine, don't be concerned about saying more.

Thanks again.
GeneralRe: More information requested... Pin
Antoine Tandin27-Mar-04 1:03
Antoine Tandin27-Mar-04 1:03 
GeneralRe: More information requested... Pin
frugalmail28-Mar-04 9:56
frugalmail28-Mar-04 9:56 
GeneralRe: More information requested... Pin
Kandjar30-Mar-04 0:21
Kandjar30-Mar-04 0:21 
GeneralRe: More information requested... Pin
Antoine Tandin7-Apr-04 5:34
Antoine Tandin7-Apr-04 5:34 
GeneralRe: More information requested... Pin
Uwe Keim15-Apr-04 19:04
sitebuilderUwe Keim15-Apr-04 19:04 
GeneralRe: More information requested... Pin
Antoine Tandin15-Apr-04 22:01
Antoine Tandin15-Apr-04 22:01 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.