]> git.lizzy.rs Git - rust.git/blob - src/operator.rs
Auto merge of #1299 - RalfJung:rustup, r=RalfJung
[rust.git] / src / operator.rs
1 use std::convert::TryFrom;
2
3 use log::trace;
4
5 use rustc_middle::{mir, ty::Ty};
6 use rustc_target::abi::{LayoutOf, Size};
7
8 use crate::*;
9
10 pub trait EvalContextExt<'tcx> {
11     fn binary_ptr_op(
12         &self,
13         bin_op: mir::BinOp,
14         left: ImmTy<'tcx, Tag>,
15         right: ImmTy<'tcx, Tag>,
16     ) -> InterpResult<'tcx, (Scalar<Tag>, bool, Ty<'tcx>)>;
17
18     fn ptr_eq(&self, left: Scalar<Tag>, right: Scalar<Tag>) -> InterpResult<'tcx, bool>;
19
20     fn pointer_offset_inbounds(
21         &self,
22         ptr: Scalar<Tag>,
23         pointee_ty: Ty<'tcx>,
24         offset: i64,
25     ) -> InterpResult<'tcx, Scalar<Tag>>;
26 }
27
28 impl<'mir, 'tcx> EvalContextExt<'tcx> for super::MiriEvalContext<'mir, 'tcx> {
29     fn binary_ptr_op(
30         &self,
31         bin_op: mir::BinOp,
32         left: ImmTy<'tcx, Tag>,
33         right: ImmTy<'tcx, Tag>,
34     ) -> InterpResult<'tcx, (Scalar<Tag>, bool, Ty<'tcx>)> {
35         use rustc_middle::mir::BinOp::*;
36
37         trace!("ptr_op: {:?} {:?} {:?}", *left, bin_op, *right);
38
39         Ok(match bin_op {
40             Eq | Ne => {
41                 // This supports fat pointers.
42                 #[rustfmt::skip]
43                 let eq = match (*left, *right) {
44                     (Immediate::Scalar(left), Immediate::Scalar(right)) => {
45                         self.ptr_eq(left.not_undef()?, right.not_undef()?)?
46                     }
47                     (Immediate::ScalarPair(left1, left2), Immediate::ScalarPair(right1, right2)) => {
48                         self.ptr_eq(left1.not_undef()?, right1.not_undef()?)?
49                             && self.ptr_eq(left2.not_undef()?, right2.not_undef()?)?
50                     }
51                     _ => bug!("Type system should not allow comparing Scalar with ScalarPair"),
52                 };
53                 (Scalar::from_bool(if bin_op == Eq { eq } else { !eq }), false, self.tcx.types.bool)
54             }
55
56             Lt | Le | Gt | Ge => {
57                 // Just compare the integers.
58                 // TODO: Do we really want to *always* do that, even when comparing two live in-bounds pointers?
59                 let left = self.force_bits(left.to_scalar()?, left.layout.size)?;
60                 let right = self.force_bits(right.to_scalar()?, right.layout.size)?;
61                 let res = match bin_op {
62                     Lt => left < right,
63                     Le => left <= right,
64                     Gt => left > right,
65                     Ge => left >= right,
66                     _ => bug!("We already established it has to be one of these operators."),
67                 };
68                 (Scalar::from_bool(res), false, self.tcx.types.bool)
69             }
70
71             Offset => {
72                 let pointee_ty =
73                     left.layout.ty.builtin_deref(true).expect("Offset called on non-ptr type").ty;
74                 let ptr = self.pointer_offset_inbounds(
75                     left.to_scalar()?,
76                     pointee_ty,
77                     right.to_scalar()?.to_machine_isize(self)?,
78                 )?;
79                 (ptr, false, left.layout.ty)
80             }
81
82             _ => bug!("Invalid operator on pointers: {:?}", bin_op),
83         })
84     }
85
86     fn ptr_eq(&self, left: Scalar<Tag>, right: Scalar<Tag>) -> InterpResult<'tcx, bool> {
87         let size = self.pointer_size();
88         // Just compare the integers.
89         // TODO: Do we really want to *always* do that, even when comparing two live in-bounds pointers?
90         let left = self.force_bits(left, size)?;
91         let right = self.force_bits(right, size)?;
92         Ok(left == right)
93     }
94
95     /// Raises an error if the offset moves the pointer outside of its allocation.
96     /// For integers, we consider each of them their own tiny allocation of size 0,
97     /// so offset-by-0 is okay for them -- except for NULL, which we rule out entirely.
98     fn pointer_offset_inbounds(
99         &self,
100         ptr: Scalar<Tag>,
101         pointee_ty: Ty<'tcx>,
102         offset: i64,
103     ) -> InterpResult<'tcx, Scalar<Tag>> {
104         let pointee_size = i64::try_from(self.layout_of(pointee_ty)?.size.bytes()).unwrap();
105         let offset = offset.checked_mul(pointee_size).ok_or_else(|| {
106             err_ub_format!("overflow during offset comutation for inbounds pointer arithmetic")
107         })?;
108         // We do this first, to rule out overflows.
109         let offset_ptr = ptr.ptr_signed_offset(offset, self)?;
110         // What we need to check is that starting at `min(ptr, offset_ptr)`,
111         // we could do an access of size `abs(offset)`. Alignment does not matter.
112         let (min_ptr, abs_offset) = if offset >= 0 {
113             (ptr, u64::try_from(offset).unwrap())
114         } else {
115             // Negative offset.
116             // If the negation overflows, the result will be negative so the try_from will fail.
117             (offset_ptr, u64::try_from(-offset).unwrap())
118         };
119         self.memory.check_ptr_access_align(
120             min_ptr,
121             Size::from_bytes(abs_offset),
122             None,
123             CheckInAllocMsg::InboundsTest,
124         )?;
125         // That's it!
126         Ok(offset_ptr)
127     }
128 }