diff options
| author | Matthias Krüger <matthias.krueger@famsik.de> | 2024-10-01 21:09:19 +0200 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2024-10-01 21:09:19 +0200 |
| commit | 97cdc8ef4426c9940bf83f4c283edc09ea6041e5 (patch) | |
| tree | 4a21303bede3e7daa6739e4fb533da1ea01bae26 | |
| parent | 389a399a501a626ebf891ae0bb076c25e325ae64 (diff) | |
| parent | bc3d0722062b8e208ba9096b8445ef69a896ed7d (diff) | |
| download | rust-97cdc8ef4426c9940bf83f4c283edc09ea6041e5.tar.gz rust-97cdc8ef4426c9940bf83f4c283edc09ea6041e5.zip | |
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
| -rw-r--r-- | library/core/src/intrinsics.rs | 3 | ||||
| -rw-r--r-- | library/core/src/ptr/const_ptr.rs | 47 | ||||
| -rw-r--r-- | library/core/src/ptr/mut_ptr.rs | 48 |
3 files changed, 54 insertions, 44 deletions
diff --git a/library/core/src/intrinsics.rs b/library/core/src/intrinsics.rs index 44cba5aad86..d7a2f1909ca 100644 --- a/library/core/src/intrinsics.rs +++ b/library/core/src/intrinsics.rs @@ -1425,8 +1425,7 @@ extern "rust-intrinsic" { /// /// 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"] diff --git a/library/core/src/ptr/const_ptr.rs b/library/core/src/ptr/const_ptr.rs index 73f0538a49d..332c5e904d7 100644 --- a/library/core/src/ptr/const_ptr.rs +++ b/library/core/src/ptr/const_ptr.rs @@ -346,7 +346,7 @@ impl<T: ?Sized> *const T { 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 @@ impl<T: ?Sized> *const T { /// /// 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 @@ impl<T: ?Sized> *const T { 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 @@ impl<T: ?Sized> *const T { 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 @@ impl<T: ?Sized> *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 @@ impl<T: ?Sized> *const T { } } - /// 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 @@ impl<T: ?Sized> *const T { /// /// 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 @@ impl<T: ?Sized> *const T { 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 @@ impl<T: ?Sized> *const T { 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 @@ impl<T: ?Sized> *const T { /// /// 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 @@ impl<T: ?Sized> *const T { } } - /// 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 @@ impl<T: ?Sized> *const T { 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 @@ impl<T: ?Sized> *const T { 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 @@ impl<T: ?Sized> *const T { 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 @@ impl<T: ?Sized> *const T { 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. /// diff --git a/library/core/src/ptr/mut_ptr.rs b/library/core/src/ptr/mut_ptr.rs index b737c5fdc40..287073497f8 100644 --- a/library/core/src/ptr/mut_ptr.rs +++ b/library/core/src/ptr/mut_ptr.rs @@ -344,7 +344,7 @@ impl<T: ?Sized> *mut T { 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 @@ impl<T: ?Sized> *mut T { /// /// 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 @@ impl<T: ?Sized> *mut T { 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 @@ impl<T: ?Sized> *mut T { 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 @@ impl<T: ?Sized> *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 @@ impl<T: ?Sized> *mut T { 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 @@ impl<T: ?Sized> *mut T { /// /// 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 @@ impl<T: ?Sized> *mut T { 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 @@ impl<T: ?Sized> *mut T { 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 @@ impl<T: ?Sized> *mut T { /// /// 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 @@ impl<T: ?Sized> *mut T { } } - /// 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 @@ impl<T: ?Sized> *mut T { 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 @@ impl<T: ?Sized> *mut T { 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 @@ impl<T: ?Sized> *mut T { 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 @@ impl<T: ?Sized> *mut T { 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. /// |
