From 165a87d424a16875beacf119d82af82de5c9cf24 Mon Sep 17 00:00:00 2001 From: Matt Borland Date: Tue, 14 Jul 2026 09:59:53 -0400 Subject: [PATCH 1/2] Make builtin constant p macros variadic --- .../safe_numbers/detail/compile_assert.hpp | 85 +++++++++---------- .../detail/signed_integer_basis.hpp | 2 +- .../detail/unsigned_integer_basis.hpp | 4 +- 3 files changed, 43 insertions(+), 48 deletions(-) diff --git a/include/boost/safe_numbers/detail/compile_assert.hpp b/include/boost/safe_numbers/detail/compile_assert.hpp index ac05ac5..b45895f 100644 --- a/include/boost/safe_numbers/detail/compile_assert.hpp +++ b/include/boost/safe_numbers/detail/compile_assert.hpp @@ -88,47 +88,52 @@ BOOST_SAFE_NUMBERS_CA_ASSERT_IMPL(expression, message, BOOST_SAFE_NUMBERS_CA_CAT(_compile_assert_fail_, __COUNTER__)) -#define BOOST_SAFE_NUMBERS_CA_CONST_P_IMPL(expression, message, fn) \ - do { \ - if(__builtin_constant_p(expression)) { \ - if (!(expression)) { \ - void fn() __attribute__ ((error(message))); \ - fn(); \ - } \ - } \ - } while (0) -#define BOOST_SAFE_NUMBERS_COMPILE_ASSERT_CONST_P(expression, message) \ - BOOST_SAFE_NUMBERS_CA_CONST_P_IMPL(expression, message, BOOST_SAFE_NUMBERS_CA_CAT(_compile_assert_fail_, __COUNTER__)) - -// GCC before 13 does not fold __builtin_constant_p when it is applied to a value derived -// from an overflow builtin, or even to a comparison in a deep constexpr call chain, so the -// guard above is missed and the assertion never fires. Guarding instead on the constness of -// the raw operands (plain integers, which fold on every supported GCC) makes the check fire -// on GCC 11 and 12 as well; the condition is still evaluated by dead-code removal exactly as -// before. CONST_P1 guards one operand, CONST_P2 guards two. -#define BOOST_SAFE_NUMBERS_CA_CONST_P1_IMPL(a, condition, message, fn) \ - do { \ - if (__builtin_constant_p(a)) { \ - if (!(condition)) { \ - void fn() __attribute__ ((error(message))); \ - fn(); \ - } \ - } \ - } while (0) -#define BOOST_SAFE_NUMBERS_COMPILE_ASSERT_CONST_P1(a, condition, message) \ - BOOST_SAFE_NUMBERS_CA_CONST_P1_IMPL(a, condition, message, BOOST_SAFE_NUMBERS_CA_CAT(_compile_assert_fail_, __COUNTER__)) - -#define BOOST_SAFE_NUMBERS_CA_CONST_P2_IMPL(a, b, condition, message, fn) \ +// CONST_P turns a provably-constant precondition violation into a build error. The optional +// trailing arguments name the operands that must be compile-time constants for the check to +// fire; with none given, the condition itself is the guarded operand: +// +// CONST_P(condition, message) guard on the condition +// CONST_P(condition, message, a) guard on operand a +// CONST_P(condition, message, a, b, ...) guard on every listed operand (ANDed) +// +// GCC before 13 does not fold __builtin_constant_p when it is applied to a value derived from +// an overflow builtin, or even to a comparison in a deep constexpr call chain, so a guard on +// the condition is missed and the assertion never fires. Naming the raw operands (plain +// integers, which fold on every supported GCC) makes the check fire on GCC 11 and 12 as well; +// the condition is still evaluated by dead-code removal exactly as before. + +// __builtin_constant_p of every listed operand, ANDed together. The levels chain through +// distinct names so each re-expands cleanly; up to eight operands are supported. +#define BOOST_SAFE_NUMBERS_CA_G1(a, ...) __builtin_constant_p(a) __VA_OPT__(&& BOOST_SAFE_NUMBERS_CA_G2(__VA_ARGS__)) +#define BOOST_SAFE_NUMBERS_CA_G2(a, ...) __builtin_constant_p(a) __VA_OPT__(&& BOOST_SAFE_NUMBERS_CA_G3(__VA_ARGS__)) +#define BOOST_SAFE_NUMBERS_CA_G3(a, ...) __builtin_constant_p(a) __VA_OPT__(&& BOOST_SAFE_NUMBERS_CA_G4(__VA_ARGS__)) +#define BOOST_SAFE_NUMBERS_CA_G4(a, ...) __builtin_constant_p(a) __VA_OPT__(&& BOOST_SAFE_NUMBERS_CA_G5(__VA_ARGS__)) +#define BOOST_SAFE_NUMBERS_CA_G5(a, ...) __builtin_constant_p(a) __VA_OPT__(&& BOOST_SAFE_NUMBERS_CA_G6(__VA_ARGS__)) +#define BOOST_SAFE_NUMBERS_CA_G6(a, ...) __builtin_constant_p(a) __VA_OPT__(&& BOOST_SAFE_NUMBERS_CA_G7(__VA_ARGS__)) +#define BOOST_SAFE_NUMBERS_CA_G7(a, ...) __builtin_constant_p(a) __VA_OPT__(&& BOOST_SAFE_NUMBERS_CA_G8(__VA_ARGS__)) +#define BOOST_SAFE_NUMBERS_CA_G8(a) __builtin_constant_p(a) +#define BOOST_SAFE_NUMBERS_CA_GUARD(...) BOOST_SAFE_NUMBERS_CA_G1(__VA_ARGS__) + +// Pick the guard expression: the condition when no operands were given, else the ANDed operands. +#define BOOST_SAFE_NUMBERS_CA_CONST_P_GUARD_(condition, ...) __builtin_constant_p(condition) +#define BOOST_SAFE_NUMBERS_CA_CONST_P_GUARD_1(condition, ...) BOOST_SAFE_NUMBERS_CA_GUARD(__VA_ARGS__) +#define BOOST_SAFE_NUMBERS_CA_CONST_P_GUARD(condition, ...) \ + BOOST_SAFE_NUMBERS_CA_CAT(BOOST_SAFE_NUMBERS_CA_CONST_P_GUARD_, __VA_OPT__(1))(condition, __VA_ARGS__) + +#define BOOST_SAFE_NUMBERS_CA_CONST_P_IMPL(condition, message, guard, fn) \ do { \ - if (__builtin_constant_p(a) && __builtin_constant_p(b)) { \ + if (guard) { \ if (!(condition)) { \ void fn() __attribute__ ((error(message))); \ fn(); \ } \ } \ } while (0) -#define BOOST_SAFE_NUMBERS_COMPILE_ASSERT_CONST_P2(a, b, condition, message) \ - BOOST_SAFE_NUMBERS_CA_CONST_P2_IMPL(a, b, condition, message, BOOST_SAFE_NUMBERS_CA_CAT(_compile_assert_fail_, __COUNTER__)) +#define BOOST_SAFE_NUMBERS_COMPILE_ASSERT_CONST_P(condition, message, ...) \ + BOOST_SAFE_NUMBERS_CA_CONST_P_IMPL( \ + condition, message, \ + BOOST_SAFE_NUMBERS_CA_CONST_P_GUARD(condition, __VA_ARGS__), \ + BOOST_SAFE_NUMBERS_CA_CAT(_compile_assert_fail_, __COUNTER__)) #define BOOST_SAFE_NUMBERS_COMPILE_ASSERT0(expression) BOOST_SAFE_NUMBERS_COMPILE_ASSERT(expression, NULL) @@ -136,9 +141,7 @@ #else #define BOOST_SAFE_NUMBERS_COMPILE_ASSERT(condition, description) #define BOOST_SAFE_NUMBERS_COMPILE_ASSERT0(expression) -#define BOOST_SAFE_NUMBERS_COMPILE_ASSERT_CONST_P(expression, message) -#define BOOST_SAFE_NUMBERS_COMPILE_ASSERT_CONST_P1(a, condition, message) -#define BOOST_SAFE_NUMBERS_COMPILE_ASSERT_CONST_P2(a, b, condition, message) +#define BOOST_SAFE_NUMBERS_COMPILE_ASSERT_CONST_P(condition, message, ...) #endif @@ -274,12 +277,4 @@ do { \ #error "BOOST_SAFE_NUMBERS_COMPILE_ASSERT_CONST_P not defined" #endif -#ifndef BOOST_SAFE_NUMBERS_COMPILE_ASSERT_CONST_P1 -#error "BOOST_SAFE_NUMBERS_COMPILE_ASSERT_CONST_P1 not defined" -#endif - -#ifndef BOOST_SAFE_NUMBERS_COMPILE_ASSERT_CONST_P2 -#error "BOOST_SAFE_NUMBERS_COMPILE_ASSERT_CONST_P2 not defined" -#endif - #endif // BOOST_SAFE_NUMBERS_DETAIL_COMPILE_ASSERT_HPP diff --git a/include/boost/safe_numbers/detail/signed_integer_basis.hpp b/include/boost/safe_numbers/detail/signed_integer_basis.hpp index 8d2c81e..3bf4584 100644 --- a/include/boost/safe_numbers/detail/signed_integer_basis.hpp +++ b/include/boost/safe_numbers/detail/signed_integer_basis.hpp @@ -2050,7 +2050,7 @@ struct signed_div_helper #ifdef BOOST_SAFE_NUMBERS_ENABLE_COMPILE_ASSERT // Divide-by-zero throws or exits under every policy that reaches this template // (throw_exception, saturate, strict), so a constant zero divisor is always a bug. - BOOST_SAFE_NUMBERS_COMPILE_ASSERT_CONST_P1(rhs_basis, rhs_basis != BasisType{0}, "signed division by zero"); + BOOST_SAFE_NUMBERS_COMPILE_ASSERT_CONST_P(rhs_basis != BasisType{0}, "signed division by zero", rhs_basis); // min / -1 overflow is an error under throw_exception and strict; saturate returns max. if constexpr (Policy == overflow_policy::throw_exception || Policy == overflow_policy::strict) diff --git a/include/boost/safe_numbers/detail/unsigned_integer_basis.hpp b/include/boost/safe_numbers/detail/unsigned_integer_basis.hpp index 807edff..10fcaf0 100644 --- a/include/boost/safe_numbers/detail/unsigned_integer_basis.hpp +++ b/include/boost/safe_numbers/detail/unsigned_integer_basis.hpp @@ -592,7 +592,7 @@ struct add_helper #ifdef BOOST_SAFE_NUMBERS_ENABLE_COMPILE_ASSERT if constexpr (Policy == overflow_policy::throw_exception || Policy == overflow_policy::strict) { - BOOST_SAFE_NUMBERS_COMPILE_ASSERT_CONST_P2(lhs_basis, rhs_basis, !overflowed, "unsigned addition overflow"); + BOOST_SAFE_NUMBERS_COMPILE_ASSERT_CONST_P(!overflowed, "unsigned addition overflow", lhs_basis, rhs_basis); } #endif @@ -614,7 +614,7 @@ struct add_helper #ifdef BOOST_SAFE_NUMBERS_ENABLE_COMPILE_ASSERT if constexpr (Policy == overflow_policy::throw_exception || Policy == overflow_policy::strict) { - BOOST_SAFE_NUMBERS_COMPILE_ASSERT_CONST_P2(lhs_basis, rhs_basis, !overflowed, "unsigned addition overflow"); + BOOST_SAFE_NUMBERS_COMPILE_ASSERT_CONST_P(!overflowed, "unsigned addition overflow", lhs_basis, rhs_basis); } #endif From ed03e05101073c4ae5a7a1453c0e7cef74c0cd50 Mon Sep 17 00:00:00 2001 From: Matt Borland Date: Tue, 14 Jul 2026 13:41:34 -0400 Subject: [PATCH 2/2] More consolidation --- .../safe_numbers/detail/compile_assert.hpp | 39 +++++++------------ 1 file changed, 15 insertions(+), 24 deletions(-) diff --git a/include/boost/safe_numbers/detail/compile_assert.hpp b/include/boost/safe_numbers/detail/compile_assert.hpp index b45895f..9271e8f 100644 --- a/include/boost/safe_numbers/detail/compile_assert.hpp +++ b/include/boost/safe_numbers/detail/compile_assert.hpp @@ -40,6 +40,11 @@ * output. */ +// BOOST_SAFE_NUMBERS_COMPILE_ASSERT() takes an optional message: (expr) or (expr, "why"). +#define BOOST_SAFE_NUMBERS_CA_FIRST(first, ...) first +#define BOOST_SAFE_NUMBERS_CA_MSG(...) BOOST_SAFE_NUMBERS_CA_MSG_(__VA_ARGS__, "") +#define BOOST_SAFE_NUMBERS_CA_MSG_(expression, message, ...) message + #ifdef __GNUC__ // The compile-time-error mechanism relies on the GCC/Clang function 'error' attribute. @@ -75,7 +80,7 @@ * @def compile_assert * @brief Macro for compile-time assertions. * @param expression The compile-time condition to be checked. - * @param message A description of the assertion. + * @param message Optional description of the assertion; defaults to "" when omitted. */ #define BOOST_SAFE_NUMBERS_CA_ASSERT_IMPL(expression, message, fn) \ do { \ @@ -84,8 +89,8 @@ fn(); \ } \ } while (0) -#define BOOST_SAFE_NUMBERS_COMPILE_ASSERT(expression, message) \ - BOOST_SAFE_NUMBERS_CA_ASSERT_IMPL(expression, message, BOOST_SAFE_NUMBERS_CA_CAT(_compile_assert_fail_, __COUNTER__)) +#define BOOST_SAFE_NUMBERS_COMPILE_ASSERT(...) \ + BOOST_SAFE_NUMBERS_CA_ASSERT_IMPL(BOOST_SAFE_NUMBERS_CA_FIRST(__VA_ARGS__), BOOST_SAFE_NUMBERS_CA_MSG(__VA_ARGS__), BOOST_SAFE_NUMBERS_CA_CAT(_compile_assert_fail_, __COUNTER__)) // CONST_P turns a provably-constant precondition violation into a build error. The optional @@ -135,12 +140,8 @@ BOOST_SAFE_NUMBERS_CA_CONST_P_GUARD(condition, __VA_ARGS__), \ BOOST_SAFE_NUMBERS_CA_CAT(_compile_assert_fail_, __COUNTER__)) - -#define BOOST_SAFE_NUMBERS_COMPILE_ASSERT0(expression) BOOST_SAFE_NUMBERS_COMPILE_ASSERT(expression, NULL) - #else -#define BOOST_SAFE_NUMBERS_COMPILE_ASSERT(condition, description) -#define BOOST_SAFE_NUMBERS_COMPILE_ASSERT0(expression) +#define BOOST_SAFE_NUMBERS_COMPILE_ASSERT(...) #define BOOST_SAFE_NUMBERS_COMPILE_ASSERT_CONST_P(condition, message, ...) #endif @@ -222,22 +223,18 @@ int stop_compile3() __attribute__ ((error("'compile_assert_scalar error detected #define BOOST_SAFE_NUMBERS_MERGE1(a,b) BOOST_SAFE_NUMBERS_MERGE2(a,b) #define BOOST_SAFE_NUMBERS_MERGE3(a,b,c) BOOST_SAFE_NUMBERS_MERGE1(a, BOOST_SAFE_NUMBERS_MERGE1(b,c)) -// The non-active fallback above already defined these as empty; replace them here. +// The non-active fallback above already defined this as empty #undef BOOST_SAFE_NUMBERS_COMPILE_ASSERT -#undef BOOST_SAFE_NUMBERS_COMPILE_ASSERT0 -#define BOOST_SAFE_NUMBERS_COMPILE_ASSERT(expr, message) \ +#define BOOST_SAFE_NUMBERS_COMPILE_ASSERT(...) \ do { \ - if (!(expr)) { \ + if (!(BOOST_SAFE_NUMBERS_CA_FIRST(__VA_ARGS__))) { \ extern void BOOST_SAFE_NUMBERS_MERGE3(_compile_assert, BOOST_SAFE_NUMBERS_COMPILE_FILE, __LINE__)(); \ BOOST_SAFE_NUMBERS_MERGE3(_compile_assert, BOOST_SAFE_NUMBERS_COMPILE_FILE, __LINE__)(); \ } \ } while (0) - -#define BOOST_SAFE_NUMBERS_COMPILE_ASSERT0(expression) BOOST_SAFE_NUMBERS_COMPILE_ASSERT(expression, NULL) #else -#define BOOST_SAFE_NUMBERS_COMPILE_ASSERT(condition, description) -#define BOOST_SAFE_NUMBERS_COMPILE_ASSERT0(expression) +#define BOOST_SAFE_NUMBERS_COMPILE_ASSERT(...) #define BOOST_SAFE_NUMBERS_COMPILE_ASSERT_ACTIVE #endif // defined(BOOST_SAFE_NUMBERS_ENABLE_COMPILE_ASSERT) @@ -252,15 +249,13 @@ do { \ #if !defined(BOOST_SAFE_NUMBERS_COMPILE_ASSERT) #define BOOST_SAFE_NUMBERS_COMPILE_ASSERT_ACTIVE -#define BOOST_SAFE_NUMBERS_COMPILE_ASSERT(expression, message) \ +#define BOOST_SAFE_NUMBERS_COMPILE_ASSERT(...) \ do { \ void _compile_assert_fail(); \ - if (!(expression)) { \ + if (!(BOOST_SAFE_NUMBERS_CA_FIRST(__VA_ARGS__))) { \ _compile_assert_fail(); \ } \ } while (0) - -#define BOOST_SAFE_NUMBERS_COMPILE_ASSERT0(expression) BOOST_SAFE_NUMBERS_COMPILE_ASSERT(expression, NULL) #endif // !defined(compile_assert) #endif // defined(BOOST_SAFE_NUMBERS_ENABLE_COMPILE_ASSERT) @@ -269,10 +264,6 @@ do { \ #error "BOOST_SAFE_NUMBERS_COMPILE_ASSERT not defined" #endif -#ifndef BOOST_SAFE_NUMBERS_COMPILE_ASSERT0 -#error "BOOST_SAFE_NUMBERS_COMPILE_ASSERT0 not defined" -#endif - #ifndef BOOST_SAFE_NUMBERS_COMPILE_ASSERT_CONST_P #error "BOOST_SAFE_NUMBERS_COMPILE_ASSERT_CONST_P not defined" #endif