libc: Fix assert() sanitiser for C++ contextual bool conversion

Replace the `(bool(*)(bool))` probe in `__assert_sanitize()` with an unevaluated
conditional expression, so types with `explicit operator bool()` that require a
contextually converted constant expression of type `bool` are handled correctly.

Ergo, arity check is now performed separately via `__assert_sanitize_arity()`, a
unary template whose parameter pack must bind to exactly on argument after
`__VA_ARGS__` is substituted into the call.

Also align NDEBUG with C23 requirements.

Reported by:	dim, aokblast
Signed-off-by:	Faraz Vahedi <kfv@kfv.io>
Reviewed by:	aokblast, fuz
MFC after:	1 week
Fixes:		867b51452e
Pull Request:	https://github.com/freebsd/freebsd-src/pull/2265
This commit is contained in:
Faraz Vahedi
2026-06-06 15:08:47 +03:30
committed by Robert Clausecker
parent 6365c45d95
commit 48d20fd1cf
+9 -29
View File
@@ -46,42 +46,22 @@
#undef __assert_unreachable #undef __assert_unreachable
#ifdef NDEBUG #ifdef NDEBUG
#define assert(e) ((void)0) #define assert(...) ((void)0)
#define _assert(e) ((void)0) #define _assert(...) ((void)0)
#if __BSD_VISIBLE #if __BSD_VISIBLE
#define __assert_unreachable() __unreachable() #define __assert_unreachable() __unreachable()
#endif /* __BSD_VISIBLE */ #endif /* __BSD_VISIBLE */
#else #else
#ifdef __cplusplus #ifdef __cplusplus
#if __cplusplus < 202002L #define assert(...) ((void)(bool(__VA_ARGS__) ? ((void)0) : \
/* __assert(__func__, __FILE__, __LINE__, \
* C++ modes prior to C++20 cannot simultaneously satisfy all three #__VA_ARGS__)))
* desirable properties of the sanitiser:
*
* Approach No double-eval Lambda support Arity check
* ----------------------------- -------------- -------------- -----------
* sizeof(cast(expression)) yes no yes
* static_cast<bool>(expression) no yes no
* (void)bool(expression) no yes no
*
* NOTE: C++20 introduced lambdas in unevaluated contexts; see P0315R4.
*
* Since no approach satisfies all three below C++20, the least harmful
* choice is to forgo the check entirely rather than silently break one
* of the remaining guarantees.
*
*/
#define __assert_sanitize(...) ((void)0)
#else #else
#define __assert_sanitize(...) (void)sizeof(((bool(*)(bool))0)(__VA_ARGS__)) #define assert(...) ((void)sizeof(((_Bool(*)(_Bool))0)(__VA_ARGS__)), \
#endif /* __cplusplus < 202002L */ (__VA_ARGS__) ? (void)0 : \
#else __assert(__func__, __FILE__, \
#define __assert_sanitize(...) (void)sizeof(((_Bool(*)(_Bool))0)(__VA_ARGS__))
#endif /* __cplusplus */
#define assert(...) (__assert_sanitize(__VA_ARGS__), \
(__VA_ARGS__) ? (void)0 : \
__assert(__func__, __FILE__, \
__LINE__, #__VA_ARGS__)) __LINE__, #__VA_ARGS__))
#endif /* __cplusplus */
#define _assert(...) assert(__VA_ARGS__) #define _assert(...) assert(__VA_ARGS__)
#if __BSD_VISIBLE #if __BSD_VISIBLE
#define __assert_unreachable() assert(0 && "unreachable segment reached") #define __assert_unreachable() assert(0 && "unreachable segment reached")