Skip to content

Safety of ptr::swap is unclear #81005

Closed

Description

The ptr::swap function allows passed pointers to overlap, and its safety section is as follows:

Behavior is undefined if any of the following conditions are violated:

  • Both x and y must be valid for both reads and writes.
  • Both x and y must be properly aligned.

Note that even if T has size 0, the pointers must be non-NULL and properly aligned.

It says that both pointers must be "valid," but IIUC, the definition of validity becomes unclear under Stacked Borrows.

Consider the following program (all code in this comment is checked using Miri with MIRIFLAGS="-Zmiri-track-raw-pointers"):

unsafe {
    let mut x: [i32; 3] = [100, 200, 300];
    let p: *mut i32 = x.as_mut_ptr();
    let p1: *mut [i32; 2] = p as *mut [i32; 2]; // pointer to the first two elements
    let p2: *mut [i32; 2] = &mut *(p.add(1) as *mut [i32; 2]); // pointer to the last two elements

    // ☆: each pointer is valid according to Stacked Borrows.
    *p2 = [100, 200];
    *p1 = [200, 300];
}

p1 and p2 are both "valid" at ☆ in the sense that this program is safe.
p2 is invalidated when we perform a write using p1, but it's ok because we don't use p2 anymore.

However, the following program exhibits UB in spite of the validity of p1 and p2, because ptr::swap first reads from p1 and the read invalidates p2.

// Example 1 (UB)
unsafe {
    let mut x: [i32; 3] = [100, 200, 300];
    let p: *mut i32 = x.as_mut_ptr();
    let p1: *mut [i32; 2] = p as *mut [i32; 2];
    let p2: *mut [i32; 2] = &mut *(p.add(1) as *mut [i32; 2]); // intermediate reference

    std::ptr::swap(p1, p2); // this is UB
}

Note that the intermediate unique reference (&mut *) is crucial for "separating" the two pointers.
If we remove the intermediate reference, those pointers have the same capabilities.
In other words, the following program does not exhibit UB:

// Example 2 (no UB)
unsafe {
    let mut x: [i32; 3] = [100, 200, 300];
    let p: *mut i32 = x.as_mut_ptr();
    let p1: *mut [i32; 2] = p as *mut [i32; 2];
    let p2: *mut [i32; 2] = p.add(1) as *mut [i32; 2]; // no intermediate reference

    std::ptr::swap(p1, p2); // this is not UB
}

The difference between the two examples is so subtle that I'm not sure how to spell out the exact safety conditions for ptr::swap.

This issue is found while formalizing the safety of ptr::swap in the Rustv project.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Assignees

No one assigned

    Labels

    A-raw-pointersArea: raw pointers, MaybeUninit, NonNullT-langRelevant to the language team, which will review and decide on the PR/issue.T-libsRelevant to the library team, which will review and decide on the PR/issue.

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions