Skip to content

When using std::ptr::copy and similar methods, does the memory need to be valid for T? #330

Closed
rust-lang/rust
#99084

Description

Plenty of methods in std::ptr take a generic T and use *mut/const T, but does this mean that the following code is UB?

let src: u8 = 42;
let mut dest: u8 = 10;
std::ptr::copy(&src as *const u8 as *const bool, &mut dest as *mut u8 as *mut bool);

What about copying a u32 as a (u16, u8), or a (u16, u8, !), that doesn't uninit any padding, right?

Same question applies to copy_nonoverlapping, swap(_nonoverlapping), write_bytes. I don't see any other methods that this would apply to.

What about copying a pointer with an integer type, that preserves provenance?


I'd be inclined to say "no, it's not UB, the generic parameter is only used for its size and alignment, no other property of the type matters", but if that's the case, we should make that clear. It definitely seems to be implied.

Activity

  1. digama0 commented on Apr 24, 2022

    @digama0

    It's not UB necessarily, but it is semantically different. The implementation is permitted to not copy padding bytes and/or make them uninit in the destination if you copy a u32 as a (u16, u8), so reading the destination at u32 later will most likely be UB.

    Relevant discussion: rust-lang/rust#63159 . The resolution of that issue will determine whether copy and copy_nonoverlapping copy padding bytes or do a typed copy.

  2. elichai commented on Apr 24, 2022

    @elichai

    About the UB issue, AFAIU the whole point of raw pointers operations (ie ptr.write(val) instead of *ptr = val) is to not assert validity, meaning that this code is valid AFAIU:

    let src: MaybeUninit<i32> = MaybeUninit::uninit();
    let mut dst: MaybeUninit<i32> = MaybeUninit::uninit();
    let src_ptr: *const i32 = src.as_ptr();
    let dest_ptr: *mut i32 = dest.as_mut_ptr();
    unsafe {ptr::copy(src_ptr, dest_ptr, 1);}

    because we never created a reference to u8 nor did we read its value, so it should be valid code.

    but as @digama0 said, the type is used for more than just size+align, I read this code as "Copy the values like you would with *ptr = value; without asserting the validity of the value", so when you copy a bool it could just copy a single bit and ignore the rest, just like with a regular bool write.

  3. digama0 commented on Apr 24, 2022

    @digama0

    @elichai That's not my impression, although you might be right in the specific case of ptr::copy as discussed in the linked issue. For ptr::write and ptr::read though I'm pretty sure these must be typed copies because the type signature of the function includes an explicit value of type T which is typed-copied from the callee's return place to the caller's temporary storage (at least semantically, even if this is optimized away in practice). So something like MaybeUninit::<u32>::uninit().as_ptr().read() looks like it should be UB (I think it is still open whether to do this for u32 but if you use bool instead then this is definitely UB), even if the value is unused.

    Edit: Miri will currently report UB if you use ptr::write or ptr::read with uninit bool but not on u32. It will not complain if you ptr::copy an uninit bool.

  4. elichai commented on Apr 26, 2022

    @elichai

    Edit: Miri will currently report UB if you use ptr::write or ptr::read with uninit bool but not on u32. It will not complain if you ptr::copy an uninit bool.

    Well, to use ptr::write on uninitialized data you need to first read it into the stack as T not as MaybeUninit<T>, so that by itself is UB.
    It seems like ptr::copy* are the only functions that allow you to write uninit data without first reading it.

  5. RalfJung commented on Apr 30, 2022

    @RalfJung
    Member

    As was already noted, this is basically the same as rust-lang/rust#63159: do these operations do a typed copy (like a read followed by a write), or do they propagate the underlying bytes unchanged and T basically doesn't matter (except for its type)?

    For backwards compatibility, we probably have to go with the latter here.

  6. RalfJung commented on Apr 30, 2022

    @RalfJung
    Member

    Edit: Miri will currently report UB if you use ptr::write or ptr::read with uninit bool but not on u32.

    With -Zmiri-check-number-validity it will also complain on u32.

    It seems like ptr::copy* are the only functions that allow you to write uninit data without first reading it.

    👍

  7. RalfJung commented on Jul 8, 2022

    @RalfJung
    Member

    Oh, good catch. Yes as far as I am concerned that was always meant to just mean "of course you can't use a [42_u8] as a bool".

  8. added a commit that references this issue on Jul 25, 2022
  9. added a commit that references this issue on Jul 25, 2022
  10. ghost closed this as completedon Jul 26, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions