1 use super::{AllocId, InterpResult}; 2 3 use rustc_macros::HashStable; 4 use rustc_target::abi::{HasDataLayout, Size}; 5 6 use std::fmt; 7 8 //////////////////////////////////////////////////////////////////////////////// 9 // Pointer arithmetic 10 //////////////////////////////////////////////////////////////////////////////// 11 12 pub trait PointerArithmetic: HasDataLayout { 13 // These are not supposed to be overridden. 14 15 #[inline(always)] pointer_size(&self) -> Size16 fn pointer_size(&self) -> Size { 17 self.data_layout().pointer_size 18 } 19 20 #[inline(always)] max_size_of_val(&self) -> Size21 fn max_size_of_val(&self) -> Size { 22 Size::from_bytes(self.target_isize_max()) 23 } 24 25 #[inline] target_usize_max(&self) -> u6426 fn target_usize_max(&self) -> u64 { 27 self.pointer_size().unsigned_int_max().try_into().unwrap() 28 } 29 30 #[inline] target_isize_min(&self) -> i6431 fn target_isize_min(&self) -> i64 { 32 self.pointer_size().signed_int_min().try_into().unwrap() 33 } 34 35 #[inline] target_isize_max(&self) -> i6436 fn target_isize_max(&self) -> i64 { 37 self.pointer_size().signed_int_max().try_into().unwrap() 38 } 39 40 #[inline] target_usize_to_isize(&self, val: u64) -> i6441 fn target_usize_to_isize(&self, val: u64) -> i64 { 42 let val = val as i64; 43 // Now wrap-around into the machine_isize range. 44 if val > self.target_isize_max() { 45 // This can only happen if the ptr size is < 64, so we know max_usize_plus_1 fits into 46 // i64. 47 debug_assert!(self.pointer_size().bits() < 64); 48 let max_usize_plus_1 = 1u128 << self.pointer_size().bits(); 49 val - i64::try_from(max_usize_plus_1).unwrap() 50 } else { 51 val 52 } 53 } 54 55 /// Helper function: truncate given value-"overflowed flag" pair to pointer size and 56 /// update "overflowed flag" if there was an overflow. 57 /// This should be called by all the other methods before returning! 58 #[inline] truncate_to_ptr(&self, (val, over): (u64, bool)) -> (u64, bool)59 fn truncate_to_ptr(&self, (val, over): (u64, bool)) -> (u64, bool) { 60 let val = u128::from(val); 61 let max_ptr_plus_1 = 1u128 << self.pointer_size().bits(); 62 (u64::try_from(val % max_ptr_plus_1).unwrap(), over || val >= max_ptr_plus_1) 63 } 64 65 #[inline] overflowing_offset(&self, val: u64, i: u64) -> (u64, bool)66 fn overflowing_offset(&self, val: u64, i: u64) -> (u64, bool) { 67 // We do not need to check if i fits in a machine usize. If it doesn't, 68 // either the wrapping_add will wrap or res will not fit in a pointer. 69 let res = val.overflowing_add(i); 70 self.truncate_to_ptr(res) 71 } 72 73 #[inline] overflowing_signed_offset(&self, val: u64, i: i64) -> (u64, bool)74 fn overflowing_signed_offset(&self, val: u64, i: i64) -> (u64, bool) { 75 // We need to make sure that i fits in a machine isize. 76 let n = i.unsigned_abs(); 77 if i >= 0 { 78 let (val, over) = self.overflowing_offset(val, n); 79 (val, over || i > self.target_isize_max()) 80 } else { 81 let res = val.overflowing_sub(n); 82 let (val, over) = self.truncate_to_ptr(res); 83 (val, over || i < self.target_isize_min()) 84 } 85 } 86 87 #[inline] offset<'tcx>(&self, val: u64, i: u64) -> InterpResult<'tcx, u64>88 fn offset<'tcx>(&self, val: u64, i: u64) -> InterpResult<'tcx, u64> { 89 let (res, over) = self.overflowing_offset(val, i); 90 if over { throw_ub!(PointerArithOverflow) } else { Ok(res) } 91 } 92 93 #[inline] signed_offset<'tcx>(&self, val: u64, i: i64) -> InterpResult<'tcx, u64>94 fn signed_offset<'tcx>(&self, val: u64, i: i64) -> InterpResult<'tcx, u64> { 95 let (res, over) = self.overflowing_signed_offset(val, i); 96 if over { throw_ub!(PointerArithOverflow) } else { Ok(res) } 97 } 98 } 99 100 impl<T: HasDataLayout> PointerArithmetic for T {} 101 102 /// This trait abstracts over the kind of provenance that is associated with a `Pointer`. It is 103 /// mostly opaque; the `Machine` trait extends it with some more operations that also have access to 104 /// some global state. 105 /// The `Debug` rendering is used to display bare provenance, and for the default impl of `fmt`. 106 pub trait Provenance: Copy + fmt::Debug { 107 /// Says whether the `offset` field of `Pointer`s with this provenance is the actual physical address. 108 /// - If `false`, the offset *must* be relative. This means the bytes representing a pointer are 109 /// different from what the Abstract Machine prescribes, so the interpreter must prevent any 110 /// operation that would inspect the underlying bytes of a pointer, such as ptr-to-int 111 /// transmutation. A `ReadPointerAsBytes` error will be raised in such situations. 112 /// - If `true`, the interpreter will permit operations to inspect the underlying bytes of a 113 /// pointer, and implement ptr-to-int transmutation by stripping provenance. 114 const OFFSET_IS_ADDR: bool; 115 116 /// Determines how a pointer should be printed. 117 /// 118 /// Default impl is only good for when `OFFSET_IS_ADDR == true`. fmt(ptr: &Pointer<Self>, f: &mut fmt::Formatter<'_>) -> fmt::Result where Self: Sized,119 fn fmt(ptr: &Pointer<Self>, f: &mut fmt::Formatter<'_>) -> fmt::Result 120 where 121 Self: Sized, 122 { 123 assert!(Self::OFFSET_IS_ADDR); 124 let (prov, addr) = ptr.into_parts(); // address is absolute 125 write!(f, "{:#x}", addr.bytes())?; 126 if f.alternate() { 127 write!(f, "{prov:#?}")?; 128 } else { 129 write!(f, "{prov:?}")?; 130 } 131 Ok(()) 132 } 133 134 /// If `OFFSET_IS_ADDR == false`, provenance must always be able to 135 /// identify the allocation this ptr points to (i.e., this must return `Some`). 136 /// Otherwise this function is best-effort (but must agree with `Machine::ptr_get_alloc`). 137 /// (Identifying the offset in that allocation, however, is harder -- use `Memory::ptr_get_alloc` for that.) get_alloc_id(self) -> Option<AllocId>138 fn get_alloc_id(self) -> Option<AllocId>; 139 140 /// Defines the 'join' of provenance: what happens when doing a pointer load and different bytes have different provenance. join(left: Option<Self>, right: Option<Self>) -> Option<Self>141 fn join(left: Option<Self>, right: Option<Self>) -> Option<Self>; 142 } 143 144 impl Provenance for AllocId { 145 // With the `AllocId` as provenance, the `offset` is interpreted *relative to the allocation*, 146 // so ptr-to-int casts are not possible (since we do not know the global physical offset). 147 const OFFSET_IS_ADDR: bool = false; 148 fmt(ptr: &Pointer<Self>, f: &mut fmt::Formatter<'_>) -> fmt::Result149 fn fmt(ptr: &Pointer<Self>, f: &mut fmt::Formatter<'_>) -> fmt::Result { 150 // Forward `alternate` flag to `alloc_id` printing. 151 if f.alternate() { 152 write!(f, "{:#?}", ptr.provenance)?; 153 } else { 154 write!(f, "{:?}", ptr.provenance)?; 155 } 156 // Print offset only if it is non-zero. 157 if ptr.offset.bytes() > 0 { 158 write!(f, "+{:#x}", ptr.offset.bytes())?; 159 } 160 Ok(()) 161 } 162 get_alloc_id(self) -> Option<AllocId>163 fn get_alloc_id(self) -> Option<AllocId> { 164 Some(self) 165 } 166 join(_left: Option<Self>, _right: Option<Self>) -> Option<Self>167 fn join(_left: Option<Self>, _right: Option<Self>) -> Option<Self> { 168 panic!("merging provenance is not supported when `OFFSET_IS_ADDR` is false") 169 } 170 } 171 172 /// Represents a pointer in the Miri engine. 173 /// 174 /// Pointers are "tagged" with provenance information; typically the `AllocId` they belong to. 175 #[derive(Copy, Clone, Eq, PartialEq, TyEncodable, TyDecodable, Hash)] 176 #[derive(HashStable)] 177 pub struct Pointer<Prov = AllocId> { 178 pub(super) offset: Size, // kept private to avoid accidental misinterpretation (meaning depends on `Prov` type) 179 pub provenance: Prov, 180 } 181 182 static_assert_size!(Pointer, 16); 183 // `Option<Prov>` pointers are also passed around quite a bit 184 // (but not stored in permanent machine state). 185 static_assert_size!(Pointer<Option<AllocId>>, 16); 186 187 // We want the `Debug` output to be readable as it is used by `derive(Debug)` for 188 // all the Miri types. 189 impl<Prov: Provenance> fmt::Debug for Pointer<Prov> { fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result190 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 191 Provenance::fmt(self, f) 192 } 193 } 194 195 impl<Prov: Provenance> fmt::Debug for Pointer<Option<Prov>> { fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result196 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 197 match self.provenance { 198 Some(prov) => Provenance::fmt(&Pointer::new(prov, self.offset), f), 199 None => write!(f, "{:#x}[noalloc]", self.offset.bytes()), 200 } 201 } 202 } 203 204 impl<Prov: Provenance> fmt::Display for Pointer<Option<Prov>> { fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result205 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 206 if self.provenance.is_none() && self.offset.bytes() == 0 { 207 write!(f, "null pointer") 208 } else { 209 fmt::Debug::fmt(self, f) 210 } 211 } 212 } 213 214 /// Produces a `Pointer` that points to the beginning of the `Allocation`. 215 impl From<AllocId> for Pointer { 216 #[inline(always)] from(alloc_id: AllocId) -> Self217 fn from(alloc_id: AllocId) -> Self { 218 Pointer::new(alloc_id, Size::ZERO) 219 } 220 } 221 222 impl<Prov> From<Pointer<Prov>> for Pointer<Option<Prov>> { 223 #[inline(always)] from(ptr: Pointer<Prov>) -> Self224 fn from(ptr: Pointer<Prov>) -> Self { 225 let (prov, offset) = ptr.into_parts(); 226 Pointer::new(Some(prov), offset) 227 } 228 } 229 230 impl<Prov> Pointer<Option<Prov>> { 231 /// Convert this pointer that *might* have a provenance into a pointer that *definitely* has a 232 /// provenance, or an absolute address. 233 /// 234 /// This is rarely what you want; call `ptr_try_get_alloc_id` instead. into_pointer_or_addr(self) -> Result<Pointer<Prov>, Size>235 pub fn into_pointer_or_addr(self) -> Result<Pointer<Prov>, Size> { 236 match self.provenance { 237 Some(prov) => Ok(Pointer::new(prov, self.offset)), 238 None => Err(self.offset), 239 } 240 } 241 242 /// Returns the absolute address the pointer points to. 243 /// Only works if Prov::OFFSET_IS_ADDR is true! addr(self) -> Size where Prov: Provenance,244 pub fn addr(self) -> Size 245 where 246 Prov: Provenance, 247 { 248 assert!(Prov::OFFSET_IS_ADDR); 249 self.offset 250 } 251 } 252 253 impl<Prov> Pointer<Option<Prov>> { 254 /// Creates a pointer to the given address, with invalid provenance (i.e., cannot be used for 255 /// any memory access). 256 #[inline(always)] from_addr_invalid(addr: u64) -> Self257 pub fn from_addr_invalid(addr: u64) -> Self { 258 Pointer { provenance: None, offset: Size::from_bytes(addr) } 259 } 260 261 #[inline(always)] null() -> Self262 pub fn null() -> Self { 263 Pointer::from_addr_invalid(0) 264 } 265 } 266 267 impl<'tcx, Prov> Pointer<Prov> { 268 #[inline(always)] new(provenance: Prov, offset: Size) -> Self269 pub fn new(provenance: Prov, offset: Size) -> Self { 270 Pointer { provenance, offset } 271 } 272 273 /// Obtain the constituents of this pointer. Not that the meaning of the offset depends on the type `Prov`! 274 /// This function must only be used in the implementation of `Machine::ptr_get_alloc`, 275 /// and when a `Pointer` is taken apart to be stored efficiently in an `Allocation`. 276 #[inline(always)] into_parts(self) -> (Prov, Size)277 pub fn into_parts(self) -> (Prov, Size) { 278 (self.provenance, self.offset) 279 } 280 map_provenance(self, f: impl FnOnce(Prov) -> Prov) -> Self281 pub fn map_provenance(self, f: impl FnOnce(Prov) -> Prov) -> Self { 282 Pointer { provenance: f(self.provenance), ..self } 283 } 284 285 #[inline] offset(self, i: Size, cx: &impl HasDataLayout) -> InterpResult<'tcx, Self>286 pub fn offset(self, i: Size, cx: &impl HasDataLayout) -> InterpResult<'tcx, Self> { 287 Ok(Pointer { 288 offset: Size::from_bytes(cx.data_layout().offset(self.offset.bytes(), i.bytes())?), 289 ..self 290 }) 291 } 292 293 #[inline] overflowing_offset(self, i: Size, cx: &impl HasDataLayout) -> (Self, bool)294 pub fn overflowing_offset(self, i: Size, cx: &impl HasDataLayout) -> (Self, bool) { 295 let (res, over) = cx.data_layout().overflowing_offset(self.offset.bytes(), i.bytes()); 296 let ptr = Pointer { offset: Size::from_bytes(res), ..self }; 297 (ptr, over) 298 } 299 300 #[inline(always)] wrapping_offset(self, i: Size, cx: &impl HasDataLayout) -> Self301 pub fn wrapping_offset(self, i: Size, cx: &impl HasDataLayout) -> Self { 302 self.overflowing_offset(i, cx).0 303 } 304 305 #[inline] signed_offset(self, i: i64, cx: &impl HasDataLayout) -> InterpResult<'tcx, Self>306 pub fn signed_offset(self, i: i64, cx: &impl HasDataLayout) -> InterpResult<'tcx, Self> { 307 Ok(Pointer { 308 offset: Size::from_bytes(cx.data_layout().signed_offset(self.offset.bytes(), i)?), 309 ..self 310 }) 311 } 312 313 #[inline] overflowing_signed_offset(self, i: i64, cx: &impl HasDataLayout) -> (Self, bool)314 pub fn overflowing_signed_offset(self, i: i64, cx: &impl HasDataLayout) -> (Self, bool) { 315 let (res, over) = cx.data_layout().overflowing_signed_offset(self.offset.bytes(), i); 316 let ptr = Pointer { offset: Size::from_bytes(res), ..self }; 317 (ptr, over) 318 } 319 320 #[inline(always)] wrapping_signed_offset(self, i: i64, cx: &impl HasDataLayout) -> Self321 pub fn wrapping_signed_offset(self, i: i64, cx: &impl HasDataLayout) -> Self { 322 self.overflowing_signed_offset(i, cx).0 323 } 324 } 325