-
Notifications
You must be signed in to change notification settings - Fork 4
Conversation
I have made 1 change: convert struct Stowaway {
storage: *mut T,
} to struct Stowaway {
storage: MaybeUninit<*mut T>,
} in order to correctly handle uninitialized bytes inside of
We could provide a checked version that only returns a valid pointer if we |
One more note: while #3 seems to be fixed prior to this PR, I don't think it is sound. A perfectly valid implementation of fn write<T>(ptr: *mut T, value: T) {
ptr.cast::<u8>().copy_from_nonoverlapping(&value as *const T as *const u8, std::mem::size_of::<T>());
forget(value);
} Since (testing with this implementation of |
After playing around with the tests from #3 some more, this type fails MIRI when you use #[repr(C)]
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
struct GapsTest {
a: u8,
_align: [usize; 0],
} This is because (as I said above), Rust does not guarantee that paddings bytes won't be copied. And given that this type is pointer aligned, it uses a word-aligned store to move this type. This also copies all padding bytes (even though you try and zero them). |
reorganize tests remove faulty test from Lucretiel#3
Because of this new finding, I removed the faulty tests from #3, and manually copied padding bytes when running MIRI (using the alternative implementation of |
I left the |
On a separate note: You should have a I can do both in a separate PR. |
Thank you for all the work you've put into this; I'm going to look more into the precise semantics of In the meantime, a design question: if we don't allow uninitialized bytes when using |
You can use
So all together, my |
And to be clear, this happens because |
When you call
But if it does overwrite the a byte, and that byte was initialized, then the new byte will be the same as the corrosponding byte in the |
I think an example would be helpful, // assuming 32-bit for brevity
// assume (u8, u16) is laid out like [init, padding, init, init]
// *ptr starts as all zero
// *ptr == [0, 0, 0, 0]
write(ptr, (0_u8, 0xEFAB_u16));
// *ptr could be in one of the following
// [0, uninit, 0xEF, 0xAB] could be uninit because of padding
// [0, 0, 0xEF, 0xAB] or padding could have been omitted |
Doesn't this implementation read uninitialized bytes? Doesn't doing that immediately trigger undefined behavior? |
It's fine to memcopy uninitialized bytes, because memcpy is special (and is working with raw pointers). Note the saftey section for In the example in the docs, the manually implemented |
@Lucretiel just pinging to check on progress, can this be merged or do you need more time/information to review this? |
No updates; I'm mostly trying to find some more authoritative information on the current rules regarding padding bytes, and researching if there's ways to detect / force Rust to not copy over padding bytes, even if it means adding some kind of derive or an unsafe trait to types you wish to stow. I admit I'm biased here; the changes as proposed would permanently break cooked-waker, because cooked-waker promises to be able to safely turn any struct into a waker, backed by stowaway. I'm therefore considering something like this: // Implement this trait for any type which is known
// to never contain uninitialized memory or padding
// bytes. Implemented for stdlib types.
//
// Possibly derivable , conditional on
// (sizeof(T) == sum(sizeof(fields)) && fields: Stowable
unsafe trait Stowable {};
fn new<T: Stowable>(value: T) -> Stowaway<T> Your original bug report (#8) specifically called out Stowaway<MaybeUninit> as being unsound. Did you have a particular use case for this pattern that you'd like Stowaway to support, or was that simply a demonstration of how to create UB? |
That was just a demonstration, however, I don't think that it was unreasonable. For example, you could put a Moreover relying on the behavior of write wrt padding bytes is always unsound. One notable example I'veseen is in crossbeam's |
Yes, that is unfortunate. Your |
Have to go to bed, will review in the morning. Thanks for the discussions from crossbeam, that's very helpful. |
Oh, I just realized something! If you implement This way you can still support all types, although it will be suboptimal, or you can opt-in to the optimal way with a small amount of easy to check unsafe. This means |
Yup, that was exactly my point 😀. You wrote it out before I could even get to it this morning. Would you mind if I asked you to file a separate pull request with just the failing tests while I review this solution vs an unsafe trait? |
Feel free to add the variant, UB-detecting write, as well. |
Ok, I'll do that! |
because of #11, I'll close this. Thanks for being so responsive to this issue @Lucretiel. |
fixes #8