about summary refs log tree commit diff
path: root/src/libnm-std-aux/nm-std-aux.h
diff options
context:
space:
mode:
Diffstat (limited to 'src/libnm-std-aux/nm-std-aux.h')
-rw-r--r--src/libnm-std-aux/nm-std-aux.h384
1 files changed, 281 insertions, 103 deletions
diff --git a/src/libnm-std-aux/nm-std-aux.h b/src/libnm-std-aux/nm-std-aux.h
index d49f7c43..e556aa4b 100644
--- a/src/libnm-std-aux/nm-std-aux.h
+++ b/src/libnm-std-aux/nm-std-aux.h
@@ -16,9 +16,11 @@
 
 #define _nm_packed             __attribute__((__packed__))
 #define _nm_unused             __attribute__((__unused__))
+#define _nm_always_inline      __attribute__((__always_inline__))
 #define _nm_used               __attribute__((__used__))
 #define _nm_pure               __attribute__((__pure__))
 #define _nm_const              __attribute__((__const__))
+#define _nm_noreturn           __attribute__((__noreturn__))
 #define _nm_warn_unused_result __attribute__((__warn_unused_result__))
 #define _nm_printf(a, b)       __attribute__((__format__(__printf__, a, b)))
 #define _nm_align(s)           __attribute__((__aligned__(s)))
@@ -42,8 +44,8 @@
 #endif
 #endif
 
-#if defined(__clang__) && __clang_major__ == 13
-/* Clang 13 can emit -Wunused-but-set-variable warning for cleanup variables
+#if defined(__clang__)
+/* Clang can emit -Wunused-but-set-variable warning for cleanup variables
  * that are only assigned (never used otherwise). Hack around */
 #define _nm_auto_extra _nm_unused
 #else
@@ -73,6 +75,14 @@
 
 /*****************************************************************************/
 
+/* This is mainly used in case of failed assertions. Usually assert() itself
+ * already ensures that the code path is marked as unreachable, however with
+ * NDEBUG that might not be the case. We want to mark the code as unreachable
+ * even with NDEBUG/G_DISABLE_ASSERT. */
+#define _nm_unreachable_code() __builtin_unreachable()
+
+/*****************************************************************************/
+
 #ifndef _NM_CC_SUPPORT_AUTO_TYPE
 #if (defined(__GNUC__) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 9)))
 #define _NM_CC_SUPPORT_AUTO_TYPE 1
@@ -154,6 +164,15 @@ typedef uint64_t _nm_bitwise nm_be64_t;
 
 /*****************************************************************************/
 
+/* NM_BOOLEAN_EXPR(expr) exists to ensure that there is still a compiler
+ * warning when accidentally(?) using assignments like `NM_BOOLEAN_EXPR(x = 1)`
+ * Compiler will warn about that and suggest either == or additional parentheses
+ * `NM_BOOLEAN_EXPR((x = 1))`.
+ *
+ * This also is true for users of this macro, like `NM_LIKELY(x = 1)` and further
+ * up `nm_assert(x = 1)`. Those users must make sure not themselves adding additional
+ * parentheses around the condition.
+ */
 #define _NM_BOOLEAN_EXPR_IMPL(v, expr) \
     ({                                 \
         int NM_UNIQ_T(V, v);           \
@@ -164,14 +183,22 @@ typedef uint64_t _nm_bitwise nm_be64_t;
             NM_UNIQ_T(V, v) = 0;       \
         NM_UNIQ_T(V, v);               \
     })
-#define NM_BOOLEAN_EXPR(expr) _NM_BOOLEAN_EXPR_IMPL(NM_UNIQ, expr)
+
+#if defined(__GNUC__) && (__GNUC__ > 4)
+#define NM_BOOLEAN_EXPR(expr)                         \
+    __builtin_choose_expr(__builtin_constant_p(expr), \
+                          (!!(expr)),                 \
+                          _NM_BOOLEAN_EXPR_IMPL(NM_UNIQ, expr))
+#else
+#define NM_BOOLEAN_EXPR(expr) (!!(expr))
+#endif
 
 #if defined(__GNUC__) && (__GNUC__ > 2) && defined(__OPTIMIZE__)
