<table border="1" cellspacing="0" cellpadding="8">
    <tr>
        <th>Issue</th>
        <td>
            <a href=https://github.com/llvm/llvm-project/issues/96196>96196</a>
        </td>
    </tr>

    <tr>
        <th>Summary</th>
        <td>
            std::lcm(UINT32_MAX, UINT32_MAX) fails when _LIBCPP_HARDENING_MODE != NONE
        </td>
    </tr>

    <tr>
      <th>Labels</th>
      <td>
            new issue
      </td>
    </tr>

    <tr>
      <th>Assignees</th>
      <td>
      </td>
    </tr>

    <tr>
      <th>Reporter</th>
      <td>
          dimitry-unified-streaming
      </td>
    </tr>
</table>

<pre>
    With any hardening mode above `_LIBCPP_HARDENING_MODE_NONE`, attempting to calculate `std::lcm` of various unsigned MAX values fails. For example with `UINT32_MAX`:

```c++
#include <limits>
#include <numeric>

static_assert(std::lcm(UINT32_MAX - 1, UINT32_MAX - 1) == UINT32_MAX - 1);

static_assert(std::lcm(UINT32_MAX, UINT32_MAX) == UINT32_MAX);

static_assert(std::lcm(INT32_MAX, INT32_MAX) == INT32_MAX);

#if 0 // expected to fail
static_assert(std::lcm(INT32_MIN, INT32_MIN) == INT32_MAX);
#endif
```

resulting in:

```text
lcmtest.cpp:6:15: error: static assertion expression is not an integral constant expression
    6 | static_assert(std::lcm(UINT32_MAX, UINT32_MAX) == UINT32_MAX);
      | ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
/usr/include/c++/v1/__numeric/gcd_lcm.h:80:3: note: subexpression not valid in a constant expression
   80 | _LIBCPP_ASSERT_ARGUMENT_WITHIN_DOMAIN((numeric_limits<_Rp>::max() / __val1 > __val2), "Overflow in lcm");
      | ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
/usr/include/c++/v1/__config:379:74: note: expanded from macro '_LIBCPP_ASSERT_ARGUMENT_WITHIN_DOMAIN'
  379 | #    define _LIBCPP_ASSERT_ARGUMENT_WITHIN_DOMAIN(expression, message) _LIBCPP_ASSERT(expression, message)
      | ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
/usr/include/c++/v1/__assert:23:10: note: expanded from macro '_LIBCPP_ASSERT'
   23 |        : _LIBCPP_ASSERTION_HANDLER(__FILE__ ":" _LIBCPP_TOSTRING(__LINE__) ": assertion " _LIBCPP_TOSTRING(            \
      | ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 24 |              expression) " failed: " message "\n"))
      | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
/usr/include/c++/v1/__assertion_handler:27:62: note: expanded from macro '_LIBCPP_ASSERTION_HANDLER'
   27 | #  define _LIBCPP_ASSERTION_HANDLER(message) ((void)message, __builtin_trap())
 | ^~~~~~~~~~~~~~
lcmtest.cpp:6:15: note: in call to 'lcm<unsigned int, unsigned int>(4294967295, 4294967295)'
    6 | static_assert(std::lcm(UINT32_MAX, UINT32_MAX) == UINT32_MAX);
      | ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
lcmtest.cpp:8:15: error: static assertion expression is not an integral constant expression
    8 | static_assert(std::lcm(INT32_MAX, INT32_MAX) == INT32_MAX);
      | ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
/usr/include/c++/v1/__numeric/gcd_lcm.h:80:3: note: subexpression not valid in a constant expression
   80 | _LIBCPP_ASSERT_ARGUMENT_WITHIN_DOMAIN((numeric_limits<_Rp>::max() / __val1 > __val2), "Overflow in lcm");
      | ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
/usr/include/c++/v1/__config:379:74: note: expanded from macro '_LIBCPP_ASSERT_ARGUMENT_WITHIN_DOMAIN'
  379 | #    define _LIBCPP_ASSERT_ARGUMENT_WITHIN_DOMAIN(expression, message) _LIBCPP_ASSERT(expression, message)
      | ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
/usr/include/c++/v1/__assert:23:10: note: expanded from macro '_LIBCPP_ASSERT'
   23 |        : _LIBCPP_ASSERTION_HANDLER(__FILE__ ":" _LIBCPP_TOSTRING(__LINE__) ": assertion " _LIBCPP_TOSTRING(            \
      | ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 24 |              expression) " failed: " message "\n"))
      | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
/usr/include/c++/v1/__assertion_handler:27:62: note: expanded from macro '_LIBCPP_ASSERTION_HANDLER'
   27 | #  define _LIBCPP_ASSERTION_HANDLER(message) ((void)message, __builtin_trap())
 | ^~~~~~~~~~~~~~
lcmtest.cpp:8:15: note: in call to 'lcm<int, int>(2147483647, 2147483647)'
    8 | static_assert(std::lcm(INT32_MAX, INT32_MAX) == INT32_MAX);
      | ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
2 errors generated.
```

