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

Change as_ptr to as_mut_ptr to fix Miri error #175

Merged
merged 1 commit into from
Mar 10, 2021

Conversation

jturner314
Copy link
Contributor

@jturner314 jturner314 commented Mar 10, 2021

Before, the example in the docs for ByteOrder::from_slice_i32 caused Miri to error with the message, "error: Undefined Behavior: trying to reborrow for Unique at alloc1389, but parent tag does not have an appropriate item in the borrow stack". Now it runs without errors (tested locally by creating an example and running it with cargo +nightly miri run --example the_example).

(This is the example in the Rust Playground. You can run it with Miri by selecting "Miri" from the "Tools" menu.)

Fwiw, I'm not sure if the original code really has undefined behavior or not, but this PR is a simple change, and the new code is a little clearer anyway.

Before, the example in the docs for `ByteOrder::from_slice_i32` caused
Miri to error with the message, "error: Undefined Behavior: trying to
reborrow for Unique at alloc1389, but parent tag <untagged> does not
have an appropriate item in the borrow stack". Now it runs without
errors.

([This is the example in the Rust
Playground.](https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=d241757cc72e45a5d5e08a7084bb3065)
You can run it with Miri by selecting "Miri" from the "Tools" menu.)
Copy link
Owner

@BurntSushi BurntSushi left a comment

Choose a reason for hiding this comment

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

Thanks!

@BurntSushi BurntSushi merged commit 83f9eea into BurntSushi:master Mar 10, 2021
@BurntSushi
Copy link
Owner

This PR is on crates.io in byteorder 1.4.3.

@BurntSushi
Copy link
Owner

@RalfJung Would you be so kind as to help me understand why this is UB? Of course the change in and of itself makes the code clearer, and I trust that Miri is correct here. So I am not trying to argue this. But rather, I just don't understand. My understanding of const vs mut raw pointers is that their only difference is in variance, but one can otherwise freely change between them. Perhaps my mental model requires revision? :-)

@RalfJung
Copy link

RalfJung commented Mar 10, 2021

My understanding of const vs mut raw pointers is that their only difference is in variance, but one can otherwise freely change between them. Perhaps my mental model requires revision? :-)

At least the way Miri is currently implemented, this is only almost correct. You can indeed freely cast between them to no effect, but casting a reference to a const ptr is different from casting a reference to a mut ptr. IOW, the kind of raw ptr matters at the transition point from the safe world of references to the unsafe world of raw pointers; it never matters again later in the "life of a raw pointer". If a raw pointer is "born" as *const T, then it is read-only (with the usual exception for UnsafeCell).

Note that long-term, this is not necessarily the model we will end up with, but due to "interesting" behavior of the borrow checker and Stacked Borrows limitations, this is currently the best we can do. Also see:

@@ -1692,7 +1692,7 @@ pub trait ByteOrder:
#[inline]
fn from_slice_i16(src: &mut [i16]) {
let src = unsafe {
slice::from_raw_parts_mut(src.as_ptr() as *mut u16, src.len())
slice::from_raw_parts_mut(src.as_mut_ptr() as *mut u16, src.len())

Choose a reason for hiding this comment

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

Is the as ... even still needed? I think you can make the code slightly more type-safe by removing the casts. :D

(Raw-ptr-casts always make me nervous as they are so incredibly unchecked.)

Copy link
Owner

Choose a reason for hiding this comment

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

Looks like it, unfortunately. Because src is a slice of i16, but we want a slice of u16. (The endianness conversions are implemented on &[u16], and we want to reuse that implementation for a &[i16].)

Choose a reason for hiding this comment

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

Ah, right... If you like, you could use the cast method which makes sure that only the pointee type is changed (but mutability is not).

@BurntSushi
Copy link
Owner

@RalfJung Oh excellent, thank you so much for fixing my mental model. Appreciate it!

@jturner314
Copy link
Contributor Author

Fwiw, one thing that surprised me is that running the tests through Miri (with cargo +nightly miri test) did not catch this, even though the tests call from_slice_i32 (via read_i32_into).

@jturner314 jturner314 deleted the fix-miri-error branch March 10, 2021 21:09
@RalfJung
Copy link

Possibly catching this as part of the tests requires -Zmiri-track-raw-pointers. Without that option, Miri might "confuse" the new read-only raw pointer with previously created read-write raw pointers, and hence grant read-write permission. However, with that option, int-to-ptr-casts are not properly supported.

@jturner314
Copy link
Contributor Author

Thanks for pointing out that option; I didn't notice it in the README. I just tried running MIRIFLAGS='-Zmiri-track-raw-pointers' cargo +nightly miri test with byteorder 5d9d038 (the commit before this PR, which causes Miri to error on the example), but the tests didn't error in this case either. (Note: If anyone reading this wants to try it, make sure to have plenty of RAM; it took ~45 minutes and used ~16 GiB of memory.) It is puzzling. I'll try updating to the latest nightly. (I've been using 2021-02-10.)

@jturner314
Copy link
Contributor Author

There is no change with the latest nightly (rustc 2021-03-09, miri 2021-03-02). Oh, well. I don't have time to pursue this further, but I thought it was interesting to point out.

@RalfJung
Copy link

Hm, so something must be different about how the test suite calls this than about how you called it in the code that triggered this PR...

@RalfJung
Copy link

@jturner314

even though the tests call from_slice_i32 (via read_i32_into).

read_i32_into calls read_u32_into, I do not see a call to from_slice_i32 there. Am I looking at the wrong thing?

@jturner314
Copy link
Contributor Author

Oh, you're right. I'm sorry for the confusion. I misread the testing code. I was looking at ReadBytesExt::read_i32_into in io.rs, rather than ByteOrder::read_i32_into in lib.rs. ReadBytesExt::read_i32_into does call from_slice_i32, but the non-doc-tests use ByteOrder::read_i32_into, which does not. The only tests which ultimately call from_slice_i32 are the doc tests for from_slice_i32 and ReadBytesExt::read_i32_into, which Miri doesn't run.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants