1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157
|
/** @file
*
* Wireshark - Network traffic analyzer
* By Gerald Combs <gerald@wireshark.org>
* Copyright 1998 Gerald Combs
*
* SPDX-License-Identifier: GPL-2.0-or-later
*/
#ifndef __WS_ASSERT_H__
#define __WS_ASSERT_H__
#include <ws_symbol_export.h>
#include <ws_attributes.h>
#include <stdbool.h>
#include <string.h>
#include <wsutil/wslog.h>
#include <wsutil/wmem/wmem.h>
/*
* XXX - WS_ASSERT_ENABLED is tested in various if statements
* below, so that we don't test various assertions unless
* assertions are enabled. Compilers will often partially
* evaluate (CONSTANT && (expression)) at compile time, so
* that if CONSTANT is 0 the rest of the test isn't evaluated
* and assumed to result in a false result, with the code in
* the if branch being removed, and if CONSTANT is 1, the
* code is treated as an if that tests the expression.
*
* This could mean that, if "defined but not used" tests are
* being done, any variable tested in the expression may be warked
* as "defined but not used" if WS_ASSERT_ENABLED is 0, causing
* a pile of warnings if the variable isn't marked as unused
* (especially true of parametre variables).
*
* However, some compilers - Clang, in my tests, and probably GCC,
* due to tests in builds not failing - treate "if (0 && (expression))"
* specially, pretending hat all variables in the expression are used,
* even if they aren't used in the generated code. (At least in
* Apple clang version 15.0.0 (clang-1500.1.0.2.5), it must be
* exactly 0 - (0) doesn't have the same effect.)
*
* That's all very well, but, unfortunately Microsoft Visual Studio's
* C compiler doesn't do that, so the variables have to be marked as
* unused, which may cause warnings "used, but marked as unused"
* warnings if the code is compiled with assertions enabled.
*/
#if defined(ENABLE_ASSERT)
#define WS_ASSERT_ENABLED 1
#elif defined(NDEBUG)
#define WS_ASSERT_ENABLED 0
#else
#define WS_ASSERT_ENABLED 1
#endif
#ifdef __cplusplus
extern "C" {
#endif /* __cplusplus */
/*
* We don't want to execute the expression without assertions because
* it might be time and space costly and the goal here is to optimize for
* that case. However removing it completely is not good enough
* because it might generate many unused variable warnings. So we use
* if (false) and let the compiler optimize away the dead execution branch.
*/
#define ws_assert_if_active(active, expr) \
do { \
if ((active) && !(expr)) \
ws_error("assertion failed: %s", #expr); \
} while (0)
/*
* ws_abort_if_fail() is not conditional on having assertions enabled.
* Usually used to appease a static analyzer.
*/
#define ws_abort_if_fail(expr) \
ws_assert_if_active(true, expr)
/*
* ws_assert() cannot produce side effects, otherwise code will
* behave differently because of having assertions enabled/disabled, and
* probably introduce some difficult to track bugs.
*/
#define ws_assert(expr) \
ws_assert_if_active(WS_ASSERT_ENABLED, expr)
#define ws_assert_streq(s1, s2) \
ws_assert((s1) && (s2) && strcmp((s1), (s2)) == 0)
#define ws_assert_utf8(str, len) \
do { \
const char *__assert_endptr; \
if (WS_ASSERT_ENABLED && \
!g_utf8_validate(str, len, &__assert_endptr)) { \
ws_log_utf8_full(LOG_DOMAIN_UTF_8, LOG_LEVEL_ERROR, \
__FILE__, __LINE__, __func__, \
str, len, __assert_endptr); \
} \
} while (0)
/*
* We don't want to disable ws_assert_not_reached() with (optional) assertions
* disabled.
* That would blast compiler warnings everywhere for no benefit, not
* even a miniscule performance gain. Reaching this function is always
* a programming error and will unconditionally abort execution.
*
* Note: With g_assert_not_reached() if the compiler supports unreachable
* built-ins (which recent versions of GCC and MSVC do) there is no warning
* blast with g_assert_not_reached() and G_DISABLE_ASSERT. However if that
* is not the case then g_assert_not_reached() is simply (void)0 and that
* causes the spurious warnings, because the compiler can't tell anymore
* that a certain code path is not used. We avoid that with
* ws_assert_not_reached(). There is no reason to ever use a no-op here.
*/
#define ws_assert_not_reached() \
ws_error("assertion \"not reached\" failed")
/*
* These macros can be used as an alternative to ws_assert() to
* assert some condition on function arguments. This must only be used
* to catch programming errors, in situations where an assertion is
* appropriate. And it should only be used if failing the condition
* doesn't necessarily lead to an inconsistent state for the program.
*
* It is possible to set the fatal log domain to "InvalidArg" to abort
* execution for debugging purposes, if one of these checks fail.
*/
#define ws_warn_badarg(str) \
ws_log_full(LOG_DOMAIN_EINVAL, LOG_LEVEL_WARNING, \
__FILE__, __LINE__, __func__, \
"invalid argument: %s", str)
#define ws_return_str_if(expr, scope) \
do { \
if (WS_ASSERT_ENABLED && (expr)) { \
ws_warn_badarg(#expr); \
return wmem_strdup_printf(scope, "(invalid argument: %s)", #expr); \
} \
} while (0)
#define ws_return_val_if(expr, val) \
do { \
if (WS_ASSERT_ENABLED && (expr)) { \
ws_warn_badarg(#expr); \
return (val); \
} \
} while (0)
#ifdef __cplusplus
}
#endif /* __cplusplus */
#endif /* __WS_ASSERT_H__ */
|