- // These need both to be pointer, and fail if they are not in the same location
- Lt | Le | Gt | Ge | Sub if left.is_ptr() && right.is_ptr() => {
- let left = left.to_ptr().expect("we checked is_ptr");
- let right = right.to_ptr().expect("we checked is_ptr");
- if left.alloc_id == right.alloc_id {
- let res = match bin_op {
- Lt => left.offset < right.offset,
- Le => left.offset <= right.offset,
- Gt => left.offset > right.offset,
- Ge => left.offset >= right.offset,
- Sub => {
- // subtract the offsets
- let left_offset = Scalar::from_uint(left.offset.bytes(), self.memory().pointer_size());
- let right_offset = Scalar::from_uint(right.offset.bytes(), self.memory().pointer_size());
- let layout = self.layout_of(self.tcx.types.usize)?;
- return self.binary_op(
- Sub,
- ImmTy::from_scalar(left_offset, layout),
- ImmTy::from_scalar(right_offset, layout),
- )
- }
- _ => bug!("We already established it has to be one of these operators."),
- };
- Ok((Scalar::from_bool(res), false))
- } else {
- // Both are pointers, but from different allocations.
- err!(InvalidPointerMath)
- }
- }
- Gt | Ge if left.is_ptr() && right.is_bits() => {
- // "ptr >[=] integer" can be tested if the integer is small enough.
- let left = left.to_ptr().expect("we checked is_ptr");
- let right = right.to_bits(self.memory().pointer_size()).expect("we checked is_bits");
- let (_alloc_size, alloc_align) = self.memory()
- .get_size_and_align(left.alloc_id, InboundsCheck::MaybeDead)
- .expect("determining size+align of dead ptr cannot fail");
- let min_ptr_val = u128::from(alloc_align.bytes()) + u128::from(left.offset.bytes());
- let result = match bin_op {
- Gt => min_ptr_val > right,
- Ge => min_ptr_val >= right,
- _ => bug!(),
- };
- if result {
- // Definitely true!
- Ok((Scalar::from_bool(true), false))
- } else {
- // Sorry, can't tell.
- err!(InvalidPointerMath)
- }
- }
- // These work if the left operand is a pointer, and the right an integer
- Add | BitAnd | Sub | Rem if left.is_ptr() && right.is_bits() => {
- // Cast to i128 is fine as we checked the kind to be ptr-sized
- self.ptr_int_arithmetic(
- bin_op,
- left.to_ptr().expect("we checked is_ptr"),
- right.to_bits(self.memory().pointer_size()).expect("we checked is_bits"),
- right_layout.abi.is_signed(),
- )
- }
- // Commutative operators also work if the integer is on the left
- Add | BitAnd if left.is_bits() && right.is_ptr() => {
- // This is a commutative operation, just swap the operands
- self.ptr_int_arithmetic(
- bin_op,
- right.to_ptr().expect("we checked is_ptr"),
- left.to_bits(self.memory().pointer_size()).expect("we checked is_bits"),
- left_layout.abi.is_signed(),
- )
- }
- // Nothing else works
- _ => err!(InvalidPointerMath),
- }
- }
-
- fn ptr_eq(
- &self,
- left: Scalar<Tag>,
- right: Scalar<Tag>,
- ) -> InterpResult<'tcx, bool> {
- let size = self.pointer_size();
- Ok(match (left, right) {
- (Scalar::Raw { .. }, Scalar::Raw { .. }) =>
- left.to_bits(size)? == right.to_bits(size)?,
- (Scalar::Ptr(left), Scalar::Ptr(right)) => {
- // Comparison illegal if one of them is out-of-bounds, *unless* they
- // are in the same allocation.
- if left.alloc_id == right.alloc_id {
- left.offset == right.offset
- } else {
- // This accepts one-past-the end. Thus, there is still technically
- // some non-determinism that we do not fully rule out when two
- // allocations sit right next to each other. The C/C++ standards are
- // somewhat fuzzy about this case, so pragmatically speaking I think
- // for now this check is "good enough".
- // FIXME: Once we support intptrcast, we could try to fix these holes.
- // Dead allocations in miri cannot overlap with live allocations, but
- // on read hardware this can easily happen. Thus for comparisons we require
- // both pointers to be live.
- self.memory().check_bounds_ptr(left, InboundsCheck::Live, CheckInAllocMsg::InboundsTest)?;
- self.memory().check_bounds_ptr(right, InboundsCheck::Live, CheckInAllocMsg::InboundsTest)?;
- // Two in-bounds pointers, we can compare across allocations.
- left == right
- }
- }
- // Comparing ptr and integer.
- (Scalar::Ptr(ptr), Scalar::Raw { data, size }) |
- (Scalar::Raw { data, size }, Scalar::Ptr(ptr)) => {
- assert_eq!(size as u64, self.pointer_size().bytes());
- let bits = data as u64;
-
- // Case I: Comparing real pointers with "small" integers.
- // Really we should only do this for NULL, but pragmatically speaking on non-bare-metal systems,
- // an allocation will never be at the very bottom of the address space.
- // Such comparisons can arise when comparing empty slices, which sometimes are "fake"
- // integer pointers (okay because the slice is empty) and sometimes point into a
- // real allocation.
- // The most common source of such integer pointers is `NonNull::dangling()`, which
- // equals the type's alignment. i128 might have an alignment of 16 bytes, but few types have
- // alignment 32 or higher, hence the limit of 32.
- // FIXME: Once we support intptrcast, we could try to fix these holes.
- if bits < 32 {
- // Test if the ptr is in-bounds. Then it cannot be NULL.
- // Even dangling pointers cannot be NULL.
- if self.memory().check_bounds_ptr(ptr, InboundsCheck::MaybeDead, CheckInAllocMsg::NullPointerTest).is_ok() {
- return Ok(false);
- }
- }