The basic problem is to retain the context after an assert fails.
Since the assert throws a C++ exception, the default behavior is to catch it in the main program. Stopping the program there means that the context for the original error is already lost.
The workaround for this problem is to teach your debugger to catch the exception when it is thrown so that the call stack is still intact and you can inspect the code that failed.
There are genuine science reasons for preferring to use exceptions -- it means that a blown assert at one point doesn't kill an entire grid run, and allows context information for the failure to be obtained as the call stack unwinds.
To resolve problems in the code, it is often useful to have a debugger window open in the context of the assertion failure, before the stack unwinds. Many debuggers (including gdb, lldb, and Visual Studio) allow you to trap all exceptions which are thrown. Unfortunately, this facility appears to be available in the NetBeans debugger only for Java code.
We will now describe how to trap the throwing of a C++ exception. We start with a description for some commonly used debuggers and at the end list a fail-safe means which should work on all debuggers.
Trapping within gdb
In gdb, you need to issue the 'catch throw' command before running the program, see edited highlights below.
[user@localhost source]$ gdb ./cloudy.exe GNU gdb Red Hat Linux (6.6-35.fc8rh) (gdb) catch throw Catchpoint 1 (throw) (gdb) run Starting program: /home/user/cloudy/trunk/source/cloudy.exe test crash assert [...etc...] If the next line says "I am still alive - the assert macro is not working ...." then there are problems. Catchpoint 1 (exception thrown) 0x4d1c6795 in __cxa_throw () from /usr/lib/libstdc++.so.6 (gdb) where #0 0x4d1c6795 in __cxa_throw () from /usr/lib/libstdc++.so.6 #1 0x0810f8ba in ParseCrashDo (p=@0xbf9c3c70) at parse_crashdo.cpp:140 #2 0x081083ed in ParseCommands () at parser.h:183 #3 0x0805c1c9 in cloudy () at cloudy.cpp:85 #4 0x0804dc5a in cdDrive () at cddrive.cpp:126 #5 0x0804b503 in cdMain (argc=1, argv=0xbf9c4234) at maincl.cpp:330 #6 0x0804cd82 in main (argc=137173456, argv=0x82eaaf2) at maincl.cpp:109 (gdb) up #1 0x0810f8ba in ParseCrashDo (p=@0xbf9c3c70) at parse_crashdo.cpp:140 140 ASSERT( DBL_MAX < ZeroNum ); (gdb)
Getting Homebrew gdb to work on a Mac requires some setup to be done before the debugger is allowed to modify running programs. This is done for security reasons. The procedure is explained on this page.
Trapping within lldb
This is the debugger that comes with the LLVM compiler and it is the default debugger on Mac systems. Enabling catching a thrown assert works much the same way as described above for gdb, except that the actual command is different: _break set -E C++_ instead of _catch throw_. Some more details can be found here.
Trapping in Xcode
The procedure to catch a thrown assert in Xcode is described here.
Trapping in the Visual Studio debugger
Debug -> Exceptions...
under Visual Studio, you can set it to break when a C++ exception is thrown which should mean you retain context.
If we're developing under C++, it seems like a good thing to enable -- you'd probably want to know about any exception thrown, and it appears to remain set between sessions.
Placing a breakpoint in the bad_assert constructor
A standard breakpoint may be placed in the constructor for the
bad_assert class (i.e. the function
bad_assert::bad_assert()) in the file cddefines.cpp. This should act like any other code breakpoint: the routine which triggers the bad assert will be one level higher up in the call stack when the debugger starts.