use either::Either; use rustc_abi::Size; use rustc_apfloat::{Float, FloatConvert}; use rustc_middle::mir::NullOp; use rustc_middle::mir::interpret::{InterpResult, PointerArithmetic, Scalar}; use rustc_middle::ty::layout::TyAndLayout; use rustc_middle::ty::{self, FloatTy, ScalarInt, Ty}; use rustc_middle::{bug, mir, span_bug}; use rustc_span::sym; use tracing::trace; use super::{ImmTy, InterpCx, Machine, MemPlaceMeta, interp_ok, throw_ub}; impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> { fn three_way_compare(&self, lhs: T, rhs: T) -> ImmTy<'tcx, M::Provenance> { let res = Ord::cmp(&lhs, &rhs); return ImmTy::from_ordering(res, *self.tcx); } fn binary_char_op(&self, bin_op: mir::BinOp, l: char, r: char) -> ImmTy<'tcx, M::Provenance> { use rustc_middle::mir::BinOp::*; if bin_op == Cmp { return self.three_way_compare(l, r); } let res = match bin_op { Eq => l == r, Ne => l != r, Lt => l < r, Le => l <= r, Gt => l > r, Ge => l >= r, _ => span_bug!(self.cur_span(), "Invalid operation on char: {:?}", bin_op), }; ImmTy::from_bool(res, *self.tcx) } fn binary_bool_op(&self, bin_op: mir::BinOp, l: bool, r: bool) -> ImmTy<'tcx, M::Provenance> { use rustc_middle::mir::BinOp::*; let res = match bin_op { Eq => l == r, Ne => l != r, Lt => l < r, Le => l <= r, Gt => l > r, Ge => l >= r, BitAnd => l & r, BitOr => l | r, BitXor => l ^ r, _ => span_bug!(self.cur_span(), "Invalid operation on bool: {:?}", bin_op), }; ImmTy::from_bool(res, *self.tcx) } fn binary_float_op + Into>>( &self, bin_op: mir::BinOp, layout: TyAndLayout<'tcx>, l: F, r: F, ) -> ImmTy<'tcx, M::Provenance> { use rustc_middle::mir::BinOp::*; // Performs appropriate non-deterministic adjustments of NaN results. let adjust_nan = |f: F| -> F { self.adjust_nan(f, &[l, r]) }; match bin_op { Eq => ImmTy::from_bool(l == r, *self.tcx), Ne => ImmTy::from_bool(l != r, *self.tcx), Lt => ImmTy::from_bool(l < r, *self.tcx), Le => ImmTy::from_bool(l <= r, *self.tcx), Gt => ImmTy::from_bool(l > r, *self.tcx), Ge => ImmTy::from_bool(l >= r, *self.tcx), Add => ImmTy::from_scalar(adjust_nan((l + r).value).into(), layout), Sub => ImmTy::from_scalar(adjust_nan((l - r).value).into(), layout), Mul => ImmTy::from_scalar(adjust_nan((l * r).value).into(), layout), Div => ImmTy::from_scalar(adjust_nan((l / r).value).into(), layout), Rem => ImmTy::from_scalar(adjust_nan((l % r).value).into(), layout), _ => span_bug!(self.cur_span(), "invalid float op: `{:?}`", bin_op), } } fn binary_int_op( &self, bin_op: mir::BinOp, left: &ImmTy<'tcx, M::Provenance>, right: &ImmTy<'tcx, M::Provenance>, ) -> InterpResult<'tcx, ImmTy<'tcx, M::Provenance>> { use rustc_middle::mir::BinOp::*; // This checks the size, so that we can just assert it below. let l = left.to_scalar_int()?; let r = right.to_scalar_int()?; // Prepare to convert the values to signed or unsigned form. let l_signed = || l.to_int(left.layout.size); let l_unsigned = || l.to_uint(left.layout.size); let r_signed = || r.to_int(right.layout.size); let r_unsigned = || r.to_uint(right.layout.size); let throw_ub_on_overflow = match bin_op { AddUnchecked => Some(sym::unchecked_add), SubUnchecked => Some(sym::unchecked_sub), MulUnchecked => Some(sym::unchecked_mul), ShlUnchecked => Some(sym::unchecked_shl), ShrUnchecked => Some(sym::unchecked_shr), _ => None, }; let with_overflow = bin_op.is_overflowing(); // Shift ops can have an RHS with a different numeric type. if matches!(bin_op, Shl | ShlUnchecked | Shr | ShrUnchecked) { let l_bits = left.layout.size.bits(); // Compute the equivalent shift modulo `size` that is in the range `0..size`. (This is // the one MIR operator that does *not* directly map to a single LLVM operation.) let (shift_amount, overflow) = if right.layout.backend_repr.is_signed() { let shift_amount = r_signed(); let rem = shift_amount.rem_euclid(l_bits.into()); // `rem` is guaranteed positive, so the `unwrap` cannot fail (u128::try_from(rem).unwrap(), rem != shift_amount) } else { let shift_amount = r_unsigned(); let rem = shift_amount.rem_euclid(l_bits.into()); (rem, rem != shift_amount) }; let shift_amount = u32::try_from(shift_amount).unwrap(); // we brought this in the range `0..size` so this will always fit // Compute the shifted result. let result = if left.layout.backend_repr.is_signed() { let l = l_signed(); let result = match bin_op { Shl | ShlUnchecked => l.checked_shl(shift_amount).unwrap(), Shr | ShrUnchecked => l.checked_shr(shift_amount).unwrap(), _ => bug!(), }; ScalarInt::truncate_from_int(result, left.layout.size).0 } else { let l = l_unsigned(); let result = match bin_op { Shl | ShlUnchecked => l.checked_shl(shift_amount).unwrap(), Shr | ShrUnchecked => l.checked_shr(shift_amount).unwrap(), _ => bug!(), }; ScalarInt::truncate_from_uint(result, left.layout.size).0 }; if overflow && let Some(intrinsic) = throw_ub_on_overflow { throw_ub!(ShiftOverflow { intrinsic, shift_amount: if right.layout.backend_repr.is_signed() { Either::Right(r_signed()) } else { Either::Left(r_unsigned()) } }); } return interp_ok(ImmTy::from_scalar_int(result, left.layout)); } // For the remaining ops, the types must be the same on both sides if left.layout.ty != right.layout.ty { span_bug!( self.cur_span(), "invalid asymmetric binary op {bin_op:?}: {l:?} ({l_ty}), {r:?} ({r_ty})", l_ty = left.layout.ty, r_ty = right.layout.ty, ) } let size = left.layout.size; // Operations that need special treatment for signed integers if left.layout.backend_repr.is_signed() { let op: Option bool> = match bin_op { Lt => Some(i128::lt), Le => Some(i128::le), Gt => Some(i128::gt), Ge => Some(i128::ge), _ => None, }; if let Some(op) = op { return interp_ok(ImmTy::from_bool(op(&l_signed(), &r_signed()), *self.tcx)); } if bin_op == Cmp { return interp_ok(self.three_way_compare(l_signed(), r_signed())); } let op: Option (i128, bool)> = match bin_op { Div if r.is_null() => throw_ub!(DivisionByZero), Rem if r.is_null() => throw_ub!(RemainderByZero), Div => Some(i128::overflowing_div), Rem => Some(i128::overflowing_rem), Add | AddUnchecked | AddWithOverflow => Some(i128::overflowing_add), Sub | SubUnchecked | SubWithOverflow => Some(i128::overflowing_sub), Mul | MulUnchecked | MulWithOverflow => Some(i128::overflowing_mul), _ => None, }; if let Some(op) = op { let l = l_signed(); let r = r_signed(); // We need a special check for overflowing Rem and Div since they are *UB* // on overflow, which can happen with "int_min $OP -1". if matches!(bin_op, Rem | Div) { if l == size.signed_int_min() && r == -1 { if bin_op == Rem { throw_ub!(RemainderOverflow) } else { throw_ub!(DivisionOverflow) } } } let (result, oflo) = op(l, r); // This may be out-of-bounds for the result type, so we have to truncate. // If that truncation loses any information, we have an overflow. let (result, lossy) = ScalarInt::truncate_from_int(result, left.layout.size); let overflow = oflo || lossy; if overflow && let Some(intrinsic) = throw_ub_on_overflow { throw_ub!(ArithOverflow { intrinsic }); } let res = ImmTy::from_scalar_int(result, left.layout); return interp_ok(if with_overflow { let overflow = ImmTy::from_bool(overflow, *self.tcx); ImmTy::from_pair(res, overflow, self) } else { res }); } } // From here on it's okay to treat everything as unsigned. let l = l_unsigned(); let r = r_unsigned(); if bin_op == Cmp { return interp_ok(self.three_way_compare(l, r)); } interp_ok(match bin_op { Eq => ImmTy::from_bool(l == r, *self.tcx), Ne => ImmTy::from_bool(l != r, *self.tcx), Lt => ImmTy::from_bool(l < r, *self.tcx), Le => ImmTy::from_bool(l <= r, *self.tcx), Gt => ImmTy::from_bool(l > r, *self.tcx), Ge => ImmTy::from_bool(l >= r, *self.tcx), BitOr => ImmTy::from_uint(l | r, left.layout), BitAnd => ImmTy::from_uint(l & r, left.layout), BitXor => ImmTy::from_uint(l ^ r, left.layout), _ => { assert!(!left.layout.backend_repr.is_signed()); let op: fn(u128, u128) -> (u128, bool) = match bin_op { Add | AddUnchecked | AddWithOverflow => u128::overflowing_add, Sub | SubUnchecked | SubWithOverflow => u128::overflowing_sub, Mul | MulUnchecked | MulWithOverflow => u128::overflowing_mul, Div if r == 0 => throw_ub!(DivisionByZero), Rem if r == 0 => throw_ub!(RemainderByZero), Div => u128::overflowing_div, Rem => u128::overflowing_rem, _ => span_bug!( self.cur_span(), "invalid binary op {:?}: {:?}, {:?} (both {})", bin_op, left, right, right.layout.ty, ), }; let (result, oflo) = op(l, r); // Truncate to target type. // If that truncation loses any information, we have an overflow. let (result, lossy) = ScalarInt::truncate_from_uint(result, left.layout.size); let overflow = oflo || lossy; if overflow && let Some(intrinsic) = throw_ub_on_overflow { throw_ub!(ArithOverflow { intrinsic }); } let res = ImmTy::from_scalar_int(result, left.layout); if with_overflow { let overflow = ImmTy::from_bool(overflow, *self.tcx); ImmTy::from_pair(res, overflow, self) } else { res } } }) } /// Computes the total size of this access, `count * elem_size`, /// checking for overflow beyond isize::MAX. pub fn compute_size_in_bytes(&self, elem_size: Size, count: u64) -> Option { // `checked_mul` applies `u64` limits independent of the target pointer size... but the // subsequent check for `max_size_of_val` means we also handle 32bit targets correctly. // (We cannot use `Size::checked_mul` as that enforces `obj_size_bound` as the limit, which // would be wrong here.) elem_size .bytes() .checked_mul(count) .map(Size::from_bytes) .filter(|&total| total <= self.max_size_of_val()) } fn binary_ptr_op( &self, bin_op: mir::BinOp, left: &ImmTy<'tcx, M::Provenance>, right: &ImmTy<'tcx, M::Provenance>, ) -> InterpResult<'tcx, ImmTy<'tcx, M::Provenance>> { use rustc_middle::mir::BinOp::*; match bin_op { // Pointer ops that are always supported. Offset => { let ptr = left.to_scalar().to_pointer(self)?; let pointee_ty = left.layout.ty.builtin_deref(true).unwrap(); let pointee_layout = self.layout_of(pointee_ty)?; assert!(pointee_layout.is_sized()); // The size always fits in `i64` as it can be at most `isize::MAX`. let pointee_size = i64::try_from(pointee_layout.size.bytes()).unwrap(); // This uses the same type as `right`, which can be `isize` or `usize`. // `pointee_size` is guaranteed to fit into both types. let pointee_size = ImmTy::from_int(pointee_size, right.layout); // Multiply element size and element count. let (val, overflowed) = self .binary_op(mir::BinOp::MulWithOverflow, right, &pointee_size)? .to_scalar_pair(); // This must not overflow. if overflowed.to_bool()? { throw_ub!(PointerArithOverflow) } let offset_bytes = val.to_target_isize(self)?; if !right.layout.backend_repr.is_signed() && offset_bytes < 0 { // We were supposed to do an unsigned offset but the result is negative -- this // can only mean that the cast wrapped around. throw_ub!(PointerArithOverflow) } let offset_ptr = self.ptr_offset_inbounds(ptr, offset_bytes)?; interp_ok(ImmTy::from_scalar( Scalar::from_maybe_pointer(offset_ptr, self), left.layout, )) } // Fall back to machine hook so Miri can support more pointer ops. _ => M::binary_ptr_op(self, bin_op, left, right), } } /// Returns the result of the specified operation. /// /// Whether this produces a scalar or a pair depends on the specific `bin_op`. pub fn binary_op( &self, bin_op: mir::BinOp, left: &ImmTy<'tcx, M::Provenance>, right: &ImmTy<'tcx, M::Provenance>, ) -> InterpResult<'tcx, ImmTy<'tcx, M::Provenance>> { trace!( "Running binary op {:?}: {:?} ({}), {:?} ({})", bin_op, *left, left.layout.ty, *right, right.layout.ty ); match left.layout.ty.kind() { ty::Char => { assert_eq!(left.layout.ty, right.layout.ty); let left = left.to_scalar(); let right = right.to_scalar(); interp_ok(self.binary_char_op(bin_op, left.to_char()?, right.to_char()?)) } ty::Bool => { assert_eq!(left.layout.ty, right.layout.ty); let left = left.to_scalar(); let right = right.to_scalar(); interp_ok(self.binary_bool_op(bin_op, left.to_bool()?, right.to_bool()?)) } ty::Float(fty) => { assert_eq!(left.layout.ty, right.layout.ty); let layout = left.layout; let left = left.to_scalar(); let right = right.to_scalar(); interp_ok(match fty { FloatTy::F16 => { self.binary_float_op(bin_op, layout, left.to_f16()?, right.to_f16()?) } FloatTy::F32 => { self.binary_float_op(bin_op, layout, left.to_f32()?, right.to_f32()?) } FloatTy::F64 => { self.binary_float_op(bin_op, layout, left.to_f64()?, right.to_f64()?) } FloatTy::F128 => { self.binary_float_op(bin_op, layout, left.to_f128()?, right.to_f128()?) } }) } _ if left.layout.ty.is_integral() => { // the RHS type can be different, e.g. for shifts -- but it has to be integral, too assert!( right.layout.ty.is_integral(), "Unexpected types for BinOp: {} {:?} {}", left.layout.ty, bin_op, right.layout.ty ); self.binary_int_op(bin_op, left, right) } _ if left.layout.ty.is_any_ptr() => { // The RHS type must be a `pointer` *or an integer type* (for `Offset`). // (Even when both sides are pointers, their type might differ, see issue #91636) assert!( right.layout.ty.is_any_ptr() || right.layout.ty.is_integral(), "Unexpected types for BinOp: {} {:?} {}", left.layout.ty, bin_op, right.layout.ty ); self.binary_ptr_op(bin_op, left, right) } _ => span_bug!( self.cur_span(), "Invalid MIR: bad LHS type for binop: {}", left.layout.ty ), } } /// Returns the result of the specified operation, whether it overflowed, and /// the result type. pub fn unary_op( &self, un_op: mir::UnOp, val: &ImmTy<'tcx, M::Provenance>, ) -> InterpResult<'tcx, ImmTy<'tcx, M::Provenance>> { use rustc_middle::mir::UnOp::*; let layout = val.layout; trace!("Running unary op {:?}: {:?} ({})", un_op, val, layout.ty); match layout.ty.kind() { ty::Bool => { let val = val.to_scalar(); let val = val.to_bool()?; let res = match un_op { Not => !val, _ => span_bug!(self.cur_span(), "Invalid bool op {:?}", un_op), }; interp_ok(ImmTy::from_bool(res, *self.tcx)) } ty::Float(fty) => { let val = val.to_scalar(); if un_op != Neg { span_bug!(self.cur_span(), "Invalid float op {:?}", un_op); } // No NaN adjustment here, `-` is a bitwise operation! let res = match fty { FloatTy::F16 => Scalar::from_f16(-val.to_f16()?), FloatTy::F32 => Scalar::from_f32(-val.to_f32()?), FloatTy::F64 => Scalar::from_f64(-val.to_f64()?), FloatTy::F128 => Scalar::from_f128(-val.to_f128()?), }; interp_ok(ImmTy::from_scalar(res, layout)) } ty::Int(..) => { let val = val.to_scalar().to_int(layout.size)?; let res = match un_op { Not => !val, Neg => val.wrapping_neg(), _ => span_bug!(self.cur_span(), "Invalid integer op {:?}", un_op), }; let res = ScalarInt::truncate_from_int(res, layout.size).0; interp_ok(ImmTy::from_scalar(res.into(), layout)) } ty::Uint(..) => { let val = val.to_scalar().to_uint(layout.size)?; let res = match un_op { Not => !val, _ => span_bug!(self.cur_span(), "Invalid unsigned integer op {:?}", un_op), }; let res = ScalarInt::truncate_from_uint(res, layout.size).0; interp_ok(ImmTy::from_scalar(res.into(), layout)) } ty::RawPtr(..) | ty::Ref(..) => { assert_eq!(un_op, PtrMetadata); let (_, meta) = val.to_scalar_and_meta(); interp_ok(match meta { MemPlaceMeta::Meta(scalar) => { let ty = un_op.ty(*self.tcx, val.layout.ty); let layout = self.layout_of(ty)?; ImmTy::from_scalar(scalar, layout) } MemPlaceMeta::None => { let unit_layout = self.layout_of(self.tcx.types.unit)?; ImmTy::uninit(unit_layout) } }) } _ => { bug!("Unexpected unary op argument {val:?}") } } } pub fn nullary_op( &self, null_op: NullOp<'tcx>, arg_ty: Ty<'tcx>, ) -> InterpResult<'tcx, ImmTy<'tcx, M::Provenance>> { use rustc_middle::mir::NullOp::*; let layout = self.layout_of(arg_ty)?; let usize_layout = || self.layout_of(self.tcx.types.usize).unwrap(); interp_ok(match null_op { SizeOf => { if !layout.is_sized() { span_bug!(self.cur_span(), "unsized type for `NullaryOp::SizeOf`"); } let val = layout.size.bytes(); ImmTy::from_uint(val, usize_layout()) } AlignOf => { if !layout.is_sized() { span_bug!(self.cur_span(), "unsized type for `NullaryOp::AlignOf`"); } let val = layout.align.abi.bytes(); ImmTy::from_uint(val, usize_layout()) } OffsetOf(fields) => { let val = self.tcx.offset_of_subfield(self.typing_env, layout, fields.iter()).bytes(); ImmTy::from_uint(val, usize_layout()) } UbChecks => ImmTy::from_bool(M::ub_checks(self)?, *self.tcx), ContractChecks => ImmTy::from_bool(M::contract_checks(self)?, *self.tcx), }) } }