And similar for `UINT64_MAX`, `std::numeric_limits<unsigned>::max()`, etc.

For signed quantities this could indeed occur, so for example `std::lcm(INT32_MIN, INT32_MIN)` is undefined. But MAX values, which are positive, should still work, even for signed types.

In libstdc++ this is handled at compile time by doing:

```c++
      if constexpr (is_signed_v<_Ct>)
        if (__is_constant_evaluated())
 return __r * __n2; // constant evaluation can detect overflow here.

 bool __overflow = __builtin_mul_overflow(__r, __n2, &__r);
```

but libc++ does:

```c++
  using _Rp  = common_type_t<_Tp, _Up>;
  _Rp __val1 = __ct_abs<_Rp, _Tp>()(__m) / std::gcd(__m, __n);
  _Rp __val2 = __ct_abs<_Rp, _Up>()(__n);
 _LIBCPP_ASSERT_ARGUMENT_WITHIN_DOMAIN((numeric_limits<_Rp>::max() / __val1 > __val2), "Overflow in lcm");
  return __val1 * __val2;
```

I think the assertion should check for `>=`, in case the types (or the common type at least) are unsigned. For the other cases, `>` should be fine. (This avoids using `__builtin_mul_overflow` which may not be available everywhere, but I'm not sure if it isn't used anywhere else.)

