wiki:AssertHowto

Version 1 (modified by Till Crueger, 15 years ago) ( diff )

--

ASSERT Howto

Introduction

ASSERT() is a small macro that allows easier debugging, when it is widely used. The custom ASSERT macro defined in this file works mainly the same way as the assert() macro that is defined in the Ansi-C standard, but includes a few nice additions.

What ASSERT() does

ASSERT can be used to make sure that a condition that always needs to be true for the code to work correctly is holding. If you have a function that takes a value greater than 0 and a value smaller than 0 indicates a mistake you should always do it the following way:

 void foo(int a) // a should be greater 0
 {
  ASSERT(a>0,"Parameter passed to foo was smaller than 0");
  ...
 }

(Note: some people say, that assertions like these should not be used to check function parameters. This is mainly due to the reason, that a failed assertion will show up inside the function. The buggy code however is at a completely different place, i.e. at the callers side. Always put the Assertions as close to the code that produces the value as possible, when looking at function parameters however this would mean, that any code calling foo would have an ASSERT(...) before it, which makes it easy to forget the Assertion at some places. Also this makes an easy example.)

If the condition inside the ASSERT does not evaluate to true the user is shown a message, including the condition that failed, the line in which the failure was observed and the message of the assertion. In the above case that would look something like this:

 Assertion "a>0" failed in foo.cpp in line 3.
 Assertion Message: Parameter passed to foo was smaller than 0

In normal conditions, i.e. when no default action is set (see below for default actions) the user is then shown a short choice menu, on how to handle the assertion. The user can choose to abort the program, throw an exception of type AssertionFailure that contains the file, line and message, ignore the assertion or even to always ignore the assertion at that point (i.e. the ASSERT() macro at this file and line is fully disabled).

Both ASSERT() and assert() handle debugging in the same way, i.e. they are only used when the NDEBUG macro is not defined. If the NDEBUG macro is defined, for example using a CXXFLAG then all asserts and ASSERTs will be disabled in the compiled program. That way in a end-user version all assertions can be removed with a single switch, thus not hassling the end-user with potential bugs.

Special functions of ASSERT()

Compared to the standard assert() macro the custom ASSERT() contains a few special functions. As first it is possible to set a global default behavior that is used anytime an assertion fails. This default behavior can be either of Assert::Ask, Assert::Abort, Assert::Throw or Assert::ignore. The default behavior is set using the ASSERT_DO() macro. For example if you want to check in a unittest that wrong code at another point actually makes a certain assert fail you could set ASSERT_DO(Assert::Throw) to make sure a exception is thrown and catch that exception using the CPPUNIT_ASSERT_THROW() macro. The current set default behavior can be queried as a string using the ASSERT_DEFAULT macro.

As a second enhancement it is possible to install callback functions as hooks that will be executed when an assertion aborts the program. These callback functions could for example be used to flush any open streams, thus making sure files on the disk are not corrupted by a unexpected abortion. It would also be possible to install functions that produce some kind of "coredump" of important internal data-structures, thus giving the person looking for the bug some valuable information. These assertion hooks should however not be used to clean up the reserved memory of the program, because a) this memory is under normal circumstances reclaimed by the OS anyway, once the program has aborted and b) the memory might still contain some hints that could be useful when running the program inside a debugger and which could be destroyed by the clean-up. To use the hooking mechanism you can simply use the ASSERT_HOOK() macro, passing this macro any kind of void function. For example:

 void foo(){
   // produce a coredump
   ...
   // close and flush all open handles
   ...
 }

 int main(int argc, char **argv){
   ASSERT_HOOK(foo);
   ...
   return 0;
 }

All hooks will be executed in the reverse order of hooking, i.e. the function hooked last will be executed first when the abortion is handled. It is also possible to remove a hook to any function using the ASSERT_UNHOOK() macro and passing it the pointer to the function one wants to remove.

Assertion hooks will only be executed when the program is terminated by an assertion using the abort mechanism. They will not be executed when the program exits in any other way. They also wont be executed when the assertion is ignored or an exception is thrown (even when the exception is not caught and thus terminates the program).

Rules for using ASSERT()