-#define NM_LIKELY(expr)   (__builtin_expect(NM_BOOLEAN_EXPR(expr), 1))
-#define NM_UNLIKELY(expr) (__builtin_expect(NM_BOOLEAN_EXPR(expr), 0))
+#define NM_LIKELY(expr)   __builtin_expect(NM_BOOLEAN_EXPR(expr), 1)
+#define NM_UNLIKELY(expr) __builtin_expect(NM_BOOLEAN_EXPR(expr), 0)
 #else
-#define NM_LIKELY(expr)   (NM_BOOLEAN_EXPR(expr))
-#define NM_UNLIKELY(expr) (NM_BOOLEAN_EXPR(expr))
+#define NM_LIKELY(expr)   NM_BOOLEAN_EXPR(expr)
+#define NM_UNLIKELY(expr) NM_BOOLEAN_EXPR(expr)
 #endif
 
 /*****************************************************************************/
@@ -192,67 +219,106 @@ typedef uint64_t _nm_bitwise nm_be64_t;
 #define NM_MORE_ASSERTS 0
 #endif
 
-#ifndef _nm_assert_call
-#define _nm_assert_call(cond)         assert(cond)
-#define _nm_assert_call_not_reached() assert(0)
+#if NM_MORE_ASSERTS == 0
+/* The string with the assertion check and the function name blows up the
+ * binary size. In production mode, let's drop those, similar to
+ * g_assertion_message_expr.
+ *
+ * Note that <assert.h> can be included multiple times. We can thus
+ * not redefine __assert_fail(...). Instead, just redefine the name
+ * __assert_fail. */
+_nm_noreturn static inline void
+_nm_assert_fail_internal(const char  *assertion,
+                         const char  *file,
+                         unsigned int line,
+                         const char  *function)
+{
+    __assert_fail("<dropped>", file, line, "<unknown-fcn>");
+}
+#define __assert_fail _nm_assert_fail_internal
 #endif
 
-#if NM_MORE_ASSERTS
-#define nm_assert(cond)        \
-    ({                         \
-        _nm_assert_call(cond); \
-        1;                     \
-    })
-#define nm_assert_se(cond)                \
-    ({                                    \
-        if (NM_LIKELY(cond)) {            \
-            ;                             \
-        } else {                          \
-            _nm_assert_call(0 && (cond)); \
-        }                                 \
-        1;                                \
-    })
-#define nm_assert_not_reached()        \
-    ({                                 \
-        _nm_assert_call_not_reached(); \
-        1;                             \
-    })
+#ifndef NDEBUG
+#define _NM_ASSERT_FAIL_ENABLED 1
+#define _nm_assert_fail(msg)    __assert_fail((msg), __FILE__, __LINE__, __func__)
 #else
