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

UB in <Range as Iterator>::advance_by (Step::forward_unchecked) for signed integers #122420

Closed
CAD97 opened this issue Mar 13, 2024 · 3 comments · Fixed by #122461
Closed

UB in <Range as Iterator>::advance_by (Step::forward_unchecked) for signed integers #122420

CAD97 opened this issue Mar 13, 2024 · 3 comments · Fixed by #122461
Assignees
Labels
C-bug Category: This is a bug. I-unsound Issue: A soundness hole (worst kind of bug), see: https://en.wikipedia.org/wiki/Soundness T-libs Relevant to the library team, which will review and decide on the PR/issue.

Comments

@CAD97
Copy link
Contributor

CAD97 commented Mar 13, 2024

I tried this code: [playground]

#![feature(iter_advance_by)]

fn main() {
    _ = (-128i8..127).advance_by(200);
}

I expected to see this happen: No UB.

Instead, this happened: UB, confirmed by Miri. Step::forward_unchecked(-128i8, 200usize) is called, which does (-128i8).unchecked_add(200usize as i8), or (-128i8).unchecked_add(-56i8).

The implementation of advance_by is correct here, and Step::forward_unchecked(-128i8, 200usize) should be safe to call. The implementation that does a wrapping as cast from usize to iN before doing the unchecked math is the incorrect code here. Signed integers should switch to just using wrapping or unsigned math, whichever gets better codegen out of LLVM. We do have wrapping_add_unsigned and checked_add_unsigned methods now, but no unchecked_add_unsigned.

Meta

rustc --version:

rustc 1.78.0-nightly (2024-03-12 a165f1f65015b1bd4afd)
@CAD97 CAD97 added the C-bug Category: This is a bug. label Mar 13, 2024
@rustbot rustbot added the needs-triage This issue may need triage. Remove it if it has been sufficiently triaged. label Mar 13, 2024
@jhpratt jhpratt added I-unsound Issue: A soundness hole (worst kind of bug), see: https://en.wikipedia.org/wiki/Soundness requires-nightly This issue requires a nightly compiler in some way. T-libs Relevant to the library team, which will review and decide on the PR/issue. and removed needs-triage This issue may need triage. Remove it if it has been sufficiently triaged. labels Mar 13, 2024
@the8472 the8472 self-assigned this Mar 13, 2024
@the8472
Copy link
Member

the8472 commented Mar 13, 2024

Note that this is reachable on stable since the default impl of nth uses advance_by and not all adapters forward both.

fn main() {
    [(-128i8..127)].into_iter().flatten().nth(200);
}

@the8472 the8472 removed the requires-nightly This issue requires a nightly compiler in some way. label Mar 13, 2024
matthiaskrgr added a commit to matthiaskrgr/rust that referenced this issue Mar 14, 2024
…, r=Amanieu

fix unsoundness in Step::forward_unchecked for signed integers

Fixes rust-lang#122420

```rust
pub fn foo(a: i8, b: u8) -> i8 {
    unsafe { a.checked_add_unsigned(b).unwrap_unchecked() }
}
```

still compiles down to a single arithmetic instruction ([godbolt](https://rust.godbolt.org/z/qsd3xYWfE)).

But we may be losing some loop optimizations if llvm can no longer easily derive that it's a finite counted loop from the no-wrapping flags.
jhpratt added a commit to jhpratt/rust that referenced this issue Mar 14, 2024
…, r=Amanieu

fix unsoundness in Step::forward_unchecked for signed integers

Fixes rust-lang#122420

```rust
pub fn foo(a: i8, b: u8) -> i8 {
    unsafe { a.checked_add_unsigned(b).unwrap_unchecked() }
}
```

still compiles down to a single arithmetic instruction ([godbolt](https://rust.godbolt.org/z/qsd3xYWfE)).

But we may be losing some loop optimizations if llvm can no longer easily derive that it's a finite counted loop from the no-wrapping flags.
matthiaskrgr added a commit to matthiaskrgr/rust that referenced this issue Mar 14, 2024
…, r=Amanieu

fix unsoundness in Step::forward_unchecked for signed integers

Fixes rust-lang#122420

```rust
pub fn foo(a: i8, b: u8) -> i8 {
    unsafe { a.checked_add_unsigned(b).unwrap_unchecked() }
}
```

still compiles down to a single arithmetic instruction ([godbolt](https://rust.godbolt.org/z/qsd3xYWfE)).

But we may be losing some loop optimizations if llvm can no longer easily derive that it's a finite counted loop from the no-wrapping flags.
@bors bors closed this as completed in 75dc99b Mar 14, 2024
rust-timer added a commit to rust-lang-ci/rust that referenced this issue Mar 14, 2024
Rollup merge of rust-lang#122461 - the8472:fix-step-forward-unchecked, r=Amanieu

fix unsoundness in Step::forward_unchecked for signed integers

Fixes rust-lang#122420

```rust
pub fn foo(a: i8, b: u8) -> i8 {
    unsafe { a.checked_add_unsigned(b).unwrap_unchecked() }
}
```

still compiles down to a single arithmetic instruction ([godbolt](https://rust.godbolt.org/z/qsd3xYWfE)).

But we may be losing some loop optimizations if llvm can no longer easily derive that it's a finite counted loop from the no-wrapping flags.
@RalfJung
Copy link
Member

Instead, this happened: UB, confirmed by Miri. Step::forward_unchecked(-128i8, 200usize) is called, which does (-128i8).unchecked_add(200usize as i8), or (-128i8).unchecked_add(-56i8).

@CAD97 did you find this with Miri or just use Miri to confirm the bug?
(I'm asking because I may want to add this to our "bugs found by Miri" list :)

@CAD97
Copy link
Contributor Author

CAD97 commented Mar 14, 2024

Only confirmed/diagnosed with Miri, spotted manually when looking at the Step code.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
C-bug Category: This is a bug. I-unsound Issue: A soundness hole (worst kind of bug), see: https://en.wikipedia.org/wiki/Soundness T-libs Relevant to the library team, which will review and decide on the PR/issue.
Projects
None yet
Development

Successfully merging a pull request may close this issue.

5 participants