Informative Assertion Failures

The other day a student was asking me how to make assertion failures in software more informative. I told him there are two pretty easy ways to do this. First, define a customized assert that takes two arguments: the predicate and also a description. For example, in the Botan crypto library we see lines like this:

BOTAN_ASSERT(signing_key, "Signing key was set");

The second technique is to use a standard single-argument assert like this:

assert((depth > 0) && "Invalid depth!");

Any other good tricks out there?

11 replies on “Informative Assertion Failures”

  1. I see little resaon to spend much effort making assertion failure messages informative. assert() should only be used to verify absolute invariants and failure means there’s a bug somewhere else. It is thus sufficient that the message is meaningful to developers with access to and understanding of the source code. Any check that might fail in a correct program (e.g. input validation, error checking) should not be using assert().

  2. Mozilla has a variadic macro where the description is optional:

    We also have variants that are:

    * conditional, because MOZ_ASSERT_IF(a, b) is easier to understand than MOZ_ASSERT(!a || b)

    * enabled even in non-debug builds. We use these for cheap checks that are critical for safety. We also use these when we need data from the wild to test a hypothesis about why some other code is crashing.

    * non-fatal, for when we want something to be be considered a “failure” for regression/fuzz testing, but we want execution to continue.

    We use fatal assertions more (1) in newer, well-maintained code and (2) when failure would be likely to indicate a security hole. I think this strikes a balance between allowing developers to dogfood debug builds and making it likely they will report important bugs they hit.

  3. glib (GNOME’s utility library) includes a variety of assert macros, such as g_assert_cmpstr, g_assert_cmpint, etc that will print the values being checked. g_assert_cmpint(i, ==, 3) will exit with a message like ‘ERROR:test.c:7:main: assertion failed (i == 3): (0 == 3)’.

  4. Hi Mans, I didn’t want to get into proper use of assert, that has been covered pretty well elsewhere.

    Matt, Jesse, Jason, thanks, this is good stuff. I tend to favor leaner asserts myself but I can see how these more expressive versions would be useful in large codes.

    Jesse, I agree that compound conditions are bad due to adding ambiguity about which one failed.

  5. The one thing that I appreciate about MSVS’s _ASSERT() macro is that it gives the operator a choice about what to do about the failure. If the failure appears to be (or is known to be) non-fatal, the user can continue on as if nothing happened (or watch it crash, whichever). If the failure is suspicious/surprising, the user can quickly break in the debugger and investigate.

    This level of flexibility encourages developers to use asserts prolificly to validate their code’s assumptions/invariants without concern about being overly disruptive. I’ve even gotten to the point where I use _ASSERT() to test for valid situations that occur so rarely that I want to know when they occur (and confirm the situation really is valid).

    Assertions are among the best tools a developer has for ensuring code correctness. Anything/anybody that discourages their use is counter-productive.

  6. It’s probably stating the obvious, but your assertions should (wherever possible) state what the unexpected value was. “Invalid depth (depth was 3)” is much more informative.

  7. I like macros to automatically generate a useful message for me. I think solaris has something similar, and google uses something similar:

    This might look something like this (twiddle so the error message is to taste):

    i.e: a VERIFY(x, >, 3) would automatically generate an assert message like:

    “Assert error on main.c:80: expected x > 3 but got 0”

  8. I like that the C++11 static_assert takes two parameters. The first is the condition and the second is the error message, just like the Botan assert.

  9. I would always recommend that a company or project either define one or more custom ASSERT macros or borrow them from elsewhere (see earlier comments for suggestions). One of the key reasons is that there are widely varying expectations about how “assert” should be used. Define your own, and you get to set the expectations (and get that improved informativeness along the way).

    Worse, using “assert” in a header can violate the ODR if some translations define NDEBUG and others don’t. This is not as unlikely as many people think (due to varying expectations).

    At the very least, I recommend having an ASSERT macro that still runs in optimized builds. If termination is too extreme, at least log the failure, to aid your eventual debugging efforts.

    If you do use assert, one approach I’ve seen is to use ‘assert(!”Depth is negative”)’ when a section of code should be unreachable. This is probably too unusual to use unless in a project that agrees that it’s readable.

Comments are closed.