Skip to content
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

Fix unsoundness in new is_power_of_two fast path #120546

Closed
wants to merge 1 commit into from

Conversation

oli-obk
Copy link
Contributor

@oli-obk oli-obk commented Feb 1, 2024

fixes #120537

@rustbot
Copy link
Collaborator

rustbot commented Feb 1, 2024

r? @cuviper

(rustbot has picked a reviewer for you, use r? to override)

@rustbot rustbot added S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. T-libs Relevant to the library team, which will review and decide on the PR/issue. labels Feb 1, 2024
@rustbot
Copy link
Collaborator

rustbot commented Feb 1, 2024

The Miri subtree was changed

cc @rust-lang/miri

Copy link
Contributor

@NCGThompson NCGThompson left a comment

Choose a reason for hiding this comment

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

The same bug exists in library/core/src/num/int_macros.rs. Can you fix that too while you are at it?

@cuviper
Copy link
Member

cuviper commented Feb 1, 2024

I think it is not as simple as exp >= Self::BITS / power_used, because that division may round down. For example, this should pass, but it fails with your change:

assert_eq!(8u64.checked_pow(21), Some(1u64 << 63));

That sees 21 >= 64 / 3, which is equal so it returns None.

@@ -288,6 +288,7 @@ macro_rules! int_module {
assert_eq!(r.saturating_pow(2), 4 as $T);
assert_eq!(r.saturating_pow(3), -8 as $T);
assert_eq!(r.saturating_pow(0), 1 as $T);
assert_eq!(2i64.checked_pow(64), None); // issue #120537
Copy link
Member

Choose a reason for hiding this comment

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

These tests are expanded for all integer types $T, so it would be nice to keep it testing each type. In this signed case, above where r = 2 as $T we can check exponent <$T>::BITS - 1 produces None, and with r = -2 we can check that BITS - 1 is Some(<$T>::MIN) and BITS gets None.

@oli-obk
Copy link
Contributor Author

oli-obk commented Feb 1, 2024

This is not the solution, and I don't want to go math golfing. I did want to use checked mul because of the rounding, but feared it would mess up the optimization.

I'll file a revert so we can avoid having it in beta, and then figure out how yo correctly write this logic and add tests for it

@NCGThompson
Copy link
Contributor

I did want to use checked mul because of the rounding, but feared it would mess up the optimization.

It does indeed mess up the optimization a little. While it makes the Rust code concise, LLVM does an extra comparison. It isn't the end of the world though.

@Noratrieb
Copy link
Member

yeah, reverting the libs changes and doing them again very carefully sounds like a good idea

@oli-obk oli-obk deleted the unsound_checked_pow branch February 1, 2024 22:11
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. T-libs Relevant to the library team, which will review and decide on the PR/issue.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

miri: checked_pow: overflowing shift by 64 in unchecked_shl
6 participants