// SPDX-License-Identifier: ISC
/*
 * Copyright (c) 2021  David Lamparter, for NetDEF, Inc.
 */

/* WARNING: this file is "special" in that it overrides the system-provided
 * assert.h by being on the include path before it.  That means it should
 * provide the functional equivalent.
 *
 * This is intentional because FRR extends assert() to write to the log and
 * add backtraces.  Overriding the entire file is the simplest and most
 * reliable way to get this to work;  there were problems previously with the
 * system assert.h getting included afterwards and redefining assert() back to
 * the system variant.
 */

#ifndef _FRR_ASSERT_H
#define _FRR_ASSERT_H

#include "xref.h"

#ifdef __cplusplus
extern "C" {
#endif

#ifndef __cplusplus
/* C++ has this built-in, but C provides it in assert.h for >=C11.  Since we
 * replace assert.h entirely, we need to provide it here too.
 */
#define static_assert _Static_assert
#endif

struct xref_assert {
	struct xref xref;

	const char *expr;
	const char *extra, *args;
};

extern void _zlog_assert_failed(const struct xref_assert *xref,
				const char *extra, ...) PRINTFRR(2, 3)
	__attribute__((noreturn));

/* the "do { } while (expr_)" is there to get a warning for assignments inside
 * the assert expression aka "assert(x = 1)".  The (necessary) braces around
 * expr_ in the if () statement would suppress these warnings.  Since
 * _zlog_assert_failed() is noreturn, the while condition will never be
 * checked.
 */
#define assert(expr_)                                                          \
	({                                                                     \
		static const struct xref_assert _xref __attribute__(           \
			(used)) = {                                            \
			.xref = XREF_INIT(XREFT_ASSERT, NULL, __func__),       \
			.expr = #expr_,                                        \
		};                                                             \
		XREF_LINK(_xref.xref);                                         \
		if (__builtin_expect((expr_) ? 0 : 1, 0))                      \
			do {                                                   \
				_zlog_assert_failed(&_xref, NULL);             \
			} while (expr_);                                       \
	})

#define assertf(expr_, extra_, ...)                                            \
	({                                                                     \
		static const struct xref_assert _xref __attribute__(           \
			(used)) = {                                            \
			.xref = XREF_INIT(XREFT_ASSERT, NULL, __func__),       \
			.expr = #expr_,                                        \
			.extra = extra_,                                       \
			.args = #__VA_ARGS__,                                  \
		};                                                             \
		XREF_LINK(_xref.xref);                                         \
		if (__builtin_expect((expr_) ? 0 : 1, 0))                      \
			do {                                                   \
				_zlog_assert_failed(&_xref, extra_,            \
						    ##__VA_ARGS__);            \
			} while (expr_);                                       \
	})

#define zassert assert

#ifdef __cplusplus
}
#endif

#endif /* _FRR_ASSERT_H */
