From: Daan De Meyer Date: Fri, 15 May 2026 12:16:01 +0000 (+0000) Subject: tree-wide: Replace exp10() with our own impl X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=d52c8f2068b57e191bafd01d9d60bddbf4a20419;p=thirdparty%2Fsystemd.git tree-wide: Replace exp10() with our own impl exp10() has a symbol version > 2.34 on latest glibc. To allow dropping our baseline required glibc runtime version to <= 2.34, let's add our own version to prevent pulling in the newer symbol from glibc. --- diff --git a/src/basic/math-util.c b/src/basic/math-util.c new file mode 100644 index 00000000000..3d95e40016c --- /dev/null +++ b/src/basic/math-util.c @@ -0,0 +1,24 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "math-util.h" + +double xexp10i(int n) { + /* Powers of 10 up to 10^22 are exact in IEEE-754 binary64. */ + static const double table[] = { + 1e0, 1e1, 1e2, 1e3, 1e4, 1e5, 1e6, 1e7, 1e8, 1e9, 1e10, 1e11, + 1e12, 1e13, 1e14, 1e15, 1e16, 1e17, 1e18, 1e19, 1e20, 1e21, 1e22, + }; + bool negative = n < 0; + + /* Cast before negation so n == INT_MIN doesn't invoke signed-overflow UB. Unsigned negation + * wraps to the magnitude we want. */ + unsigned k = negative ? -(unsigned) n : (unsigned) n; + + /* 10^309 already overflows binary64 to +Inf; anything beyond just stays there. */ + k = MIN(k, 309u); + double r = k < ELEMENTSOF(table) ? table[k] : table[ELEMENTSOF(table) - 1]; + for (unsigned i = ELEMENTSOF(table) - 1; i < k; i++) + r *= 10.0; + + return negative ? 1.0 / r : r; +} diff --git a/src/basic/math-util.h b/src/basic/math-util.h index f3c471b1606..cac5fc31311 100644 --- a/src/basic/math-util.h +++ b/src/basic/math-util.h @@ -12,3 +12,8 @@ /* To avoid x == y and triggering compile warning -Wfloat-equal. This returns false if one of the argument is * NaN or infinity. One of the argument must be a floating point. */ #define fp_equal(x, y) iszero_safe((x) - (y)) + +/* 10^n. Exact for |n| ≤ 22; otherwise multiplies and may accumulate rounding error. Saturates to + * 0.0 or +Inf outside binary64's exponent range; large |n| is capped internally so untrusted + * inputs can't cause unbounded work. */ +double xexp10i(int n); diff --git a/src/basic/meson.build b/src/basic/meson.build index 9007cff1781..20c424b45bc 100644 --- a/src/basic/meson.build +++ b/src/basic/meson.build @@ -64,6 +64,7 @@ basic_sources = files( 'log.c', 'log-context.c', 'login-util.c', + 'math-util.c', 'memfd-util.c', 'memory-util.c', 'mempool.c', diff --git a/src/libsystemd/sd-json/sd-json.c b/src/libsystemd/sd-json/sd-json.c index 1ba07da6f9b..dad7a3174d9 100644 --- a/src/libsystemd/sd-json/sd-json.c +++ b/src/libsystemd/sd-json/sd-json.c @@ -2801,7 +2801,11 @@ static int json_parse_number(const char **p, JsonValue *ret) { *p = c; if (is_real) { - ret->real = ((negative ? -1.0 : 1.0) * (x + (y / shift))) * exp10((exponent_negative ? -1.0 : 1.0) * exponent); + /* Clamp before casting to int — a JSON input with an absurdly large exponent could + * otherwise trigger undefined behaviour in the double→int conversion. xexp10i() + * itself saturates anything beyond ~10^308, so clamping at INT_MAX is harmless. */ + int e = exponent > (double) INT_MAX ? INT_MAX : (int) exponent; + ret->real = ((negative ? -1.0 : 1.0) * (x + (y / shift))) * xexp10i(exponent_negative ? -e : e); return JSON_TOKEN_REAL; } else if (negative) { ret->integer = i; diff --git a/src/shared/color-util.c b/src/shared/color-util.c index f2add33b1d8..1d30732147f 100644 --- a/src/shared/color-util.c +++ b/src/shared/color-util.c @@ -1,8 +1,7 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ -#include - #include "color-util.h" +#include "math-util.h" void rgb_to_hsv(double r, double g, double b, double *ret_h, double *ret_s, double *ret_v) { diff --git a/src/shared/dns-rr.c b/src/shared/dns-rr.c index 0ad01de88a6..eb91910e835 100644 --- a/src/shared/dns-rr.c +++ b/src/shared/dns-rr.c @@ -1,7 +1,5 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ -#include - #include "alloc-util.h" #include "bitmap.h" #include "dns-answer.h" /* IWYU pragma: keep */ @@ -13,6 +11,7 @@ #include "hash-funcs.h" #include "hexdecoct.h" #include "json-util.h" +#include "math-util.h" #include "memory-util.h" #include "siphash24.h" #include "string-table.h" @@ -773,9 +772,9 @@ static char* format_location(uint32_t latitude, uint32_t longitude, uint32_t alt int lat = latitude >= 1U<<31 ? (int) (latitude - (1U<<31)) : (int) ((1U<<31) - latitude); int lon = longitude >= 1U<<31 ? (int) (longitude - (1U<<31)) : (int) ((1U<<31) - longitude); double alt = altitude >= 10000000u ? altitude - 10000000u : -(double)(10000000u - altitude); - double siz = (size >> 4) * exp10((double) (size & 0xF)); - double hor = (horiz_pre >> 4) * exp10((double) (horiz_pre & 0xF)); - double ver = (vert_pre >> 4) * exp10((double) (vert_pre & 0xF)); + double siz = (size >> 4) * xexp10i(size & 0xF); + double hor = (horiz_pre >> 4) * xexp10i(horiz_pre & 0xF); + double ver = (vert_pre >> 4) * xexp10i(vert_pre & 0xF); if (asprintf(&s, "%d %d %.3f %c %d %d %.3f %c %.2fm %.2fm %.2fm %.2fm", (lat / 60000 / 60), diff --git a/src/test/test-math-util.c b/src/test/test-math-util.c index 9771576fcbb..3e8a9d5ba32 100644 --- a/src/test/test-math-util.c +++ b/src/test/test-math-util.c @@ -107,4 +107,39 @@ TEST(fp_equal) { assert_se(!fp_equal(42 / INFINITY, INFINITY / INFINITY)); } +TEST(xexp10i) { + /* Table-lookup range: every value is exact in binary64 */ + ASSERT_TRUE(fp_equal(xexp10i(0), 1.0)); + ASSERT_TRUE(fp_equal(xexp10i(1), 10.0)); + ASSERT_TRUE(fp_equal(xexp10i(2), 100.0)); + ASSERT_TRUE(fp_equal(xexp10i(22), 1e22)); + + /* Negative exponents */ + ASSERT_TRUE(fp_equal(xexp10i(-1), 0.1)); + ASSERT_TRUE(fp_equal(xexp10i(-3), 0.001)); + + /* Beyond the table: result still matches a plain 10.0 multiplication chain (no precision + * claim beyond binary64) */ + ASSERT_TRUE(xexp10i(23) > 0.9e23 && xexp10i(23) < 1.1e23); + ASSERT_TRUE(xexp10i(100) > 0.9e100 && xexp10i(100) < 1.1e100); + + /* Overflow saturates to +Inf, underflow to 0 — matching glibc exp10() */ + ASSERT_TRUE(isinf(xexp10i(400))); + ASSERT_TRUE(fp_equal(xexp10i(-400), 0.0)); + + /* Pathological inputs must still terminate quickly thanks to the internal cap */ + ASSERT_TRUE(isinf(xexp10i(INT_MAX))); + ASSERT_TRUE(fp_equal(xexp10i(INT_MIN), 0.0)); + + /* Regression guard for the DBL_MAX round-trip described in math-util.c: 10^308 must be small + * enough that DBL_MAX's mantissa (≈ 1.7976931348623157) multiplied by it does not overflow + * to +Inf. Delegating to __builtin_powi(10.0, n) here breaks this — libgcc's __powidf2 + * accumulates a few ULPs of error through repeated squaring, and the product spills over. + * Matching test-json's delta of 0.0001, the reconstructed value must land within 0.01% of + * DBL_MAX. */ + double dbl_max_reconstructed = 1.7976931348623157 * xexp10i(308); + ASSERT_FALSE(isinf(dbl_max_reconstructed)); + ASSERT_TRUE(ABS(1.0 - DBL_MAX / dbl_max_reconstructed) < 0.0001); +} + DEFINE_TEST_MAIN(LOG_DEBUG);