-#define nm_assert(cond)  \
-    ({                   \
-        if (0) {         \
-            if (cond) {} \
-        }                \
-        1;               \
+#define _NM_ASSERT_FAIL_ENABLED 0
+#define _nm_assert_fail(msg)                 \
+    do {                                     \
+        _nm_unused const char *_msg = (msg); \
+                                             \
+        _nm_unreachable_code();              \
+    } while (0)
+#endif
+
+#define NM_MORE_ASSERTS_EFFECTIVE (_NM_ASSERT_FAIL_ENABLED ? NM_MORE_ASSERTS : 0)
+
+#define nm_assert(cond)                                                \
+    ({                                                                 \
+        /* 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) {                          \
+            if (__builtin_constant_p(cond) && !(cond)) {               \
+                /* Constant expressions are still evaluated and result
+                 * in unreachable code. This handles nm_assert(FALSE). */ \
+                _nm_unreachable_code();                                \
+            }                                                          \
+            /* pass */                                                 \
+        } else if (NM_LIKELY(cond)) {                                  \
+            /* pass */                                                 \
+        } else {                                                       \
+            _nm_assert_fail(#cond);                                    \
+        }                                                              \
+        1;                                                             \
     })
-#define nm_assert_se(cond)     \
-    ({                         \
-        if (NM_LIKELY(cond)) { \
-            ;                  \
-        }                      \
-        1;                     \
+
+#define nm_assert_se(cond)                                            \
+    ({                                                                \
+        /* nm_assert() must do *nothing* of effect, except evaluating
+         * @cond (exactly 1 times).
+         *
+         * As such, nm_assert() is async-signal-safe (provided @cond is, and
+         * the assertion does not fail). */ \
+        if (NM_LIKELY(cond)) {                                        \
+            /* pass */                                                \
+        } else {                                                      \
+            if (NM_MORE_ASSERTS_EFFECTIVE != 0) {                     \
+                _nm_assert_fail(#cond);                               \
+            }                                                         \
+            _nm_unreachable_code();                                   \
+        }                                                             \
+        1;                                                            \
+    })
+
+#define nm_assert_not_reached()         \
+    ({                                  \
+        _nm_assert_fail("unreachable"); \
+        1;                              \
     })
-#define nm_assert_not_reached() ({ 1; })
-#endif
 
 /* 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
  * needs to be done. */
-#define XXX(msg)   \
-    nm_assert(!"X" \
-               "XX error: " msg "")
+#define XXX(msg)                              \
+    ({                                        \
+        _nm_assert_fail("X"                   \
+                        "XX error: " msg ""); \
+        1;                                    \
+    })
 
-#define nm_assert_unreachable_val(val) \
-    ({                                 \
-        nm_assert_not_reached();       \
-        (val);                         \
+#define nm_assert_unreachable_val(val)              \
+    ({                                              \
+        _nm_assert_fail("unreachable value " #val); \
+        (val);                                      \
     })
 
 #define NM_STATIC_ASSERT(cond) static_assert(cond, "")
 #define NM_STATIC_ASSERT_EXPR_1(cond) \
-    (sizeof(struct { char __static_assert_expr_1[(cond) ? 1 : -1]; }) == 1)
-#define NM_STATIC_ASSERT_EXPR_VOID(cond) \
-    ((void) (sizeof(struct { char __static_assert_expr_void[(cond) ? 1 : -1]; }) == 1))
+    (!!sizeof(struct { unsigned __static_assert_expr_1 : ((cond) ? 2 : -1); }))
+#define NM_STATIC_ASSERT_EXPR_VOID(cond) ((void) NM_STATIC_ASSERT_EXPR_1(cond))
 
 /*****************************************************************************/
 
@@ -282,12 +348,8 @@ typedef uint64_t _nm_bitwise nm_be64_t;
      * It's useful to check the let the compiler ensure that @value is
      * of a certain type. */
 #define _NM_ENSURE_TYPE(type, value) (_Generic((value), type : (value)))
-#define _NM_ENSURE_TYPE_CONST(type, value)              \
-    (_Generic((value), const type                       \
-              : ((const type)(value)), const type const \
-              : ((const type)(value)), type             \
-              : ((const type)(value)), type const       \
-              : ((const type)(value))))
+#define _NM_ENSURE_TYPE_CONST(type, value) \
+    (_Generic((value), const type : ((const type)(value)), type : ((const type)(value))))
 #else
 #define _NM_ENSURE_TYPE(type, value)       (value)
 #define _NM_ENSURE_TYPE_CONST(type, value) ((const type)(value))
@@ -413,6 +475,24 @@ nm_mult_clamped_u(unsigned a, unsigned b)
 
 /*****************************************************************************/
 
+static inline size_t
+NM_ALIGN_TO(size_t l, size_t ali)
+{
+    nm_assert(nm_utils_is_power_of_two(ali));
+
+    if (l > SIZE_MAX - (ali - 1))
+        return SIZE_MAX; /* indicate overflow */
+
+    return ((l + ali - 1) & ~(ali - 1));
+}
+
+#define NM_ALIGN4(l)    NM_ALIGN_TO(l, 4)
+#define NM_ALIGN8(l)    NM_ALIGN_TO(l, 8)
+#define NM_ALIGN(l)     NM_ALIGN_TO(l, sizeof(void *))
+#define NM_ALIGN_PTR(p) ((void *) NM_ALIGN((uintptr_t) (p)))
+
+/*****************************************************************************/
+
 #define NM_SWAP(p_a, p_b)                   \
     do {                                    \
         typeof(*(p_a)) *const _p_a = (p_a); \
@@ -504,10 +584,21 @@ nm_memeq(const void *s1, const void *s2, size_t len)
     return nm_memcmp(s1, s2, len) == 0;
 }
 
+static inline void *
+nm_memcpy(void *restrict dest, const void *restrict src, size_t n)
+{
+    /* Workaround undefined behavior in memcpy() with NULL pointers. */
+    if (n == 0)
+        return dest;
+
+    nm_assert(src);
+    return memcpy(dest, src, n);
+}
+
 /*
  * Very similar to g_str_has_prefix() with the obvious meaning.
  * Differences:
- * 1) suffix is enforced to be a C string literal
+ * 1) prefix is enforced to be a C string literal
  *   (it is thus more restricted, but you'll know it at compile time).
  * 2) it accepts str==NULL
  *   (it is thus more forgiving than g_str_has_prefix())
@@ -902,38 +993,6 @@ _NM_IN_STRSET_EVAL_op_streq(const char *x1, const char *x)
 
 /*****************************************************************************/
 
-/**
- * nm_close:
- *
- * Like close() but throws an assertion if the input fd is
- * invalid.  Closing an invalid fd is a programming error, so
- * it's better to catch it early.
- */
-static inline int
-nm_close(int fd)
-{
-    int r;
-
-    r = close(fd);
-    nm_assert(r != -1 || fd < 0 || errno != EBADF);
-    return r;
-}
-
-static inline bool
-nm_clear_fd(int *p_fd)
-{
-    int fd;
-
-    if (!p_fd || (fd = *p_fd) < 0)
-        return false;
-
-    *p_fd = -1;
-    nm_close(fd);
-    return true;
-}
-
-/*****************************************************************************/
-
 /* Note: @value is only evaluated when *out_val is present.
  * Thus,
  *    NM_SET_OUT (out_str, g_strdup ("hallo"));
@@ -1011,13 +1070,106 @@ _nm_auto_protect_errno(const int *p_saved_errno)
 
 /*****************************************************************************/
 
+/**
+ * nm_close_with_error:
+ *
+ * Wrapper around close().
+ *
+ * This fails an nm_assert() for EBADF with a non-negative file descriptor. Trying
+ * to close an invalid file descriptor is always a serious bug. Never use close()
+ * directly, because we want to catch such bugs.
+ *
+ * This also suppresses any EINTR and pretends success. That is appropriate
+ * on Linux (but not necessarily on other POSIX systems).
+ *
+ * In no case is it appropriate to use @fd afterwards (or retry).
+ *
+ * This function returns 0 on success, or a negative errno value.
+ * On success, errno is undefined afterwards. On failure, errno is
+ * the same as the (negative) return value.
+ *
+ * In the common case, when you don't intend to handle the error from close(),
+ * prefer nm_close() over nm_close_with_error(). Never use close() directly.
+ *
+ * The function is also async-signal-safe (unless an assertion fails).
+ *
+ * Returns: 0 on success or the negative errno from close().
+ */
+static inline int
+nm_close_with_error(int fd)
+{
+    int r;
+
+    r = close(fd);
+
+    if (r != 0) {
+        int errsv = errno;
+
+        nm_assert(r == -1);
+
+        /* EBADF indicates a bug.
+         *
+         * - if fd is non-negative, this means the tracking of the descriptor
+         *   got messed up. That's very bad, somebody closed a wrong FD or we
+         *   might do so. On a multi threaded application, messing up the tracking
+         *   of the file descriptor means we race against closing an unrelated FD.
+         * - if fd is negative, it may not be a bug but intentional. However, our callers
+         *   are not supposed to call close() on a negative FD either. Assert
+         *   against that too. */
+        nm_assert(errsv != EBADF);
+
+        if (errsv == EINTR) {
+            /* There isn't really much we can do about EINTR. On Linux, always this means
+             * the FD was closed. On some POSIX systems that may be different and retry
+             * would be appropriate.
+             *
+             * Whether there was any IO error is unknown. Assume not and signal success. */
+            return 0;
+        }
+
+        return -errsv;
+    }
+
+    return 0;
+}
+
+/**
+ * nm_close:
+ *
+ * Wrapper around nm_close_with_error(), which ignores any error and preserves the
+ * caller's errno.
+ *
+ * We usually don't care about errors from close, so this is usually preferable over
+ * nm_close_with_error(). Never use close() directly.
+ *
+ * Everything from nm_close_with_error() applies.
+ */
+static inline void
+nm_close(int fd)
+{
+    NM_AUTO_PROTECT_ERRNO(errsv);
+
+    nm_close_with_error(fd);
+}
+
+static inline bool
+nm_clear_fd(int *p_fd)
+{
+    int fd;
+
+    if (!p_fd || (fd = *p_fd) < 0)
+        return false;
+
+    *p_fd = -1;
+    nm_close(fd);
+    return true;
+}
+
 static inline void
 _nm_auto_close(int *pfd)
 {
-    if (*pfd >= 0) {
-        NM_AUTO_PROTECT_ERRNO(errsv);
-        (void) nm_close(*pfd);
-    }
+    if (*pfd >= 0)
+        nm_close(*pfd);
 }
 #define nm_auto_close nm_auto(_nm_auto_close)
 
@@ -1298,13 +1450,10 @@ nm_utils_addr_family_other(int addr_family)
 static inline size_t
 nm_utils_addr_family_to_size(int addr_family)
 {
-    switch (addr_family) {
-    case NM_AF_INET:
-        return NM_AF_INET_SIZE;
-    case NM_AF_INET6:
+    if (!NM_IS_IPv4(addr_family))
         return NM_AF_INET6_SIZE;
-    }
-    return nm_assert_unreachable_val(0);
+    else
+        return NM_AF_INET_SIZE;
 }
 
 static inline size_t
@@ -1341,4 +1490,33 @@ nm_utils_addr_family_from_size(size_t len)
     return NM_AF_UNSPEC;
 }
 
+#define _NM_PTR_IS_ALIGNED_(uniq, type, ptr)                                            \
+    ({                                                                                  \
+        const void *const NM_UNIQ_T(_ptr, uniq) = (ptr);                                \
+                                                                                        \
+        /* NULL is accepted too. */                                                     \
+                                                                                        \
+        (!NM_UNIQ_T(_ptr, uniq)                                                         \
+         || ((((uintptr_t) (void *) NM_UNIQ_T(_ptr, uniq)) % _nm_alignof(type)) == 0)); \
+    })
+
+#define _NM_PTR_IS_ALIGNED(type, ptr) _NM_PTR_IS_ALIGNED_(NM_UNIQ, type, (ptr))
+
+/* We build with "-Wcast-align=strict", which can warn about alignment problems
+ * with casting. In some cases, we know that the pointer has the suitable
+ * alignment and the cast is in fact correct. The way to disable the warning
+ * would be to cast ((Type *) ((void *) (ptr))).
+ *
+ * This macro does essentially that, but it also does an nm_assert() that the
+ * alignment of the pointer is suitable to cast to (Type *). */
+#define _NM_CAST_ALIGN(uniq, Type, ptr)                             \
+    ({                                                              \
+        const void *const NM_UNIQ_T(_ptr, uniq) = (ptr);            \
+                                                                    \
+        nm_assert(_NM_PTR_IS_ALIGNED(Type, NM_UNIQ_T(_ptr, uniq))); \
+                                                                    \
+        ((Type *) NM_UNIQ_T(_ptr, uniq));                           \
+    })
+#define NM_CAST_ALIGN(Type, ptr) _NM_CAST_ALIGN(NM_UNIQ, Type, ptr)
+
 #endif /* __NM_STD_AUX_H__ */