std-aux,glib-aux: rework nm_assert() implementations

1) ensure the compiler always sees the condition (even if
  it's unreachable). That is important, to avoid warnings
  about unused variables and to ensure the condition compiles.
  Previously, if NM_MORE_ASSERTS was enabled and NDEBUG (or
  G_DISABLE_ASSERT) was defined, the condition was not seen by
  the compiler.

2) to achieve point 1, we evaluate the expression now always in
  nm_assert() macro directly. This also has the benefit that we
  control exactly what is done there, and the implementation is
  guaranteed to not use any code that is not async-signal-safe
  (unless the assertion fails, of course).

3) add NM_MORE_ASSERTS_EFFECTIVE.
  When using no glib (libnm-std-aux), the assert is implemented
  by C89 assert(), while libnm-glib-aux redirects that to g_assert().
  Note that these assertions are only in effect, if both NM_MORE_ASSERTS
  and NDEBUG/G_DISABLE_ASSERT say so. NM_MORE_ASSERTS_EFFECTIVE
  is thus the effectively used level for nm_assert().

4) use the proper __assert_fail() and g_assertion_message_expr()
  calls. __assert_fail() is not standard, but it is there for glibc
  and musl. So relying on this is probably fine. Otherwise, we will
  get a compilation error and notice it.
This commit is contained in:
Thomas Haller
2022-10-20 08:49:51 +02:00
parent 197963fba3
commit 8e3299498d
2 changed files with 64 additions and 49 deletions

View File

@@ -525,16 +525,22 @@ nm_str_realloc(char *str)
/*****************************************************************************/ /*****************************************************************************/
/* redefine assertions to use g_assert*() */ /* redefine assertions to use g_assert*() */
#undef _nm_assert_call #undef _nm_assert_fail
#undef _nm_assert_call_not_reached #define _nm_assert_fail(msg) \
#define _nm_assert_call(cond) g_assert(cond) g_assertion_message_expr(G_LOG_DOMAIN, __FILE__, __LINE__, G_STRFUNC, msg)
#define _nm_assert_call_not_reached() g_assert_not_reached()
#undef _NM_ASSERT_FAIL_ENABLED
#ifndef G_DISABLE_ASSERT
#define _NM_ASSERT_FAIL_ENABLED 1
#else
#define _NM_ASSERT_FAIL_ENABLED 0
#endif
/* Usage: /* Usage:
* *
* if (NM_MORE_ASSERT_ONCE (5)) { extra_check (); } * if (NM_MORE_ASSERT_ONCE (5)) { extra_check (); }
* *
* This will only run the check once, and only if NM_MORE_ASSERT is >= than * This will only run the check once, and only if NM_MORE_ASSERTS is >= than
* more_assert_level. * more_assert_level.
*/ */
#define NM_MORE_ASSERT_ONCE(more_assert_level) \ #define NM_MORE_ASSERT_ONCE(more_assert_level) \

View File

@@ -194,60 +194,69 @@ typedef uint64_t _nm_bitwise nm_be64_t;
#define NM_MORE_ASSERTS 0 #define NM_MORE_ASSERTS 0
#endif #endif
#ifndef _nm_assert_call #define _nm_assert_fail(msg) __assert_fail((msg), __FILE__, __LINE__, __func__)
#define _nm_assert_call(cond) assert(cond) #ifndef NDEBUG
#define _nm_assert_call_not_reached() assert(0) #define _NM_ASSERT_FAIL_ENABLED 1
#else
#define _NM_ASSERT_FAIL_ENABLED 1
#endif #endif
#if NM_MORE_ASSERTS #define NM_MORE_ASSERTS_EFFECTIVE (_NM_ASSERT_FAIL_ENABLED ? NM_MORE_ASSERTS : 0)
#define nm_assert(cond) \
({ \ #define nm_assert(cond) \
_nm_assert_call(cond); \ ({ \
1; \ /* nm_assert() must do *nothing* of effect, except evaluating
* @cond (0 or 1 times).
*
* As such, nm_assert() is async-signal-safe (provided @cond is, and
* the assertion does not fail). */ \
if (NM_MORE_ASSERTS_EFFECTIVE == 0) { \
/* pass */ \
} else if NM_LIKELY (cond) { \
/* pass */ \
} else { \
_nm_assert_fail(#cond); \
} \
1; \
}) })
#define nm_assert_se(cond) \
({ \ #define nm_assert_se(cond) \
if (NM_LIKELY(cond)) { \ ({ \
; \ /* nm_assert() must do *nothing* of effect, except evaluating
} else { \ * @cond (exactly 1 times).
_nm_assert_call(0 && (cond)); \ *
} \ * As such, nm_assert() is async-signal-safe (provided @cond is, and
1; \ * the assertion does not fail). */ \
if NM_LIKELY (cond) { \
/* pass */ \
} else if (NM_MORE_ASSERTS_EFFECTIVE == 0) { \
/* pass */ \
} else { \
_nm_assert_fail(#cond); \
} \
1; \
}) })
#define nm_assert_not_reached() \
({ \ #define nm_assert_not_reached() \
_nm_assert_call_not_reached(); \ ({ \
1; \ _nm_assert_fail("unreachable"); \
1; \
}) })
#else
#define nm_assert(cond) \
({ \
if (0) { \
if (cond) {} \
} \
1; \
})
#define nm_assert_se(cond) \
({ \
if (NM_LIKELY(cond)) { \
; \
} \
1; \
})
#define nm_assert_not_reached() ({ 1; })
#endif
/* This is similar nm_assert_not_reached(), but it's supposed to be used only during /* This is similar nm_assert_not_reached(), but it's supposed to be used only during
* development. Like _XXX_ comments, they can be used as a marker that something still * development. Like _XXX_ comments, they can be used as a marker that something still
* needs to be done. */ * needs to be done. */
#define XXX(msg) \ #define XXX(msg) \
nm_assert(!"X" \ ({ \
"XX error: " msg "") _nm_assert_fail("X" \
"XX error: " msg ""); \
1; \
})
#define nm_assert_unreachable_val(val) \ #define nm_assert_unreachable_val(val) \
({ \ ({ \
nm_assert_not_reached(); \ _nm_assert_fail("unreachable value " #val); \
(val); \ (val); \
}) })
#define NM_STATIC_ASSERT(cond) static_assert(cond, "") #define NM_STATIC_ASSERT(cond) static_assert(cond, "")