]> git.lizzy.rs Git - rust.git/blob - compiler/rustc_middle/src/mir/interpret/pointer.rs
Rollup merge of #106971 - oli-obk:tait_error, r=davidtwco
[rust.git] / compiler / rustc_middle / src / mir / interpret / pointer.rs
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)]
16     fn pointer_size(&self) -> Size {
17         self.data_layout().pointer_size
18     }
19
20     #[inline(always)]
21     fn max_size_of_val(&self) -> Size {
22         Size::from_bytes(self.machine_isize_max())
23     }
24
25     #[inline]
26     fn machine_usize_max(&self) -> u64 {
27         self.pointer_size().unsigned_int_max().try_into().unwrap()
28     }
29
30     #[inline]
31     fn machine_isize_min(&self) -> i64 {
32         self.pointer_size().signed_int_min().try_into().unwrap()
33     }
34
35     #[inline]
36     fn machine_isize_max(&self) -> i64 {
37         self.pointer_size().signed_int_max().try_into().unwrap()
38     }
39
40     #[inline]
41     fn machine_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.machine_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]
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]
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]
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.machine_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.machine_isize_min())
84         }
85     }
86
87     #[inline]
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]
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 distplay 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`.
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.)
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.
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
149     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
163     fn get_alloc_id(self) -> Option<AllocId> {
164         Some(self)
165     }
166
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> {
190     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>> {
196     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>> {
205     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)]
217     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)]
224     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.
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!
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     #[inline(always)]
255     pub fn from_addr(addr: u64) -> Self {
256         Pointer { provenance: None, offset: Size::from_bytes(addr) }
257     }
258
259     #[inline(always)]
260     pub fn null() -> Self {
261         Pointer::from_addr(0)
262     }
263 }
264
265 impl<'tcx, Prov> Pointer<Prov> {
266     #[inline(always)]
267     pub fn new(provenance: Prov, offset: Size) -> Self {
268         Pointer { provenance, offset }
269     }
270
271     /// Obtain the constituents of this pointer. Not that the meaning of the offset depends on the type `Prov`!
272     /// This function must only be used in the implementation of `Machine::ptr_get_alloc`,
273     /// and when a `Pointer` is taken apart to be stored efficiently in an `Allocation`.
274     #[inline(always)]
275     pub fn into_parts(self) -> (Prov, Size) {
276         (self.provenance, self.offset)
277     }
278
279     pub fn map_provenance(self, f: impl FnOnce(Prov) -> Prov) -> Self {
280         Pointer { provenance: f(self.provenance), ..self }
281     }
282
283     #[inline]
284     pub fn offset(self, i: Size, cx: &impl HasDataLayout) -> InterpResult<'tcx, Self> {
285         Ok(Pointer {
286             offset: Size::from_bytes(cx.data_layout().offset(self.offset.bytes(), i.bytes())?),
287             ..self
288         })
289     }
290
291     #[inline]
292     pub fn overflowing_offset(self, i: Size, cx: &impl HasDataLayout) -> (Self, bool) {
293         let (res, over) = cx.data_layout().overflowing_offset(self.offset.bytes(), i.bytes());
294         let ptr = Pointer { offset: Size::from_bytes(res), ..self };
295         (ptr, over)
296     }
297
298     #[inline(always)]
299     pub fn wrapping_offset(self, i: Size, cx: &impl HasDataLayout) -> Self {
300         self.overflowing_offset(i, cx).0
301     }
302
303     #[inline]
304     pub fn signed_offset(self, i: i64, cx: &impl HasDataLayout) -> InterpResult<'tcx, Self> {
305         Ok(Pointer {
306             offset: Size::from_bytes(cx.data_layout().signed_offset(self.offset.bytes(), i)?),
307             ..self
308         })
309     }
310
311     #[inline]
312     pub fn overflowing_signed_offset(self, i: i64, cx: &impl HasDataLayout) -> (Self, bool) {
313         let (res, over) = cx.data_layout().overflowing_signed_offset(self.offset.bytes(), i);
314         let ptr = Pointer { offset: Size::from_bytes(res), ..self };
315         (ptr, over)
316     }
317
318     #[inline(always)]
319     pub fn wrapping_signed_offset(self, i: i64, cx: &impl HasDataLayout) -> Self {
320         self.overflowing_signed_offset(i, cx).0
321     }
322 }