The rules for using ASSERT() are basically the same ones that can be used as guidlines for the standard assert() macro. So if you think you know those guidelines you can skip the following.

  • ASSERT() should be used only for problems that indicate a bug, i.e. problems that can be improved by rewriting parts of the program. ASSERT() should not be used to query problems that can go wrong during the normal execution of the program. For example ASSERT() should not be used to test whether a file could be opened, or memory could be reserved, as a failure of either of those tasks can not be improved upon by rewriting the code.
  • The condition in the ASSERT() macro should never contain any side-effects. Only call methods, when you are absolutely certain that these methods wont have any side-effects. Calling ASSERT() should in no way change the state of the program, because once the end-user version is produced using the NDEBUG flag all assertions are removed and so are the conditions. If the condition did cause a state transition, this state transition would be removed and the behavior of the end-user and the debug version might differ. Things you should watch out for are for example
           ASSERT(++i,"i was zero after incrementing");
    
    instead always do
           ++i;
           ASSERT(i,"i was zero after incrementing");
    
  • Give descriptive error messages. This one is a bit obvious but easy to do wrong, so I included it here. An
           ASSERT(ptr,"Pointer was zero");
    
    wont help anyone. If you do
           ASSERT(ptr,"Second argument of function foo should have pointed to an object of type bar, but was zero.");
    
    instead, people will almost immidiately know what to look for.

Differences between ASSERT() and assert()

This chapter is to explain why a custom ASSERT() macro was introduced and should be used in place of the standard assert(). Here are the main differences between ASSERT() and assert().

  • ASSERT() makes it easy to add a more verbose message about the nature of the failure. For assert() it has become customary to add messages using constructs like
           assert(c>0 && "Counter should be at least 1");
    
    in order to add descriptions. However both the syntax and the final output for this are a bit awkward. The custom ASSERT() handles messages in a much better way, as well as making them mandatory instead of optional.
  • ASSERT() leaves the user and the programmer a choice how to handle an assertion. While the assert() macro will always abort the program, the ASSERT() macro normally gives the user a choice on what to do. For debugging it might also be interesting how a broken assumption influences the rest of the program, so the assertion can also be ignored. Also the Exception mechanism allows assertions to be part of unittests, whereas they would always fail if the assert() macro was used.
  • ASSERT() does not unwind the stack (at least when compiled using gcc). The normal assert() exits the program, which unwinds the stack and destroys any hope for recovering a stack trace. ASSERT() on the other hand aborts the program using a special trap function, that leaves the stack intact. This way, when the program is run inside a debugger the stack is still available and can be inspected. This is the main reason, why it is safe to use ASSERT() to check function parameters, whereas assert() would give problems in such cases.
  • ASSERT() allows for hooks to be installed when the program exits. As mentioned above this makes it possible to produce coredumps, make sure all files are in a usable state or other tasks that have to be performed before killing the program.

Tips and tricks and FAQ

  • ASSERT() is broken. When I abort the program it says something about an "Illegal instruction"

The complaints about the illegal instruction after an abortion are no need to worry. This illegal instruction is part of the trap that is used to exit the program while leaving the stack intact. This illegal instruction can be detected by the debugger, which means it will give you the usual prompt once it is encountered. The illegal instruction is guaranteed not to mess up anything, so there is no need to worry about it.

  • When compiling the program with $NON_GCC_COMPILER and then debugging it, it will unwind the stack. I need the backtrace however to find the bug

The mechanism to preserve the stack is compiler specific. For now only a mechanism that is supported by gcc is implemented, because this compiler is widely used. For other compilers the program is simply exited, and the stack is destroyed. If you need a backtrace and you cannot use gcc you have to figure out a way to have your compiler produce a trap instruction in the program. You might want to use google to find out how to get your compiler to do that. For many compilers a _asm {int 3} is said to work. Also for VC++ the instruction debugbreak() might produce a trap. Also dividing by zero is a hack that could be used as a last hope if you don't find a way to produce traps with your compiler even after a longer search. If you found a way to handle the traps you can then add the macro DEBUG_BREAK for your compiler and the stack will be preserved.

  • I have a portion of the program that should never be executed. How can I assure this using assert.

This is a common task for assertions. For example you might have an exhaustive switch/case where the default value indicates that something went wrong. Simply use the following construct:

       switch(foo){
         case Bar:
           ...
           break;
         case Baz:
           ...
           break;
         ...
         default:
           ASSERT(0,"This switch should always be exhaustive.\nDid somebody add values to the enum?");
       }

Note: See TracWiki for help on using the wiki.