Assertions

Check for unexpected events

assert() - abort the program if assertion is false

The assert() macro can help programmers find bugs in their programs, or handle exceptional cases via a crash that will produce limited debugging output.

It’s a useful tool, but it has some limitations when used in NeoMutt:

  • It doesn’t clear the screen.
  • A debugger stops about 6 levels beneath the actual macro
  • It kills the running program

ASSERT() - stop the program if assertion is false

NeoMutt now has an upgraded ASSERT() macro.

  1. It calls endwin() to reset the screen mode
  2. A debugger will stop ON the macro, rather than about 6 levels beneath it
  3. You can continue running NeoMutt by stepping over the ASSERT()

Inspiration from Chris Wellons’ https://nullprogram.com/blog/2022/06/26/

Using ASSERT()

To use the ASSERT() macro:

#include "mutt/lib.h"

void function(const char *str)
{
  ASSERT(str);
}

To disable the macro at build time, define NDEBUG, e.g.

./configure OPTIONS...
make "EXTRA_CFLAGS="-DNDEBUG"

Sample Output

When combined with configure --debug-backtrace the output may look like:

NeoMutt 20240425-37-55fed8
Backtrace
    assertion_dump() ip = 21, sp = 7ffd651127a0
    op_print() ip = 4c, sp = 7ffd651127d0
    index_function_dispatcher() ip = 14e, sp = 7ffd65112820
    dlg_index() ip = b70, sp = 7ffd65112890
    main() ip = 2a41, sp = 7ffd65112950

index/functions.c:2080:op_print() -- assertion failed ((priv->menu->current % 2) == 1)
Illegal instruction (core dumped)

Debugging

When the assertion fails, the debugger will stop immediately on the ASSERT() line. This gives you a chance to examine the variables, and even step over the problem.

2075 /**
2076│  * op_print - Print the current entry - Implements ::index_function_t - @ingroup index_function_api
2077│  */
2078 static int op_print(struct IndexSharedData *shared, struct IndexPrivateData *priv, int op)
2079 {
2080├─> ASSERT((priv->menu->current % 2) == 1);
2081   struct EmailArray ea = ARRAY_HEAD_INITIALIZER;
2082   ea_add_tagged(&ea, shared->mailbox_view, shared->email, priv->tag_prefix);
2083   mutt_print_message(shared->mailbox, &ea);
2084   ARRAY_FREE(&ea);
2085
2086   /* in an IMAP folder index with imap_peek=no, printing could change
2087│    * new or old messages status to read. Redraw what's needed.  */
2088   const bool c_imap_peek = cs_subset_bool(shared->sub, "imap_peek");
2089   if ((shared->mailbox->type == MUTT_IMAP) && !c_imap_peek)
2090   {
2091     menu_queue_redraw(priv->menu, (priv->tag_prefix ? MENU_REDRAW_INDEX : MENU_REDRAW_CURRENT));
2092   }
2093
2094   return FR_SUCCESS;
2095 }

In gdb, you can step over this problem by jumping to a new line, e.g. j 2094

NeoMutt will continue running and at the next refresh repaint the entire screen.

Search by Algolia