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.