Skip to content

[libc++] Fix incorrect overflow checking in std::lcm #96310

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Jun 25, 2024

Conversation

ldionne
Copy link
Member

@ldionne ldionne commented Jun 21, 2024

We should have been using __builtin_mul_overflow from the start instead of adding a manual (and error-prone) check for overflow.

Fixes #96196

We should have been using __builtin_mul_overflow from the start instead
of adding a manual (and error-prone) check for overflow.

Fixes llvm#96196
@ldionne ldionne requested a review from a team as a code owner June 21, 2024 14:05
@llvmbot llvmbot added the libc++ libc++ C++ Standard Library. Not GNU libstdc++. Not libc++abi. label Jun 21, 2024
@llvmbot
Copy link
Member

llvmbot commented Jun 21, 2024

@llvm/pr-subscribers-libcxx

Author: Louis Dionne (ldionne)

Changes

We should have been using __builtin_mul_overflow from the start instead of adding a manual (and error-prone) check for overflow.

Fixes #96196


Full diff: https://github.com/llvm/llvm-project/pull/96310.diff

2 Files Affected:

  • (modified) libcxx/include/__numeric/gcd_lcm.h (+5-2)
  • (modified) libcxx/test/std/numerics/numeric.ops/numeric.ops.lcm/lcm.pass.cpp (+25-1)
diff --git a/libcxx/include/__numeric/gcd_lcm.h b/libcxx/include/__numeric/gcd_lcm.h
index 87ebbae0157f5..3916db2fae0e6 100644
--- a/libcxx/include/__numeric/gcd_lcm.h
+++ b/libcxx/include/__numeric/gcd_lcm.h
@@ -117,8 +117,11 @@ _LIBCPP_CONSTEXPR _LIBCPP_HIDE_FROM_ABI common_type_t<_Tp, _Up> lcm(_Tp __m, _Up
   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;
+  _Rp __res;
+  bool __overflow = __builtin_mul_overflow(__val1, __val2, &__res);
+  (void)__overflow;
+  _LIBCPP_ASSERT_ARGUMENT_WITHIN_DOMAIN(!__overflow, "Overflow in lcm");
+  return __res;
 }
 
 #endif // _LIBCPP_STD_VER
diff --git a/libcxx/test/std/numerics/numeric.ops/numeric.ops.lcm/lcm.pass.cpp b/libcxx/test/std/numerics/numeric.ops/numeric.ops.lcm/lcm.pass.cpp
index 16bfbbef41c45..992ecf64a8567 100644
--- a/libcxx/test/std/numerics/numeric.ops/numeric.ops.lcm/lcm.pass.cpp
+++ b/libcxx/test/std/numerics/numeric.ops/numeric.ops.lcm/lcm.pass.cpp
@@ -89,6 +89,13 @@ constexpr bool do_test(int = 0)
     return accumulate;
 }
 
+template <class T>
+constexpr bool test_limits() {
+    assert(std::lcm(std::numeric_limits<T>::max() - 1, std::numeric_limits<T>::max() - 1) == std::numeric_limits<T>::max() - 1);
+    assert(std::lcm(std::numeric_limits<T>::max(), std::numeric_limits<T>::max()) == std::numeric_limits<T>::max());
+    return true;
+}
+
 int main(int argc, char**)
 {
     int non_cce = argc; // a value that can't possibly be constexpr
@@ -141,5 +148,22 @@ int main(int argc, char**)
     assert(res1 == 1324997410816LL);
     }
 
-  return 0;
+    // https://github.com/llvm/llvm-project/issues/96196
+    {
+        assert(test_limits<unsigned int>());
+        assert(test_limits<std::uint32_t>());
+        assert(test_limits<std::uint64_t>());
+        assert(test_limits<int>());
+        assert(test_limits<std::int32_t>());
+        assert(test_limits<std::int64_t>());
+
+        static_assert(test_limits<unsigned int>(), "");
+        static_assert(test_limits<std::uint32_t>(), "");
+        static_assert(test_limits<std::uint64_t>(), "");
+        static_assert(test_limits<int>(), "");
+        static_assert(test_limits<std::int32_t>(), "");
+        static_assert(test_limits<std::int64_t>(), "");
+    }
+
+    return 0;
 }

Copy link

@dimitry-unified-streaming dimitry-unified-streaming left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

Comment on lines 121 to 123
bool __overflow = __builtin_mul_overflow(__val1, __val2, &__res);
(void)__overflow;
_LIBCPP_ASSERT_ARGUMENT_WITHIN_DOMAIN(!__overflow, "Overflow in lcm");
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
bool __overflow = __builtin_mul_overflow(__val1, __val2, &__res);
(void)__overflow;
_LIBCPP_ASSERT_ARGUMENT_WITHIN_DOMAIN(!__overflow, "Overflow in lcm");
[[maybe_unused]] bool __overflow = __builtin_mul_overflow(__val1, __val2, &__res);
_LIBCPP_ASSERT_ARGUMENT_WITHIN_DOMAIN(!__overflow, "Overflow in lcm");

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is the general preference in libc++ to suppress unused warnings? I haven't made any inventory so I'm not sure what is used more often, casting to void or using maybe_unused.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Casting to void is used more often because a lot of code has to work in C++03 and not all compilers we support have [[]] attributes in C++03 yet.

@ldionne ldionne merged commit fd62906 into llvm:main Jun 25, 2024
56 of 57 checks passed
@ldionne ldionne deleted the review/fix-lcm-with-hardening branch June 25, 2024 13:35
AlexisPerry pushed a commit to llvm-project-tlp/llvm-project that referenced this pull request Jul 9, 2024
We should have been using __builtin_mul_overflow from the start instead
of adding a manual (and error-prone) check for overflow.

Fixes llvm#96196
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
libc++ libc++ C++ Standard Library. Not GNU libstdc++. Not libc++abi.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

std::lcm(UINT32_MAX, UINT32_MAX) fails when _LIBCPP_HARDENING_MODE != NONE
5 participants