libstdc++: Remove UB from month and weekday additions and subtractions.
commit2cb3d42d3f3e7a5345ee7a6f3676a10c84864d72
authorCassio Neri <cassio.neri@gmail.com>
Sun, 10 Dec 2023 11:31:31 +0000 (10 11:31 +0000)
committerJonathan Wakely <jwakely@redhat.com>
Fri, 5 Jan 2024 10:23:35 +0000 (5 10:23 +0000)
tree11f8bad18db9ca7a2f50bed1a603de7e34b71b90
parent9f3eb93e72703f6ea30aa27d0b6fc6db62cb4a04
libstdc++: Remove UB from month and weekday additions and subtractions.

The following invoke signed integer overflow (UB) [1]:

  month   + months{MAX} // where MAX is the maximum value of months::rep
  month   + months{MIN} // where MIN is the maximum value of months::rep
  month   - months{MIN} // where MIN is the minimum value of months::rep
  weekday + days  {MAX} // where MAX is the maximum value of days::rep
  weekday - days  {MIN} // where MIN is the minimum value of days::rep

For the additions to MAX, the crux of the problem is that, in libstdc++,
months::rep and days::rep are int64_t. Other implementations use int32_t, cast
operands to int64_t and perform arithmetic operations without risk of
overflowing.

For month + months{MIN}, the implementation follows the Standard's "returns
clause" and evaluates:

   modulo(static_cast<long long>(unsigned{__x}) + (__y.count() - 1), 12);

Overflow occurs when MIN - 1 is evaluated. Casting to a larger type could help
but, unfortunately again, this is not possible for libstdc++.

For the subtraction of MIN, the problem is that -MIN is not representable.

It's fair to say that the intention is for these additions/subtractions to
be performed in modulus (12 or 7) arithmetic so that no overflow is expected.

To fix these UB, this patch implements:

  template <unsigned __d, typename _T>
  unsigned __add_modulo(unsigned __x, _T __y);

  template <unsigned __d, typename _T>
  unsigned __sub_modulo(unsigned __x, _T __y);

which respectively, returns the remainder of Euclidean division of, __x + __y
and __x - __y by __d without overflowing. These functions replace

  constexpr unsigned __modulo(long long __n, unsigned __d);

which also calculates the reminder of __n, where __n is the result of the
addition or subtraction. Hence, these operations might invoke UB before __modulo
is called and thus, __modulo can't do anything to remediate the issue.

In addition to solve the UB issues, __add_modulo and __sub_modulo allow better
codegen (shorter and branchless) on x86-64 and ARM [2].

[1] https://godbolt.org/z/a9YfWdn57
[2] https://godbolt.org/z/Gh36cr7E4

libstdc++-v3/ChangeLog:

* include/std/chrono: Fix + and - for months and weekdays.
* testsuite/std/time/month/1.cc: Add constexpr tests against overflow.
* testsuite/std/time/month/2.cc: New test for extreme values.
* testsuite/std/time/weekday/1.cc: Add constexpr tests against overflow.
* testsuite/std/time/weekday/2.cc: New test for extreme values.
libstdc++-v3/include/std/chrono
libstdc++-v3/testsuite/std/time/month/1.cc
libstdc++-v3/testsuite/std/time/month/2.cc [new file with mode: 0644]
libstdc++-v3/testsuite/std/time/weekday/1.cc
libstdc++-v3/testsuite/std/time/weekday/2.cc [new file with mode: 0644]