https://gcc.gnu.org/g:f139530a10bbb3c422ec30e1603899428ca5183c
commit r17-2410-gf139530a10bbb3c422ec30e1603899428ca5183c Author: Kito Cheng <[email protected]> Date: Thu Jul 9 22:16:55 2026 +0800 libstdc++: fix UB when ellint phi argument is infinity When phi is +/-infinity, the periodicity-reduction step computes std::floor(infinity) which returns infinity, then casts it to int. That cast is undefined behavior per [conv.fpint] because infinity is outside the range of int. A conforming compiler is free to exploit this UB; in practice, Clang eliminates the loop-exit check and produces an infinite loop. GCC currently does not exploit this particular UB, but the cast is still undefined and should be fixed. Fix by rejecting non-finite phi before the floor+cast and throwing std::domain_error. NaN phi is already handled by the __isnan check above, so the new guard only needs __builtin_isinf(phi); std::isfinite is unavailable in the C++98 build of this header. Boost guards against this by an overflow check (phi >= max_value<T>()), where -infinity is first negated to +infinity by the sign-handling block[1]. [1] https://www.boost.org/doc/libs/1_85_0/boost/math/special_functions/ellint_1.hpp libstdc++-v3/ChangeLog: * include/tr1/ell_integral.tcc (__ellint_1): Throw domain_error when phi is not finite. (__ellint_2): Likewise. (__ellint_3): Likewise. * testsuite/special_functions/11_ellint_1/check_inf.cc: New test. * testsuite/special_functions/12_ellint_2/check_inf.cc: New test. * testsuite/special_functions/13_ellint_3/check_inf.cc: New test. Diff: --- libstdc++-v3/include/tr1/ell_integral.tcc | 6 +- .../special_functions/11_ellint_1/check_inf.cc | 64 ++++++++++++++++++++++ .../special_functions/12_ellint_2/check_inf.cc | 63 +++++++++++++++++++++ .../special_functions/13_ellint_3/check_inf.cc | 64 ++++++++++++++++++++++ 4 files changed, 194 insertions(+), 3 deletions(-) diff --git a/libstdc++-v3/include/tr1/ell_integral.tcc b/libstdc++-v3/include/tr1/ell_integral.tcc index e78d4e538a6a..8e66857bc7b2 100644 --- a/libstdc++-v3/include/tr1/ell_integral.tcc +++ b/libstdc++-v3/include/tr1/ell_integral.tcc @@ -223,7 +223,7 @@ namespace tr1 if (__isnan(__k) || __isnan(__phi)) return std::numeric_limits<_Tp>::quiet_NaN(); - else if (std::abs(__k) > _Tp(1)) + else if ((std::abs(__k) > _Tp(1)) || __builtin_isinf(__phi)) std::__throw_domain_error(__N("Bad argument in __ellint_1.")); else { @@ -437,7 +437,7 @@ namespace tr1 if (__isnan(__k) || __isnan(__phi)) return std::numeric_limits<_Tp>::quiet_NaN(); - else if (std::abs(__k) > _Tp(1)) + else if ((std::abs(__k) > _Tp(1)) || __builtin_isinf(__phi)) std::__throw_domain_error(__N("Bad argument in __ellint_2.")); else { @@ -705,7 +705,7 @@ namespace tr1 if (__isnan(__k) || __isnan(__nu) || __isnan(__phi)) return std::numeric_limits<_Tp>::quiet_NaN(); - else if (std::abs(__k) > _Tp(1)) + else if ((std::abs(__k) > _Tp(1)) || __builtin_isinf(__phi)) std::__throw_domain_error(__N("Bad argument in __ellint_3.")); else { diff --git a/libstdc++-v3/testsuite/special_functions/11_ellint_1/check_inf.cc b/libstdc++-v3/testsuite/special_functions/11_ellint_1/check_inf.cc new file mode 100644 index 000000000000..1262d9b483ff --- /dev/null +++ b/libstdc++-v3/testsuite/special_functions/11_ellint_1/check_inf.cc @@ -0,0 +1,64 @@ +// { dg-do run { target c++17 } } +// { dg-options "-D__STDCPP_WANT_MATH_SPEC_FUNCS__" } + +// 8.1.11 ellint_1 - non-finite phi must not produce UB + +#include <cmath> +#include <limits> +#include <stdexcept> +#include <testsuite_hooks.h> + +void +test01() +{ + // +infinity phi should throw domain_error, not loop forever. + bool caught = false; + try + { + (void) std::ellint_1f(0.5F, std::numeric_limits<float>::infinity()); + } + catch (const std::domain_error&) + { + caught = true; + } + VERIFY(caught); +} + +void +test02() +{ + bool caught = false; + try + { + (void) std::ellint_1(0.5, std::numeric_limits<double>::infinity()); + } + catch (const std::domain_error&) + { + caught = true; + } + VERIFY(caught); +} + +void +test03() +{ + bool caught = false; + try + { + (void) std::ellint_1l(0.5L, std::numeric_limits<long double>::infinity()); + } + catch (const std::domain_error&) + { + caught = true; + } + VERIFY(caught); +} + +int +main() +{ + test01(); + test02(); + test03(); + return 0; +} diff --git a/libstdc++-v3/testsuite/special_functions/12_ellint_2/check_inf.cc b/libstdc++-v3/testsuite/special_functions/12_ellint_2/check_inf.cc new file mode 100644 index 000000000000..5b765738fd1b --- /dev/null +++ b/libstdc++-v3/testsuite/special_functions/12_ellint_2/check_inf.cc @@ -0,0 +1,63 @@ +// { dg-do run { target c++17 } } +// { dg-options "-D__STDCPP_WANT_MATH_SPEC_FUNCS__" } + +// 8.1.12 ellint_2 - non-finite phi must not produce UB + +#include <cmath> +#include <limits> +#include <stdexcept> +#include <testsuite_hooks.h> + +void +test01() +{ + bool caught = false; + try + { + (void) std::ellint_2f(0.5F, std::numeric_limits<float>::infinity()); + } + catch (const std::domain_error&) + { + caught = true; + } + VERIFY(caught); +} + +void +test02() +{ + bool caught = false; + try + { + (void) std::ellint_2(0.5, std::numeric_limits<double>::infinity()); + } + catch (const std::domain_error&) + { + caught = true; + } + VERIFY(caught); +} + +void +test03() +{ + bool caught = false; + try + { + (void) std::ellint_2l(0.5L, std::numeric_limits<long double>::infinity()); + } + catch (const std::domain_error&) + { + caught = true; + } + VERIFY(caught); +} + +int +main() +{ + test01(); + test02(); + test03(); + return 0; +} diff --git a/libstdc++-v3/testsuite/special_functions/13_ellint_3/check_inf.cc b/libstdc++-v3/testsuite/special_functions/13_ellint_3/check_inf.cc new file mode 100644 index 000000000000..c960a910b5a9 --- /dev/null +++ b/libstdc++-v3/testsuite/special_functions/13_ellint_3/check_inf.cc @@ -0,0 +1,64 @@ +// { dg-do run { target c++17 } } +// { dg-options "-D__STDCPP_WANT_MATH_SPEC_FUNCS__" } + +// 8.1.13 ellint_3 - non-finite phi must not produce UB + +#include <cmath> +#include <limits> +#include <stdexcept> +#include <testsuite_hooks.h> + +void +test01() +{ + bool caught = false; + try + { + (void) std::ellint_3f(0.5F, 0.5F, std::numeric_limits<float>::infinity()); + } + catch (const std::domain_error&) + { + caught = true; + } + VERIFY(caught); +} + +void +test02() +{ + bool caught = false; + try + { + (void) std::ellint_3(0.5, 0.5, std::numeric_limits<double>::infinity()); + } + catch (const std::domain_error&) + { + caught = true; + } + VERIFY(caught); +} + +void +test03() +{ + bool caught = false; + try + { + (void) std::ellint_3l(0.5L, 0.5L, + std::numeric_limits<long double>::infinity()); + } + catch (const std::domain_error&) + { + caught = true; + } + VERIFY(caught); +} + +int +main() +{ + test01(); + test02(); + test03(); + return 0; +}
