libstdc++: Make std::lcm and std::gcd detect overflow [PR105844]
authorJonathan Wakely <jwakely@redhat.com>
Fri, 10 Jun 2022 13:39:13 +0000 (14:39 +0100)
committerJonathan Wakely <jwakely@redhat.com>
Fri, 10 Jun 2022 14:24:29 +0000 (15:24 +0100)
commit671970a5621e18e7079b4ca113e56434c858db66
tree825abe925325c2097641498395ed2345a3620479
parent1e65f2ed99024f23c56f7b6a961898bcaa882a92
libstdc++: Make std::lcm and std::gcd detect overflow [PR105844]

When I fixed PR libstdc++/92978 I introduced a regression whereby
std::lcm(INT_MIN, 1) and std::lcm(50000, 49999) would no longer produce
errors during constant evaluation. Those calls are undefined, because
they violate the preconditions that |m| and the result can be
represented in the return type (which is int in both those cases). The
regression occurred because __absu<unsigned>(INT_MIN) is well-formed,
due to the explicit casts to unsigned in that new helper function, and
the out-of-range multiplication is well-formed, because unsigned
arithmetic wraps instead of overflowing.

To fix 92978 I made std::gcm and std::lcm calculate |m| and |n|
immediately, yielding a common unsigned type that was used to calculate
the result. That was partly correct, but there's no need to use an
unsigned type. Doing so only suppresses the overflow errors so the
compiler can't detect them. This change replaces __absu with __abs_r
that returns the common type (not its corresponding unsigned type). This
way we can detect overflow in __abs_r when required, while still
supporting the most-negative value when it can be represented in the
result type. To detect LCM results that are out of range of the result
type we still need explicit checks, because neither constant evaluation
nor UBsan will complain about unsigned wrapping for cases such as
std::lcm(500000u, 499999u). We can detect those overflows efficiently by
using __builtin_mul_overflow and asserting.

libstdc++-v3/ChangeLog:

PR libstdc++/105844
* include/experimental/numeric (experimental::gcd): Simplify
assertions. Use __abs_r instead of __absu.
(experimental::lcm): Likewise. Remove use of __detail::__lcm so
overflow can be detected.
* include/std/numeric (__detail::__absu): Rename to __abs_r and
change to allow signed result type, so overflow can be detected.
(__detail::__lcm): Remove.
(gcd): Simplify assertions. Use __abs_r instead of __absu.
(lcm): Likewise. Remove use of __detail::__lcm so overflow can
be detected.
* testsuite/26_numerics/gcd/gcd_neg.cc: Adjust dg-error lines.
* testsuite/26_numerics/lcm/lcm_neg.cc: Likewise.
* testsuite/26_numerics/gcd/105844.cc: New test.
* testsuite/26_numerics/lcm/105844.cc: New test.
libstdc++-v3/include/experimental/numeric
libstdc++-v3/include/std/numeric
libstdc++-v3/testsuite/26_numerics/gcd/105844.cc [new file with mode: 0644]
libstdc++-v3/testsuite/26_numerics/gcd/gcd_neg.cc
libstdc++-v3/testsuite/26_numerics/lcm/105844.cc [new file with mode: 0644]
libstdc++-v3/testsuite/26_numerics/lcm/lcm_neg.cc