Closed erickt closed 2 years ago
Sorry, but this is not sound. You can reborrow a &mut UninitSlice
to get a "subslice" to the same range. The subslice can then be converted to a &mut [MaybeUninit<u8>]
, but the original slice will still be usable once you stop using the subslice.
More generally, you need to be able to pass a &mut UninitSlice
to untrusted code and trust that it wont deinitialize it. Even without reborrowing, this would break that.
Sorry, but this is not sound. You can reborrow a &mut UninitSlice to get a "subslice" to the same range. The subslice can then be converted to a &mut [MaybeUninit
], but the original slice will still be usable once you stop using the subslice. More generally, you need to be able to pass a
&mut UninitSlice
to untrusted code and trust that it wont deinitialize it. Even without reborrowing, this would break that.
Ah good point, I didn't consider reborrowing, yeah that would let us deinitialize UninitSlice
. However, could you go into more detail about how this is unsound? I don't believe reborrowing would let us get overlapping &mut
sections.
Would it be worthwhile to instead add a separate BytesMut::chunk_mut_slice(&mut self) -> &mut [MaybeUninit<u8>]
(or something), since a lot of APIs seem to be using this type for working with uninitialized slices? Or would you rather hold off on that now, wait for something like https://doc.rust-lang.org/nightly/std/io/struct.ReadBuf.html to stabilize before considering new APIs?
I'm fine with adding an unsafe method for converting to &mut [MaybeUninit<u8>]
if it's something we do often.
As for reborrowing, it really does give overlapping &mut
sections. The reason it isn't UB is that the compiler will prevent you from using the original slice until after the last use of the reborrow. Since only one of them is usable at the time, the exclusivity is still upheld.
More fundamentally, the reason its unsound to do this is that the code that created the &mut UninitSlice
knows that part of the slice is already initialized, and for safety reasons, it needs that part of the slice to remain initialized after giving out an &mut UninitSlice
to the memory. Any way of deinitializing it given the &mut UninitSlice
(or any reference or pointer derived from it) would cause issues.
Note that Tokio already has a stable variant of ReadBuf
.
I'm fine with adding an unsafe method for converting to &mut [MaybeUninit
] if it's something we do often. ... More fundamentally, the reason its unsound to do this is that the code that created the &mut UninitSlice knows that part of the slice is already initialized, and for safety reasons, it needs that part of the slice to remain initialized after giving out an &mut UninitSlice to the memory. Any way of deinitializing it given the &mut UninitSlice (or any reference or pointer derived from it) would cause issues.
Ah, that's great to know. It'd be great to document this, since it'd be important to check for this invariant during reviews. Would you be okay with the name UninitSlice::as_uninit_slice_mut
? It looks like the standard library may stabilize on that naming scheme: https://doc.rust-lang.org/std/primitive.pointer.html#method.as_uninit_slice_mut
Note that Tokio already has a stable variant of ReadBuf.
Oh sure, I just figured that when we stabilize std::io::ReadBuf, we might want to directly integrate support into the bytes
crate. But we probably don't want to make the bytes
crate depend on tokio
for this API. We could lift tokio's ReadBuf into it's own crate, but I imagine that'd take some time to detangle it from everything else in tokio, so it might not be worth it.
Thanks for the review! I pushed up a new version that hopefully fixes your concerns.
This adds an unsafe method to convert a
&mut UninitSlice
into a&mut [MaybeUninit<u8>]
. This method is unsafe because some of the bytes in the slice may be initialized, and the caller should not overwrite them with uninitialized bytes.This came about when auditing tokio-util's udp frame, where they want to pass the unitialized portion of a
BytesMut
to ReadBuf::uninit. They need to do this unsafe pointer casting in a few places, which complicates audits. This method lets us document the safety invariants the caller needs to maintain when doing this conversion.