Rollup merge of #130229 - RalfJung:ptr-offset-unsigned, r=scottmcm
ptr::add/sub: do not claim equivalence with `offset(c as isize)` In https://github.com/rust-lang/rust/pull/110837, the `offset` intrinsic got changed to also allow a `usize` offset parameter. The intention is that this will do an unsigned multiplication with the size, and we have UB if that overflows -- and we also have UB if the result is larger than `usize::MAX`, i.e., if a subsequent cast to `isize` would wrap. ~~The LLVM backend sets some attributes accordingly.~~ This updates the docs for `add`/`sub` to match that intent, in preparation for adjusting codegen to exploit this UB. We use this opportunity to clarify what the exact requirements are: we compute the offset using mathematical multiplication (so it's no problem to have an `isize * usize` multiplication, we just multiply integers), and the result must fit in an `isize`. Cc `@rust-lang/opsem` `@nikic` https://github.com/rust-lang/rust/pull/130239 updates Miri to detect this UB. `sub` still has some cases of UB not reflected in the underlying intrinsic semantics (and Miri does not catch): when we subtract `usize::MAX`, then after casting to `isize` that's just `-1` so we end up adding one unit without noticing any UB, but actually the offset we gave does not fit in an `isize`. Miri will currently still not complain for such cases: ```rust fn main() { let x = &[0i32; 2]; let x = x.as_ptr(); // This should be UB, we are subtracting way too much. unsafe { x.sub(usize::MAX).read() }; } ``` However, the LLVM IR we generate here also is UB-free. This is "just" library UB but not language UB. Cc `@saethlin;` might be worth adding precondition checks against overflow on `offset`/`add`/`sub`? Fixes https://github.com/rust-lang/rust/issues/130211
This commit is contained in:
commit
97cdc8ef44
@ -1425,8 +1425,7 @@ pub fn select_unpredictable<T>(b: bool, true_val: T, false_val: T) -> T {
|
||||
///
|
||||
/// If the computed offset is non-zero, then both the starting and resulting pointer must be
|
||||
/// either in bounds or at the end of an allocated object. If either pointer is out
|
||||
/// of bounds or arithmetic overflow occurs then any further use of the returned value will
|
||||
/// result in undefined behavior.
|
||||
/// of bounds or arithmetic overflow occurs then this operation is undefined behavior.
|
||||
///
|
||||
/// The stabilized version of this intrinsic is [`pointer::offset`].
|
||||
#[must_use = "returns a new pointer rather than modifying its argument"]
|
||||
|
@ -346,7 +346,7 @@ pub const fn to_raw_parts(self) -> (*const (), <T as super::Pointee>::Metadata)
|
||||
if self.is_null() { None } else { Some(unsafe { &*(self as *const MaybeUninit<T>) }) }
|
||||
}
|
||||
|
||||
/// Adds an offset to a pointer.
|
||||
/// Adds a signed offset to a pointer.
|
||||
///
|
||||
/// `count` is in units of T; e.g., a `count` of 3 represents a pointer
|
||||
/// offset of `3 * size_of::<T>()` bytes.
|
||||
@ -355,7 +355,8 @@ pub const fn to_raw_parts(self) -> (*const (), <T as super::Pointee>::Metadata)
|
||||
///
|
||||
/// If any of the following conditions are violated, the result is Undefined Behavior:
|
||||
///
|
||||
/// * The computed offset, `count * size_of::<T>()` bytes, must not overflow `isize`.
|
||||
/// * The offset in bytes, `count * size_of::<T>()`, computed on mathematical integers (without
|
||||
/// "wrapping around"), must fit in an `isize`.
|
||||
///
|
||||
/// * If the computed offset is non-zero, then `self` must be derived from a pointer to some
|
||||
/// [allocated object], and the entire memory range between `self` and the result must be in
|
||||
@ -398,7 +399,7 @@ pub const fn to_raw_parts(self) -> (*const (), <T as super::Pointee>::Metadata)
|
||||
unsafe { intrinsics::offset(self, count) }
|
||||
}
|
||||
|
||||
/// Calculates the offset from a pointer in bytes.
|
||||
/// Adds a signed offset in bytes to a pointer.
|
||||
///
|
||||
/// `count` is in units of **bytes**.
|
||||
///
|
||||
@ -418,7 +419,7 @@ pub const fn to_raw_parts(self) -> (*const (), <T as super::Pointee>::Metadata)
|
||||
unsafe { self.cast::<u8>().offset(count).with_metadata_of(self) }
|
||||
}
|
||||
|
||||
/// Calculates the offset from a pointer using wrapping arithmetic.
|
||||
/// Adds a signed offset to a pointer using wrapping arithmetic.
|
||||
///
|
||||
/// `count` is in units of T; e.g., a `count` of 3 represents a pointer
|
||||
/// offset of `3 * size_of::<T>()` bytes.
|
||||
@ -480,7 +481,7 @@ pub const fn wrapping_offset(self, count: isize) -> *const T
|
||||
unsafe { intrinsics::arith_offset(self, count) }
|
||||
}
|
||||
|
||||
/// Calculates the offset from a pointer in bytes using wrapping arithmetic.
|
||||
/// Adds a signed offset in bytes to a pointer using wrapping arithmetic.
|
||||
///
|
||||
/// `count` is in units of **bytes**.
|
||||
///
|
||||
@ -804,7 +805,11 @@ pub const fn guaranteed_ne(self, other: *const T) -> Option<bool>
|
||||
}
|
||||
}
|
||||
|
||||
/// Adds an offset to a pointer (convenience for `.offset(count as isize)`).
|
||||
/// Adds an unsigned offset to a pointer.
|
||||
///
|
||||
/// This can only move the pointer forward (or not move it). If you need to move forward or
|
||||
/// backward depending on the value, then you might want [`offset`](#method.offset) instead
|
||||
/// which takes a signed offset.
|
||||
///
|
||||
/// `count` is in units of T; e.g., a `count` of 3 represents a pointer
|
||||
/// offset of `3 * size_of::<T>()` bytes.
|
||||
@ -813,7 +818,8 @@ pub const fn guaranteed_ne(self, other: *const T) -> Option<bool>
|
||||
///
|
||||
/// If any of the following conditions are violated, the result is Undefined Behavior:
|
||||
///
|
||||
/// * The computed offset, `count * size_of::<T>()` bytes, must not overflow `isize`.
|
||||
/// * The offset in bytes, `count * size_of::<T>()`, computed on mathematical integers (without
|
||||
/// "wrapping around"), must fit in an `isize`.
|
||||
///
|
||||
/// * If the computed offset is non-zero, then `self` must be derived from a pointer to some
|
||||
/// [allocated object], and the entire memory range between `self` and the result must be in
|
||||
@ -856,7 +862,7 @@ pub const fn guaranteed_ne(self, other: *const T) -> Option<bool>
|
||||
unsafe { intrinsics::offset(self, count) }
|
||||
}
|
||||
|
||||
/// Calculates the offset from a pointer in bytes (convenience for `.byte_offset(count as isize)`).
|
||||
/// Adds an unsigned offset in bytes to a pointer.
|
||||
///
|
||||
/// `count` is in units of bytes.
|
||||
///
|
||||
@ -876,8 +882,11 @@ pub const fn guaranteed_ne(self, other: *const T) -> Option<bool>
|
||||
unsafe { self.cast::<u8>().add(count).with_metadata_of(self) }
|
||||
}
|
||||
|
||||
/// Subtracts an offset from a pointer (convenience for
|
||||
/// `.offset((count as isize).wrapping_neg())`).
|
||||
/// Subtracts an unsigned offset from a pointer.
|
||||
///
|
||||
/// This can only move the pointer backward (or not move it). If you need to move forward or
|
||||
/// backward depending on the value, then you might want [`offset`](#method.offset) instead
|
||||
/// which takes a signed offset.
|
||||
///
|
||||
/// `count` is in units of T; e.g., a `count` of 3 represents a pointer
|
||||
/// offset of `3 * size_of::<T>()` bytes.
|
||||
@ -886,7 +895,8 @@ pub const fn guaranteed_ne(self, other: *const T) -> Option<bool>
|
||||
///
|
||||
/// If any of the following conditions are violated, the result is Undefined Behavior:
|
||||
///
|
||||
/// * The computed offset, `count * size_of::<T>()` bytes, must not overflow `isize`.
|
||||
/// * The offset in bytes, `count * size_of::<T>()`, computed on mathematical integers (without
|
||||
/// "wrapping around"), must fit in an `isize`.
|
||||
///
|
||||
/// * If the computed offset is non-zero, then `self` must be derived from a pointer to some
|
||||
/// [allocated object], and the entire memory range between `self` and the result must be in
|
||||
@ -937,8 +947,7 @@ pub const fn guaranteed_ne(self, other: *const T) -> Option<bool>
|
||||
}
|
||||
}
|
||||
|
||||
/// Calculates the offset from a pointer in bytes (convenience for
|
||||
/// `.byte_offset((count as isize).wrapping_neg())`).
|
||||
/// Subtracts an unsigned offset in bytes from a pointer.
|
||||
///
|
||||
/// `count` is in units of bytes.
|
||||
///
|
||||
@ -958,8 +967,7 @@ pub const fn guaranteed_ne(self, other: *const T) -> Option<bool>
|
||||
unsafe { self.cast::<u8>().sub(count).with_metadata_of(self) }
|
||||
}
|
||||
|
||||
/// Calculates the offset from a pointer using wrapping arithmetic.
|
||||
/// (convenience for `.wrapping_offset(count as isize)`)
|
||||
/// Adds an unsigned offset to a pointer using wrapping arithmetic.
|
||||
///
|
||||
/// `count` is in units of T; e.g., a `count` of 3 represents a pointer
|
||||
/// offset of `3 * size_of::<T>()` bytes.
|
||||
@ -1020,8 +1028,7 @@ pub const fn wrapping_add(self, count: usize) -> Self
|
||||
self.wrapping_offset(count as isize)
|
||||
}
|
||||
|
||||
/// Calculates the offset from a pointer in bytes using wrapping arithmetic.
|
||||
/// (convenience for `.wrapping_byte_offset(count as isize)`)
|
||||
/// Adds an unsigned offset in bytes to a pointer using wrapping arithmetic.
|
||||
///
|
||||
/// `count` is in units of bytes.
|
||||
///
|
||||
@ -1038,8 +1045,7 @@ pub const fn wrapping_byte_add(self, count: usize) -> Self {
|
||||
self.cast::<u8>().wrapping_add(count).with_metadata_of(self)
|
||||
}
|
||||
|
||||
/// Calculates the offset from a pointer using wrapping arithmetic.
|
||||
/// (convenience for `.wrapping_offset((count as isize).wrapping_neg())`)
|
||||
/// Subtracts an unsigned offset from a pointer using wrapping arithmetic.
|
||||
///
|
||||
/// `count` is in units of T; e.g., a `count` of 3 represents a pointer
|
||||
/// offset of `3 * size_of::<T>()` bytes.
|
||||
@ -1100,8 +1106,7 @@ pub const fn wrapping_sub(self, count: usize) -> Self
|
||||
self.wrapping_offset((count as isize).wrapping_neg())
|
||||
}
|
||||
|
||||
/// Calculates the offset from a pointer in bytes using wrapping arithmetic.
|
||||
/// (convenience for `.wrapping_offset((count as isize).wrapping_neg())`)
|
||||
/// Subtracts an unsigned offset in bytes from a pointer using wrapping arithmetic.
|
||||
///
|
||||
/// `count` is in units of bytes.
|
||||
///
|
||||
|
@ -344,7 +344,7 @@ pub const fn to_raw_parts(self) -> (*mut (), <T as super::Pointee>::Metadata) {
|
||||
if self.is_null() { None } else { Some(unsafe { &*(self as *const MaybeUninit<T>) }) }
|
||||
}
|
||||
|
||||
/// Adds an offset to a pointer.
|
||||
/// Adds a signed offset to a pointer.
|
||||
///
|
||||
/// `count` is in units of T; e.g., a `count` of 3 represents a pointer
|
||||
/// offset of `3 * size_of::<T>()` bytes.
|
||||
@ -353,7 +353,8 @@ pub const fn to_raw_parts(self) -> (*mut (), <T as super::Pointee>::Metadata) {
|
||||
///
|
||||
/// If any of the following conditions are violated, the result is Undefined Behavior:
|
||||
///
|
||||
/// * The computed offset, `count * size_of::<T>()` bytes, must not overflow `isize`.
|
||||
/// * The offset in bytes, `count * size_of::<T>()`, computed on mathematical integers (without
|
||||
/// "wrapping around"), must fit in an `isize`.
|
||||
///
|
||||
/// * If the computed offset is non-zero, then `self` must be derived from a pointer to some
|
||||
/// [allocated object], and the entire memory range between `self` and the result must be in
|
||||
@ -398,7 +399,7 @@ pub const fn to_raw_parts(self) -> (*mut (), <T as super::Pointee>::Metadata) {
|
||||
unsafe { intrinsics::offset(self, count) }
|
||||
}
|
||||
|
||||
/// Calculates the offset from a pointer in bytes.
|
||||
/// Adds a signed offset in bytes to a pointer.
|
||||
///
|
||||
/// `count` is in units of **bytes**.
|
||||
///
|
||||
@ -418,7 +419,8 @@ pub const fn to_raw_parts(self) -> (*mut (), <T as super::Pointee>::Metadata) {
|
||||
unsafe { self.cast::<u8>().offset(count).with_metadata_of(self) }
|
||||
}
|
||||
|
||||
/// Calculates the offset from a pointer using wrapping arithmetic.
|
||||
/// Adds a signed offset to a pointer using wrapping arithmetic.
|
||||
///
|
||||
/// `count` is in units of T; e.g., a `count` of 3 represents a pointer
|
||||
/// offset of `3 * size_of::<T>()` bytes.
|
||||
///
|
||||
@ -477,7 +479,7 @@ pub const fn wrapping_offset(self, count: isize) -> *mut T
|
||||
unsafe { intrinsics::arith_offset(self, count) as *mut T }
|
||||
}
|
||||
|
||||
/// Calculates the offset from a pointer in bytes using wrapping arithmetic.
|
||||
/// Adds a signed offset in bytes to a pointer using wrapping arithmetic.
|
||||
///
|
||||
/// `count` is in units of **bytes**.
|
||||
///
|
||||
@ -885,7 +887,11 @@ pub const fn guaranteed_ne(self, other: *mut T) -> Option<bool>
|
||||
unsafe { (self as *const T).sub_ptr(origin) }
|
||||
}
|
||||
|
||||
/// Adds an offset to a pointer (convenience for `.offset(count as isize)`).
|
||||
/// Adds an unsigned offset to a pointer.
|
||||
///
|
||||
/// This can only move the pointer forward (or not move it). If you need to move forward or
|
||||
/// backward depending on the value, then you might want [`offset`](#method.offset) instead
|
||||
/// which takes a signed offset.
|
||||
///
|
||||
/// `count` is in units of T; e.g., a `count` of 3 represents a pointer
|
||||
/// offset of `3 * size_of::<T>()` bytes.
|
||||
@ -894,7 +900,8 @@ pub const fn guaranteed_ne(self, other: *mut T) -> Option<bool>
|
||||
///
|
||||
/// If any of the following conditions are violated, the result is Undefined Behavior:
|
||||
///
|
||||
/// * The computed offset, `count * size_of::<T>()` bytes, must not overflow `isize`.
|
||||
/// * The offset in bytes, `count * size_of::<T>()`, computed on mathematical integers (without
|
||||
/// "wrapping around"), must fit in an `isize`.
|
||||
///
|
||||
/// * If the computed offset is non-zero, then `self` must be derived from a pointer to some
|
||||
/// [allocated object], and the entire memory range between `self` and the result must be in
|
||||
@ -937,7 +944,7 @@ pub const fn guaranteed_ne(self, other: *mut T) -> Option<bool>
|
||||
unsafe { intrinsics::offset(self, count) }
|
||||
}
|
||||
|
||||
/// Calculates the offset from a pointer in bytes (convenience for `.byte_offset(count as isize)`).
|
||||
/// Adds an unsigned offset in bytes to a pointer.
|
||||
///
|
||||
/// `count` is in units of bytes.
|
||||
///
|
||||
@ -957,8 +964,11 @@ pub const fn guaranteed_ne(self, other: *mut T) -> Option<bool>
|
||||
unsafe { self.cast::<u8>().add(count).with_metadata_of(self) }
|
||||
}
|
||||
|
||||
/// Subtracts an offset from a pointer (convenience for
|
||||
/// `.offset((count as isize).wrapping_neg())`).
|
||||
/// Subtracts an unsigned offset from a pointer.
|
||||
///
|
||||
/// This can only move the pointer backward (or not move it). If you need to move forward or
|
||||
/// backward depending on the value, then you might want [`offset`](#method.offset) instead
|
||||
/// which takes a signed offset.
|
||||
///
|
||||
/// `count` is in units of T; e.g., a `count` of 3 represents a pointer
|
||||
/// offset of `3 * size_of::<T>()` bytes.
|
||||
@ -967,7 +977,8 @@ pub const fn guaranteed_ne(self, other: *mut T) -> Option<bool>
|
||||
///
|
||||
/// If any of the following conditions are violated, the result is Undefined Behavior:
|
||||
///
|
||||
/// * The computed offset, `count * size_of::<T>()` bytes, must not overflow `isize`.
|
||||
/// * The offset in bytes, `count * size_of::<T>()`, computed on mathematical integers (without
|
||||
/// "wrapping around"), must fit in an `isize`.
|
||||
///
|
||||
/// * If the computed offset is non-zero, then `self` must be derived from a pointer to some
|
||||
/// [allocated object], and the entire memory range between `self` and the result must be in
|
||||
@ -1018,8 +1029,7 @@ pub const fn guaranteed_ne(self, other: *mut T) -> Option<bool>
|
||||
}
|
||||
}
|
||||
|
||||
/// Calculates the offset from a pointer in bytes (convenience for
|
||||
/// `.byte_offset((count as isize).wrapping_neg())`).
|
||||
/// Subtracts an unsigned offset in bytes from a pointer.
|
||||
///
|
||||
/// `count` is in units of bytes.
|
||||
///
|
||||
@ -1039,8 +1049,7 @@ pub const fn guaranteed_ne(self, other: *mut T) -> Option<bool>
|
||||
unsafe { self.cast::<u8>().sub(count).with_metadata_of(self) }
|
||||
}
|
||||
|
||||
/// Calculates the offset from a pointer using wrapping arithmetic.
|
||||
/// (convenience for `.wrapping_offset(count as isize)`)
|
||||
/// Adds an unsigned offset to a pointer using wrapping arithmetic.
|
||||
///
|
||||
/// `count` is in units of T; e.g., a `count` of 3 represents a pointer
|
||||
/// offset of `3 * size_of::<T>()` bytes.
|
||||
@ -1099,8 +1108,7 @@ pub const fn wrapping_add(self, count: usize) -> Self
|
||||
self.wrapping_offset(count as isize)
|
||||
}
|
||||
|
||||
/// Calculates the offset from a pointer in bytes using wrapping arithmetic.
|
||||
/// (convenience for `.wrapping_byte_offset(count as isize)`)
|
||||
/// Adds an unsigned offset in bytes to a pointer using wrapping arithmetic.
|
||||
///
|
||||
/// `count` is in units of bytes.
|
||||
///
|
||||
@ -1117,8 +1125,7 @@ pub const fn wrapping_byte_add(self, count: usize) -> Self {
|
||||
self.cast::<u8>().wrapping_add(count).with_metadata_of(self)
|
||||
}
|
||||
|
||||
/// Calculates the offset from a pointer using wrapping arithmetic.
|
||||
/// (convenience for `.wrapping_offset((count as isize).wrapping_neg())`)
|
||||
/// Subtracts an unsigned offset from a pointer using wrapping arithmetic.
|
||||
///
|
||||
/// `count` is in units of T; e.g., a `count` of 3 represents a pointer
|
||||
/// offset of `3 * size_of::<T>()` bytes.
|
||||
@ -1177,8 +1184,7 @@ pub const fn wrapping_sub(self, count: usize) -> Self
|
||||
self.wrapping_offset((count as isize).wrapping_neg())
|
||||
}
|
||||
|
||||
/// Calculates the offset from a pointer in bytes using wrapping arithmetic.
|
||||
/// (convenience for `.wrapping_offset((count as isize).wrapping_neg())`)
|
||||
/// Subtracts an unsigned offset in bytes from a pointer using wrapping arithmetic.
|
||||
///
|
||||
/// `count` is in units of bytes.
|
||||
///
|
||||
|
Loading…
Reference in New Issue
Block a user