From efcd7315e65acdc4426da7ae6686533e29d19411 Mon Sep 17 00:00:00 2001
From: Thomas Haller <thaller@redhat.com>
Date: Thu, 31 Aug 2023 17:22:13 +0200
Subject: [PATCH 1/2] generic: document that
 _c_boolean_expr_()/_c_unlikely_()/_c_likely_() are async-signal-safe

They are very fundamental. It's important that we can use them at all places.
Document it.
---
 src/c-stdaux-generic.h | 6 ++++++
 1 file changed, 6 insertions(+)

diff --git a/src/c-stdaux-generic.h b/src/c-stdaux-generic.h
index 6f87fd4..6126e7b 100644
--- a/src/c-stdaux-generic.h
+++ b/src/c-stdaux-generic.h
@@ -129,6 +129,8 @@ extern "C" {
  *
  * Outside of macros, this has no added value.
  *
+ * This macro is async-signal-safe, provided that the condition is.
+ *
  * Return: Evaluates to the value of ``!!_x``.
  */
 #define _c_boolean_expr_(_x) _c_internal_boolean_expr_(__COUNTER__, _x)
@@ -164,6 +166,8 @@ extern "C" {
  *
  * Alias for ``__builtin_expect(!!(_x), 1)``.
  *
+ * This macro is async-signal-safe, provided that the condition is.
+ *
  * Return: The expression ``!!_x`` is evaluated and returned.
  */
 #define _c_likely_(_x) _c_internal_likely_(_x)
@@ -200,6 +204,8 @@ extern "C" {
  *
  * Alias for ``__builtin_expect(!!(_x), 0)``.
  *
+ * This macro is async-signal-safe, provided that the condition is.
+ *
  * Return: The expression ``!!_x`` is evaluated and returned.
  */
 #define _c_unlikely_(_x) _c_internal_unlikely_(_x)

From 81e28b0c425fa1c693b74844b67a48522a434785 Mon Sep 17 00:00:00 2001
From: Thomas Haller <thaller@redhat.com>
Date: Thu, 31 Aug 2023 12:10:46 +0200
Subject: [PATCH 2/2] assert: add "c-stdaux-assert.h" with c_more_assert()

The standard assert() and c_assert() macros are enabled by default, allowing
debugging assertions that can be disabled by defining NDEBUG. Notably,
c_assert() always evaluates the condition, whereas assert() expects
side-effect-free conditions.

However, these macros are often active in most builds, as many projects do not
define NDEBUG, even for release builds. While assertions are essential for
debugging and testing, they do add runtime overhead. Using different levels of
assertion can help manage this. Some assertions check conditions are almost
certain to hold, such as function argument validation where code review easily
confirms invariants. In such cases, always-on assertions may not be justified,
and developers may prefer to avoid assertions that are still active in release
builds.

This is where `c_more_assert()` fits in. By setting C_MORE_ASSERT to 2 or
higher, the user can enable these assertions. Otherwise, they are disabled by
default, minimizing runtime cost. `c_more_assert_with()` extends this
functionality by allowing selective enablement at specific assertion levels
through C_MORE_ASSERT.

Compared to assert(), these macros other advantages beside being disabled by
default:
- The compiler sees the expression regardless of NDEBUG or assertion level,
  catching potential compile errors in expressions and avoiding certain
  compiler warnings that otherwise depend on whether the assertion is enabled.
- Constant expressions that fail always mark the code path as unreachable,
  reducing warnings on unreachable paths.

Also, as c_assert() evaluates all expressions, unreachable paths are now
recognized even with NDEBUG defined. That may allow aggressive optimization as
the compiler can reason that the assertion holds. This is what the user asks
for with NDEBUG.

Similar to <assert.h>, <c-stdaux-assert.h> supports multiple inclusions and
reconfiguration through NDEBUG and C_MORE_ASSERT.

Assertion failures now print only critical information (e.g., file:line),
omitting less useful strings unless C_MORE_ASSERT >= 2, to reduce binary size.

Additionally, C_MORE_ASSERT_LEVEL represents the effective assertion level and
can be used both as a macro and a runtime constant, allowing conditionally
compiled code:

    if (C_MORE_ASSERT_LEVEL >= 5) {
        /* Elaborate checking of invariant. The compiler validates this code
         * while optimizing out the entire block due to the constant expression. */
    }

One important difference might be that c_assert() is now a do{}while(0)
statement, previously it was an expression. I don't think anybody should
care about the difference, especially since the expression previously
also returned void.
---
 src/c-stdaux-assert.h  | 194 +++++++++++++++++++++++++++++++++++++++++
 src/c-stdaux-generic.h |  20 -----
 src/c-stdaux.h         |   2 +
 src/docs/api.rst       |   2 +-
 src/meson.build        |   1 +
 src/test-api.c         |  40 +++++++++
 6 files changed, 238 insertions(+), 21 deletions(-)
 create mode 100644 src/c-stdaux-assert.h

diff --git a/src/c-stdaux-assert.h b/src/c-stdaux-assert.h
new file mode 100644
index 0000000..a878e80
--- /dev/null
+++ b/src/c-stdaux-assert.h
@@ -0,0 +1,194 @@
+/* There is no include guard. Just like <assert.h>, we can include this header
+ * multiple times to update the macros for NDEBUG/C_MORE_ASSERT changes.
+ *
+ * The user can define NDEBUG to disable all asserts.
+ *
+ * The user can define C_MORE_ASSERT to a non-negative number to control
+ * which assertions are enabled.
+ */
+
+#include <assert.h>
+#include <c-stdaux-generic.h>
+
+/**
+ * C_MORE_ASSERT: user define to configure assertion levels (similar to NDEBUG).
+ *
+ * If NDEBUG is defined, then assert() is a nop. This also implies
+ * C_MORE_ASSERT_LEVEL of zero, which means that c_more_assert() does not
+ * evaluate the condition at runtime.
+ *
+ * Otherwise, if C_MORE_ASSERT is defined it determines the
+ * C_MORE_ASSERT_LEVEL. If C_MORE_ASSERT, is undefined, C_MORE_ASSERT_LEVEL
+ * defaults to 1.
+ *
+ * The effective C_MORE_ASSERT_LEVEL affects whether c_more_assert() and
+ * c_more_assert_with() evaluates the condition at runtime. The purpose is that
+ * more assertions are disabled by default (and in release builds). For
+ * debugging and testing, define C_MORE_ASSERT to a number larger than 1.
+ */
+#undef C_MORE_ASSERT_LEVEL
+#ifdef NDEBUG
+#define C_MORE_ASSERT_LEVEL 0
+#elif !defined(C_MORE_ASSERT)
+#define C_MORE_ASSERT_LEVEL 1
+#else
+#define C_MORE_ASSERT_LEVEL (C_MORE_ASSERT)
+#endif
+
+#undef _c_assert_fail
+#if C_MORE_ASSERT_LEVEL > 0 && defined(__GNU_LIBRARY__)
+/* Depending on "_with_msg", we hide the "msg" string unless we build with
+ * "C_MORE_ASSERT > 1". The point is to avoid embedding debugging strings in
+ * the binary with release builds.
+ *
+ * The assertion failure messages are often not very useful for the end user
+ * and for the developer __FILE__:__LINE__ is sufficient.
+ *
+ * __assert_fail() also exists on musl, but we don't have a separate detection
+ * for musl.
+ */
+#define _c_assert_fail(_with_msg, msg)                                       \
+    __assert_fail(                                                           \
+        C_MORE_ASSERT_LEVEL > 1 || (_with_msg) ? "" msg "" : "<dropped>",    \
+        __FILE__, __LINE__,                                                  \
+        C_MORE_ASSERT_LEVEL > 1 || (_with_msg) ? "<unknown-fcn>" : __func__)
+#else
+#define _c_assert_fail(_with_msg, msg) \
+    do {                               \
+        assert(false && msg);          \
+        _c_unreachable_code();         \
+    } while (0)
+#endif
+
+/* There is an include guard. The remainder of this header is only evaluated
+ * once upon multiple inclusions. */
+#if !defined(C_HAS_STDAUX_ASSERT)
+#define C_HAS_STDAUX_ASSERT
+
+#if defined(C_COMPILER_GNUC)
+#define _c_unreachable_code() __builtin_unreachable()
+#else /* defined(C_COMPILER_GNUC) */
+#define _c_unreachable_code()                                                 \
+    do {                                                                      \
+        /* Infinite loop without side effects is undefined behavior and marks \
+         * unreachable code. */                                               \
+    } while (1)
+#endif /* defined(C_COMPILER_GNUC) */
+
+#if defined(C_COMPILER_GNUC)
+#define _c_assert_constant(_cond)                                            \
+    do {                                                                     \
+        if (__builtin_constant_p(_cond) && !(_cond)) {                       \
+            /* With gcc, constant expressions are still evaluated and result \
+             * in unreachable code too.                                      \
+             *                                                               \
+             * The point is to avoid compiler warnings with                  \
+             * c_more_assert(false) and NDEBUG.                              \
+             */                                                              \
+            _c_unreachable_code();                                           \
+        }                                                                    \
+    } while (0)
+#else /* defined(C_COMPILER_GNUC) */
+#define _c_assert_constant(_cond) \
+    do {                          \
+        /* This does nothing. */  \
+    } while (0)
+#endif /* defined(C_COMPILER_GNUC) */
+
+/**
+ * c_more_assert_with() - Conditional runtime assertion.
+ * @_level: Assertion level that determines whether the assertion is evaluated,
+ *     based on comparison with C_MORE_ASSERT_LEVEL.
+ * @_cond: Condition or expression to validate.
+ *
+ * This macro performs an assertion based on the specified _level in comparison
+ * to the compile-time constant C_MORE_ASSERT_LEVEL. C_MORE_ASSERT_LEVEL
+ * typically defaults to 1 but can be modified by defining NDEBUG or
+ * C_MORE_ASSERT.
+ *
+ * - If _level is less than C_MORE_ASSERT_LEVEL, the condition is ignored and
+ *   the assertion code is excluded from the final build, allowing for performance
+ *   optimizations.
+ *
+ * - If _cond is a constant expression that fails, the compiler will mark the code
+ *   path as unreachable, regardless of NDEBUG or the configured C_MORE_ASSERT_LEVEL.
+ *
+ * Unlike c_assert(), which always evaluates the condition,
+ * `c_more_assert_with()` * only evaluates the condition if the specified _level *
+ * meets the configured assertion threshold. This conditional behavior requires *
+ * that _cond has no side effects, as it may not be evaluated in all cases.
+ *
+ * Note: This macro is usually excluded from regular builds unless explicitly
+ * enabled by defining C_MORE_ASSERT, making it particularly useful for debugging
+ * and testing without incurring runtime costs in production builds.
+ *
+ * The macro is async-signal-safe, if @_cond is and the assertion doesn't fail.
+ */
+#define c_more_assert_with(_level, _cond)                        \
+    do {                                                         \
+        /* c_more_assert_with() must do *nothing* of effect,     \
+         * except evaluating @_cond (0 or 1 times).              \
+         *                                                       \
+         * As such, it is async-signal-safe (provided @_cond and \
+         * @_level is, and the assertion does not fail). */      \
+        if ((_level) < C_MORE_ASSERT_LEVEL) {                    \
+            _c_assert_constant(_cond);                           \
+        } else if (_c_likely_(_cond)) {                          \
+            /* pass */                                           \
+        } else {                                                 \
+            _c_assert_fail(false, #_cond);                       \
+        }                                                        \
+    } while (0)
+
+/**
+ * c_more_assert() - Conditional runtime assertion.
+ * @_cond: Condition or expression to validate.
+ *
+ * This is the same as c_more_assert_with(2, _cond). This means that
+ * the assertion is usually disabled in regular builds unless the user
+ * opts in by setting C_MORE_ASSERT to 2 or larger.
+ *
+ * The macro is async-signal-safe, if @_cond is and the assertion doesn't fail.
+ */
+#define c_more_assert(_cond) c_more_assert_with(2, _cond)
+
+/**
+ * c_assert() - Runtime assertions
+ * @_cond:                 Result of an expression
+ *
+ * This function behaves like the standard ``assert(3)`` macro. That is, if
+ * ``NDEBUG`` is defined, it is a no-op. In all other cases it will assert that
+ * the result of the passed expression is true.
+ *
+ * Unlike the standard ``assert(3)`` macro, this function always evaluates its
+ * argument. This means side-effects will always be evaluated! However, if the
+ * macro is used with constant expressions, the compiler will be able to
+ * optimize it away.
+ *
+ * The macro is async-signal-safe, if @_cond is and the assertion doesn't fail.
+ */
+#define c_assert(_cond)                    \
+    do {                                   \
+        if (!_c_likely_(_cond)) {          \
+            _c_assert_fail(false, #_cond); \
+        }                                  \
+    } while (0)
+
+/**
+ * c_assert_not_reached() - Fail assertion when called.
+ *
+ * With C_COMPILER_GNUC, the macro calls assert(false) and marks the code
+ * path as __builtin_unreachable(). The benefit is that also with NDEBUG the
+ * compiler considers the path unreachable.
+ *
+ * Otherwise, just calls assert(false).
+ *
+ * The macro is async-signal-safe.
+ */
+#define c_assert_not_reached() _c_assert_fail(true, "unreachable")
+
+#endif /* !defined(C_HAS_STDAUX_ASSERT) */
+
+#ifdef __cplusplus
+}
+#endif
diff --git a/src/c-stdaux-generic.h b/src/c-stdaux-generic.h
index 6126e7b..3800b8c 100644
--- a/src/c-stdaux-generic.h
+++ b/src/c-stdaux-generic.h
@@ -81,7 +81,6 @@ extern "C" {
  */
 /**/
 
-#include <assert.h>
 #include <errno.h>
 #include <inttypes.h>
 #include <limits.h>
@@ -316,25 +315,6 @@ extern "C" {
 #  define c_internal_assume_aligned(_ptr, _alignment, _offset) ((void)(_alignment), (void)(_offset), (_ptr))
 #endif
 
-/**
- * c_assert() - Runtime assertions
- * @_x:                 Result of an expression
- *
- * This function behaves like the standard ``assert(3)`` macro. That is, if
- * ``NDEBUG`` is defined, it is a no-op. In all other cases it will assert that
- * the result of the passed expression is true.
- *
- * Unlike the standard ``assert(3)`` macro, this function always evaluates its
- * argument. This means side-effects will always be evaluated! However, if the
- * macro is used with constant expressions, the compiler will be able to
- * optimize it away.
- */
-#define c_assert(_x) (                                                          \
-                _c_likely_(_x)                                                  \
-                        ? assert(true && #_x)                                   \
-                        : assert(false && #_x)                                  \
-        )
-
 /**
  * c_errno() - Return valid errno
  *
diff --git a/src/c-stdaux.h b/src/c-stdaux.h
index 0eccb20..23be069 100644
--- a/src/c-stdaux.h
+++ b/src/c-stdaux.h
@@ -50,6 +50,8 @@ extern "C" {
 #  include <c-stdaux-unix.h>
 #endif
 
+#include <c-stdaux-assert.h>
+
 #ifdef __cplusplus
 }
 #endif
diff --git a/src/docs/api.rst b/src/docs/api.rst
index a25e9fa..be55c53 100644
--- a/src/docs/api.rst
+++ b/src/docs/api.rst
@@ -1,5 +1,5 @@
 API
 ===
 
-.. c:autodoc:: c-stdaux.h c-stdaux-generic.h c-stdaux-gnuc.h c-stdaux-unix.h
+.. c:autodoc:: c-stdaux.h c-stdaux-generic.h c-stdaux-gnuc.h c-stdaux-unix.h c-stdaux-assert.h
    :transform: kerneldoc
diff --git a/src/meson.build b/src/meson.build
index 0e958af..16dbd96 100644
--- a/src/meson.build
+++ b/src/meson.build
@@ -20,6 +20,7 @@ libcstdaux_dep = declare_dependency(
 if not meson.is_subproject()
         install_headers(
                 'c-stdaux.h',
+                'c-stdaux-assert.h',
                 'c-stdaux-generic.h',
                 'c-stdaux-gnuc.h',
                 'c-stdaux-unix.h',
diff --git a/src/test-api.c b/src/test-api.c
index e52d42c..1dab1a5 100644
--- a/src/test-api.c
+++ b/src/test-api.c
@@ -20,6 +20,8 @@ static void direct_cleanup_fn(int p) { (void)p; }
 C_DEFINE_CLEANUP(int, cleanup_fn);
 C_DEFINE_DIRECT_CLEANUP(int, direct_cleanup_fn);
 
+int global_int_0;
+
 static void test_api_generic(void) {
         /* C_COMPILER_* */
         {
@@ -155,6 +157,34 @@ static void test_api_generic(void) {
                 for (i = 0; i < sizeof(fns) / sizeof(*fns); ++i)
                         c_assert(!!fns[i]);
         }
+
+        if (false)
+                c_assert_not_reached();
+
+        switch (global_int_0) {
+        default:
+                /* Test that we don't get a -Wimplicit-fallthrough warning and
+                 * the compiler detect that the function doesn't return. */
+                c_assert_not_reached();
+        case 1:
+        case 0:
+                c_assert(global_int_0 == 0);
+                break;
+        }
+
+        {
+                int v;
+
+                v = 0;
+                c_assert((v = 1));
+                c_assert(v == 1);
+
+                v = 5;
+                c_more_assert_with(C_MORE_ASSERT_LEVEL - 1, (++v == -1));
+                c_more_assert_with(C_MORE_ASSERT_LEVEL, (++v == 6));
+                c_more_assert_with(C_MORE_ASSERT_LEVEL + 1, (++v == 7));
+                c_assert(v == 7);
+        }
 }
 
 #else /* C_MODULE_GENERIC */
@@ -280,6 +310,16 @@ static void test_api_gnuc(void) {
         {
                 c_assert(c_align_to(0, 0) == 0);
         }
+
+        /* Check that assertions can be nested without compiler warnings. That easily
+         * happens when asserting on macros that contain expression statements and
+         * themselves assertions. */
+        {
+            c_assert(__extension__({ c_assert(true); true; }));
+            c_assert(__extension__({ c_more_assert(true); true; }));
+            c_more_assert(__extension__({ c_assert(true); true; }));
+            c_more_assert(__extension__({ c_more_assert(true); true; }));
+        }
 }
 
 #else /* C_MODULE_GNUC */