<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">