/* * Assert.hpp * * Created on: Mar 18, 2010 * Author: crueger */ #ifndef ASSERT_HPP_ #define ASSERT_HPP_ #include #include #include #include #include /** * \file Helpers/Assert.hpp *

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:
* @code * void foo(int a) // a should be greater 0 * { * ASSERT(a>0,"Parameter passed to foo was smaller than 0"); * ... * } * @endcode * * (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:
* @code * Assertion "a>0" failed in foo.cpp in line 3. * Assertion Message: Parameter passed to foo was smaller than 0 * @endcode * * 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:
* @code * void foo(){ * // produce a coredump * ... * // close and flush all open handles * ... * } * * int main(int argc, char **argv){ * ASSERT_HOOK(foo); * ... * return 0; * } * @endcode * * 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
    * @code * ASSERT(++i,"i was zero after incrementing"); * @endcode * instead always do * @code * ++i; * ASSERT(i,"i was zero after incrementing"); * @endcode *
  • Give descriptive error messages. This one is a bit obvious but easy to do wrong, so I included * it here. An * @code * ASSERT(ptr,"Pointer was zero"); * @endcode * wont help anyone. If you do
    * @code * ASSERT(ptr,"Second argument of function foo should have pointed to an object of type bar, but was zero."); * @endcode * 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 * @code * assert(c>0 && "Counter should be at least 1"); * @endcode 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: * @code * switch(foo){ * case Bar: * ... * break; * case Baz: * ... * break; * ... * default: * ASSERT(0,"This switch should always be exhaustive.\nDid somebody add values to the enum?"); * } * @endcode *
*/ #ifndef NDEBUG #ifndef STRINGIFY #define STRINGIFY(x) #x #endif #ifdef __GNUC__ // on gcc we know how to exit to the Debugger #define DEBUG_BREAK __builtin_trap() #else #define DEBUG_BREAK exit(1) #endif #define ASSERT(condition,message) \ do{\ static bool ignore = false;\ if(!ignore){\ if(Assert::_my_assert::check((condition),STRINGIFY(condition),(message),\ __FILE__,__LINE__,ignore)){\ Assert::_my_assert::doHooks();\ DEBUG_BREAK;\ }\ } \ }while(0) #define ASSERT_NOCATCH(message) \ catch(Assert::AssertionFailure&){throw;}\ catch(...){\ static bool ignore = false; \ if(!ignore){\ if(Assert::_my_assert::check(false,"Exception caught",(message),__FILE__,__LINE__,ignore)){\ Assert::_my_assert::doHooks();\ DEBUG_BREAK;\ }\ }\ } do{(void)(0);}while(0) #define assert_cast Assert::_wrapper(__LINE__,__FILE__)._convert #define ASSERT_DO(action) do{Assert::_my_assert::setDefault(action);}while(0) #define ASSERT_HOOK(hook) do{Assert::_my_assert::addHook(hook);}while(0) #define ASSERT_UNHOOK(hook) do{Assert::_my_assert::removeHook(hook);}while(0) #define ASSERT_DEFAULT (Assert::_myAssert::printDefault()) #else // we need to do something, so this is the usual solution (e.g. assert.h) #define ASSERT(condition,message) (void)(0) #define ASSERT_NOCATCH(message) catch(...) {throw;} do{(void)(0);}while(0) #define assert_cast static_cast #define ASSERT_DO(action) (void)(0) #define ASSERT_HOOK(hook) (void)(0) #define ASSERT_UNHOOK(hook) (void)(0) #define ASSERT_DEFAULT std::string("Deactivated") #endif namespace Assert{ typedef void (*hook_t)(void); enum Action {Ask,Abort,Throw,Ignore,MAX_ACTION}; extern const char ActionKeys[MAX_ACTION]; extern const char* ActionNames[MAX_ACTION]; class AssertionFailure{ public: AssertionFailure(std::string _condition, std::string _file, int _line, std::string _message); std::string getFile(); int getLine(); std::string getMessage(); std::ostream& operator<<(std::ostream&); private: std::string condition; std::string file; int line; std::string message; }; //! @cond #ifndef NDEBUG class _my_assert{ public: static bool check(const bool res, const char* condition, const char* message, const char* filename, const int line, bool& ignore); static void addHook(Assert::hook_t hook); static void removeHook(Assert::hook_t hook); static void doHooks(); static void setDefault(Assert::Action); static Assert::Action getDefault(); static std::string printDefault(); private: static Assert::Action defaultAction; static std::vector hooks; }; class _wrapper{ public: _wrapper(int _line,const char* _file) : line(_line), file(_file) {} // Overloaded template for pointers template target _convert(source *src){ std::stringstream sstr; sstr << file << ":" << line; bool &ignore = ignores[sstr.str()]; if(!ignore){ if(_my_assert::check(dynamic_cast(src)==static_cast(src),"type-safe typecast", message_ptr,file,line,ignore)){ _my_assert::doHooks(); DEBUG_BREAK; } } return static_cast(src); } // Overloaded template for references template target _convert(source &src){ std::stringstream sstr; sstr << file << ":" << line; bool &ignore = ignores[sstr.str()]; try{ target res =dynamic_cast(src); return res; } catch(...){ if(!ignore){ if(_my_assert::check(0,"type-safe typecast",message_ref,file,line,ignore)){ _my_assert::doHooks(); DEBUG_BREAK; } } } // The error was ignored. Just return whatever a static_cast would do return static_cast(src); } private: int line; const char *file; static std::map ignores; // this avoids duplication of the strings when templates are instantiated static const char* message_ptr; static const char* message_ref; }; #endif //! @endcond } #endif /* ASSERT_HPP_ */