</pre>
<img width="1px" height="1px" alt="" src="http://email.email.llvm.org/o/eJzsWV1v4jwW_jXm5qgoOJDABReUjxmkKX3V6eidO8skB_BOYrO2Q9ub_e2r4xAaOp1uW-1oZ6VGVUsS2-freY6fGumc2mrEMRtcssGsIyu_M3acq1J5-3BRabVRmF84b1GWSm87a5M_jP9WfgdSP8BO2hy10lsoTY4g1-aAwJJIfFleTv_6S3ye3Mzmq-Xqk7i6ns3F6no1Z0nE-BSk91juPU31BjJZZFUhfZjsfM7iCYsnRVayJAKzgYO0ylQOKh3czeFq8h0OsqjQwUaqwnVhYSzgvSz3BcId-ceS6NtydRtzcTX5TlbjCYtmLGp-J1H9kzF-ST_1Ux4rnRVVjsDiaUFpcCyeP_dSVyValT2-Db-dl15lQjqH1jM-PAuGDx89ggvoUSKePhkBi2csnv38gsWX77J0buVZC29e_Wzx59Z-YWlK4wYiYHzB-ALwfo-Zx5xwQLV8i_3lqmWfbv6DfR6jztXmCQLazll0VRFgqfSvEOPx3tePiqz06Hw32-9ZPElYPOkNWDwBtNZY-lCHAXUYymiK1qJz9FE50MaD1KC0x62VBWRGOy-1bw2rDQEAJMDSKfyuskO4yAIbzP_1lqvJ7aJylvHFkSKMLxpm8cWhx_hCiIYyfLHNclFkZXfH4smQqBlTtrTxGLJWrVt5oiQdZKFyUBrkizkaRiGCpv1Mvn6d39yKyc2nb1fz1a34e3n7ebkSs-urCYFlyPjw6JJoqD4VN3uidEhpKe_DqBFhFYQ4yKIHLJ7XHzmlj0-BcX59QLspzB15GOrA_zupff_1-qJkRm_UlkqQjlg8SfvtSuD9Xuocc9hYU0IpM2uA8fSVCU6b-ON0VMfPY0pFjhul8dVlahWaT6FE5-QWqSrnC7ww8l11eH0Kj2SMJ5xg3IvenMFWqoDHwcXjRUucj11er8TnyWr2ZX7D-FCIxfLLXAhCIYGW89Pw2-uvtzfL1acw6styNReihjINbLWkX8yB1sUG0_8dlM_KAbzfTk99tcseAgwbCVJjDHdHIITQB1N9pOfPsHiDH6-HhTJa7KTOC6Qdgae0UfA3I-Ss6i2wpI-8epZV52hpcadufwejcsZHp-dTEGJdKdoBhbdyX7e_x1Q9V_mXt8ImSqVJ5RW0yzOeUpOMpyc5p7Qn22f38ZzxYZ-P-qMk5aMBvW_fjdpp-IP2xueyMfy9wmD4muDfp9jeyfcPSfAhCT4kwYck-M3XhyT4P5IEw1dJgqMSOAkA3uun_WGc9FN63L47FwB_xB5Yj-f1Lu9gixqt9Jh3XzhwmOgcnCpVIS1sjG2OrZJ-c2xF20nrUOyn3akRTT9vUcfZ6LNu2-LCWDjqrH9WUnvlFTrwO-UgM1VBe2qOmIPJssrSAs4Ez5rTtadHdC-cxrAkIjVT6RqIeRcuK986vKPxdzuV7UBahL1xyqtDwJzbBVecV0UBd8b-CJEcUAdXju77hz26s9iWGgq1dj4_Mq8OSzmo6ZaD9JCZcq8KBK9KhPUD5Ebp7atOB2skqE0tOKjFEGeUE7U_4kBiYVoj96yThEmh6SonGrUikJJA-HhKKYu-shqEoPUnIITmLL5sDswe1U49nxp2JjXk6DHzYBrhsUOLZ8mBtTEFCHEaQZB_JHdZFadXwVdbk1_zWtIk4UnrNO05OK8rTxVo0p8bdK9MbeWU3oK42UPwKzNlabSgEgtPeb3dB3e-1VrsNI0mnGQYhZN5IdeNbKMZt_u6lYQUD4UoG_V2QvE2y5s3IeBz9p8s8F9Z-PbEwvkCf5z0PMGrXiEgLKzwYmWXxCX9A_wOW0rhyNNsh9mPpn8Fj2fH7hOavMMwLfCVeGBsuK9rHB4TMQuUzlN81Ayatlaf7NNo43dow2Lu2BTJUBI1PqwRqMl0ycAt0V7SVuaOwGJJ9AuoJ9GxB5XyIfxnsUaQB6kKuS6Qeo59uCMukVGC95LxtAwDXWWRmK08KKcZTz1UjpqMrmcAFg67J1538nGcj-KR7OC4l_aGw1GcpL3Obhz3NkOUA77e8Dhe8zTlo36U5aNBxnG9xk1HjXnE-1HCo16fp4NeF5O81xvl_RTjuI8Rsn6EpVRFtygOZdfYbUc5V-F4lPRGSaeQayxc-I6Hc413EF4G6TPr2DHNuVhXW8f6UaGcd4-reOULHL_h_-jwbQzc7VDD818CAeM9otHqejXvVLYY77zfhx4RuttW-V217mamZHxBbhz_XOyt-QdmnuQVOe8YX9TBHcb83wEAAP__7NVOZw">