Press Cancel to cancel (or Cancelable Asserts)

2 10 2013

Back when I developed on Unix asserts where simple, if an assert fired the application would abort with a nice core dump which could be then debugged at leisure. When I moved to Windows development one of the changes I had to get used to was that Visual Studio’s _ASSERTs were not fatal.


At first glance this looks like an improvement, you can choose which asserts are fatal and which are not.

There is the obvious wart “Press Retry to debug” I had to read this line several hundred times before it became automatic. Still, all in all, an improvement. However the situation on one of our mega-lines-of-code projects was not so good. It’s a bit embarrassing to admit this (if others have faced the same situation please comment below, misery loves company) but the debug build of the application became very cumbersome to use. This was probably the result of having some teams work only with production builds and some with debug builds and from the fact that some flows in a specific team’s code would only happen when used in a particular way from other teams’ modules. Whatever the cause the result was that when working with the debug build you would have to press Ignore many, many times.

The sane thing to do would have been to treat an ASSERT being activated as a critical defect and either fix the calling code or, if the assert was a false positive, remove the assert. Politics got in the way of sanity and changing the code that contained the assert would often be more effort than clicking Ignore a couple of times.

Usually the situation wasn’t so dire, after all a code base does not typically contain that many asserts, after all in order for an assert to exist someone had to write it explicitly. It got worse when the (false positive) assert was in a loop or part of a recursive call, in these cases you would have to Ignore the same assert dozens if not hundreds of times. In fact it got so bad that the debug version of the application had a menu item which turned off assertions using _CrtSetReportMode(CRT_ASSERT, _CRTDBG_MODE_DEBUG).

I had a small insight, it doesn’t really matter how many times an assert fires, it should optimally not fire at all, but if it does it doesn’t matter if it’s once or a thousand times. If a false-positive assert does creep into your code, the short term goal, is to get it to stop bothering you. For this purpose I wrote the CANCLEABLE_ASSERT macro, this macro never got committed to our code base since it’s obviously not the right thing to do. The right thing to do is fix all the asserts but perhaps it would have been the pragmatic thing to do since this product eventually got to the state where almost nobody used the debug build.

I retouched the macro a bit  for the purposes of this post and here it is in all its glory (or lack thereof).

#ifdef _DEBUG
#include <windows.h>
#include <intrin.h>

#define CANCLEABLE_ASSERT(expr)              /*1*/ \
  do {                                       /*2*/ \
    static bool check(true);                 /*3*/ \
    if(check &&!(expr)) {                    /*4*/ \
      switch(MessageBoxA(NULL,                     \
      "Assertion failed: "#expr                    \
      "\n\nFile: " __FILE__                        \
      "\nLine: " _CRT_STRINGIZE(__LINE__)          \
      "\n\nDo you want to debug?"                  \
      "\n(Cancel means don't assert here again)",  \
      "Debug?", MB_YESNOCANCEL | MB_ICONHAND)){	   \
      case IDYES:                                  \
        __debugbreak()                       /*5*/ \
        break;                                     \
      case IDCANCEL:                               \
        check = false;                             \
      }                                            \
   }                                               \
} while((void)false, false)                  /*6*/
#define CANCLEABLE_ASSERT(x)((void)0)

A bit of explanation for anyone interested.

  1. If this is the first time you see multi-line macros, having a backslash at the end of the line pulls the next line into the macro, this is why I used C style comments  and not C++ for numbering, otherwise the rest of the macro would also be commented out.
  2. The whole thing is wrapped by a do { } while in order to make this a single statement (here’s why).
  3. The do { } while also introduces a new scope which makes the static boolean very localized and we don’t have to worry about giving it a unique name.
  4. the condition is only evaluated if this specific assert hasn’t been canceled due to logical and short-circuiting this is a nice feature since expr may be arbitrarily costly to compute (this is also why expr is parenthesised).
  5. In case we choose to debug the code the int 3 assembly instruction breaks into the debugger, note that in assembly int stands for interrupt, not integer  Edit: Thanks to Ofek for suggesting I use __debugbreak() instead.
  6. The while condition is ugly in order to prevent compiler warnings.

Cancelable asserts

As an added benefit this dialog has more intiative buttons. Yes I want to debug, No I do not want to debug, please Cancel this breakpoint for the duration of this program.

As I mentioned above, this code did not make into our code base so I can’t vouch for it 100% but I’m hereby placing it in the public domain. I hope you find it useful (or at least mildly interesting).




2 responses

3 10 2013
Ofek Shilon

Nice hack.
If you use the __debugbreak() intrinsic instead of inline asm it would work on x64 too (and be more readable).

3 10 2013
Motti Lanzkron

Thanks for the suggestion, I updated the post.

Leave a Reply

Fill in your details below or click an icon to log in: Logo

You are commenting using your account. Log Out /  Change )

Google+ photo

You are commenting using your Google+ account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )


Connecting to %s

%d bloggers like this: