Skip to content
Snippets Groups Projects
Select Git revision
  • e4dac61fba3cdf3889ffcdd739c747d00ecd8cd9
  • master default protected
2 results

build.rs

Blame
  • Forked from Per Lindgren / e7020e_2021
    Source project has a limited visibility.
    operator.rs 19.30 KiB
    use rustc::mir;
    use rustc::ty::{Ty};
    use rustc_const_math::ConstFloat;
    use syntax::ast::FloatTy;
    use std::cmp::Ordering;
    
    use error::{EvalError, EvalResult};
    use eval_context::{EvalContext, ValTy};
    use lvalue::Lvalue;
    use memory::{MemoryPointer, PointerOffset, SByte};
    use value::{
        PrimVal,
        PrimValKind,
        Value,
        bytes_to_f32,
        bytes_to_f64,
        f32_to_bytes,
        f64_to_bytes,
        bytes_to_bool,
    };
    
    impl<'a, 'tcx> EvalContext<'a, 'tcx> {
        fn binop_with_overflow(
            &mut self,
            op: mir::BinOp,
            left: &mir::Operand<'tcx>,
            right: &mir::Operand<'tcx>,
        ) -> EvalResult<'tcx, (PrimVal, bool)> {
            let left_ty    = self.operand_ty(left);
            let right_ty   = self.operand_ty(right);
            let left_val   = self.eval_operand_to_primval(left)?;
            let right_val  = self.eval_operand_to_primval(right)?;
            self.binary_op(op, left_val, left_ty, right_val, right_ty)
        }
    
        /// Applies the binary operation `op` to the two operands and writes a tuple of the result
        /// and a boolean signifying the potential overflow to the destination.
        pub(super) fn intrinsic_with_overflow(
            &mut self,
            op: mir::BinOp,
            left: &mir::Operand<'tcx>,
            right: &mir::Operand<'tcx>,
            dest: Lvalue<'tcx>,
            dest_ty: Ty<'tcx>,
        ) -> EvalResult<'tcx> {
            let (val, overflowed) = self.binop_with_overflow(op, left, right)?;
            let val = Value::ByValPair(val, PrimVal::from_bool(overflowed));
            self.write_value(ValTy { value: val, ty: dest_ty}, dest)
        }
    
        /// Applies the binary operation `op` to the arguments and writes the result to the
        /// destination. Returns `true` if the operation overflowed.
        pub(super) fn intrinsic_overflowing(
            &mut self,
            op: mir::BinOp,
            left: &mir::Operand<'tcx>,
            right: &mir::Operand<'tcx>,
            dest: Lvalue<'tcx>,
            dest_ty: Ty<'tcx>,
        ) -> EvalResult<'tcx, bool> {
            let (val, overflowed) = self.binop_with_overflow(op, left, right)?;
            self.write_primval(dest, val, dest_ty)?;
            Ok(overflowed)
        }
    }
    
    macro_rules! overflow {
        ($op:ident, $l:expr, $r:expr) => ({
            let (val, overflowed) = $l.$op($r);
            let primval = PrimVal::Bytes(val as u128);
            Ok((primval, overflowed))
        })
    }
    
    macro_rules! int_arithmetic {
        ($kind:expr, $int_op:ident, $l:expr, $r:expr) => ({
            let l = $l;
            let r = $r;
            match $kind {
                I8  => overflow!($int_op, l as i8,  r as i8),
                I16 => overflow!($int_op, l as i16, r as i16),
                I32 => overflow!($int_op, l as i32, r as i32),
                I64 => overflow!($int_op, l as i64, r as i64),
                I128 => overflow!($int_op, l as i128, r as i128),
                U8  => overflow!($int_op, l as u8,  r as u8),
                U16 => overflow!($int_op, l as u16, r as u16),
                U32 => overflow!($int_op, l as u32, r as u32),
                U64 => overflow!($int_op, l as u64, r as u64),
                U128 => overflow!($int_op, l as u128, r as u128),
                _ => bug!("int_arithmetic should only be called on int primvals"),
            }
        })
    }
    
    macro_rules! int_shift {
        ($kind:expr, $int_op:ident, $l:expr, $r:expr) => ({
            let l = $l;
            let r = $r;
            match $kind {
                I8  => overflow!($int_op, l as i8,  r),
                I16 => overflow!($int_op, l as i16, r),
                I32 => overflow!($int_op, l as i32, r),
                I64 => overflow!($int_op, l as i64, r),
                I128 => overflow!($int_op, l as i128, r),
                U8  => overflow!($int_op, l as u8,  r),
                U16 => overflow!($int_op, l as u16, r),
                U32 => overflow!($int_op, l as u32, r),
                U64 => overflow!($int_op, l as u64, r),
                U128 => overflow!($int_op, l as u128, r),
                _ => bug!("int_shift should only be called on int primvals"),
            }
        })
    }
    
    impl<'a, 'tcx> EvalContext<'a, 'tcx> {
    
        /// Returns the result of the specified operation and whether it overflowed.
        pub fn binary_op(
            &mut self,
            bin_op: mir::BinOp,
            left: PrimVal,
            left_ty: Ty<'tcx>,
            right: PrimVal,
            right_ty: Ty<'tcx>,
        ) -> EvalResult<'tcx, (PrimVal, bool)> {
            use rustc::mir::BinOp::*;
            use value::PrimValKind::*;
    
            let left_kind  = self.ty_to_primval_kind(left_ty)?;
            let right_kind = self.ty_to_primval_kind(right_ty)?;
    
            // I: Handle operations that support pointers
            let usize = PrimValKind::from_uint_size(self.memory.pointer_size());
            let isize = PrimValKind::from_int_size(self.memory.pointer_size());
    
            if !left.is_concrete() || !right.is_concrete() {
                return self.abstract_binary_op(bin_op,
                                               left, left_kind,
                                               right, right_kind);
            }
    
            match (left, right) {
                (PrimVal::Ptr(lptr), PrimVal::Ptr(rptr)) => {
                    if !lptr.has_concrete_offset() || !rptr.has_concrete_offset() {
                        return self.abstract_ptr_ops(bin_op, lptr, left_kind, rptr, right_kind);
                    }
                }
                _ => {
    
                }
            }
    
            if !left_kind.is_float() && !right_kind.is_float() {
                match bin_op {
                    Offset if left_kind == Ptr && right_kind == usize => {
                        let pointee_ty = left_ty.builtin_deref(true).expect("Offset called on non-ptr type").ty;
                        let ptr = self.pointer_offset(left, pointee_ty, right.to_bytes()? as i64)?;
                        return Ok((ptr, false));
                    },
                    // These work on anything
                    Eq if left_kind == right_kind => {
                        let result = match (left, right) {
                            (PrimVal::Bytes(left), PrimVal::Bytes(right)) => left == right,
                            (PrimVal::Ptr(left), PrimVal::Ptr(right)) => left == right,
                            (PrimVal::Undef, _) | (_, PrimVal::Undef) => return Err(EvalError::ReadUndefBytes),
                            _ => false,
                        };
                        return Ok((PrimVal::from_bool(result), false));
                    }
                    Ne if left_kind == right_kind => {
                        let result = match (left, right) {
                            (PrimVal::Bytes(left), PrimVal::Bytes(right)) => left != right,
                            (PrimVal::Ptr(left), PrimVal::Ptr(right)) => left != right,
                            (PrimVal::Undef, _) | (_, PrimVal::Undef) => return Err(EvalError::ReadUndefBytes),
                            _ => true,
                        };
                        return Ok((PrimVal::from_bool(result), false));
                    }
                    // These need both pointers to be in the same allocation
                    Lt | Le | Gt | Ge | Sub
                    if left_kind == right_kind
                    && (left_kind == Ptr || left_kind == usize || left_kind == isize)
                    && left.is_ptr() && right.is_ptr() => {
                        let left = left.to_ptr()?;
                        let right = right.to_ptr()?;
                        let (left_offset, right_offset) = match (left.offset, right.offset) {
                            (PointerOffset::Concrete(l), PointerOffset::Concrete(r)) => (l, r),
                            _ => unimplemented!(),
                        };
    
    
                        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 => {
                                    return int_arithmetic!(left_kind, overflowing_sub, left_offset, right_offset);
                                }
                                _ => bug!("We already established it has to be one of these operators."),
                            };
                            return Ok((PrimVal::from_bool(res), false));
                        } else {
                            // Both are pointers, but from different allocations.
                            return Err(EvalError::InvalidPointerMath);
                        }
                    }
                    // These work if one operand is a pointer, the other an integer
                    Add | BitAnd | Sub
                    if left_kind == right_kind && (left_kind == usize || left_kind == isize)
                    && left.is_ptr() && right.is_bytes() => {
                        // Cast to i128 is fine as we checked the kind to be ptr-sized
                        return self.ptr_int_arithmetic(bin_op, left.to_ptr()?, right.to_bytes()? as i128, left_kind == isize);
                    }
                    Add | BitAnd
                    if left_kind == right_kind && (left_kind == usize || left_kind == isize)
                    && left.is_bytes() && right.is_ptr() => {
                        // This is a commutative operation, just swap the operands
                        return self.ptr_int_arithmetic(bin_op, right.to_ptr()?, left.to_bytes()? as i128, left_kind == isize);
                    }
                    _ => {}
                }
            }
    
            // II: From now on, everything must be bytes, no pointers
            let l = left.to_bytes()?;
            let r = right.to_bytes()?;
    
            // These ops can have an RHS with a different numeric type.
            if bin_op == Shl || bin_op == Shr {
                return match bin_op {
                    Shl => int_shift!(left_kind, overflowing_shl, l, r as u32),
                    Shr => int_shift!(left_kind, overflowing_shr, l, r as u32),
                    _ => bug!("it has already been checked that this is a shift op"),
                };
            }
    
            if left_kind != right_kind {
                let msg = format!("unimplemented binary op: {:?}, {:?}, {:?}", left, right, bin_op);
                return Err(EvalError::Unimplemented(msg));
            }
    
            let float_op = |op, l, r, ty| {
                let l = ConstFloat {
                    bits: l,
                    ty,
                };
                let r = ConstFloat {
                    bits: r,
                    ty,
                };
                match op {
                    Eq => PrimVal::from_bool(l.try_cmp(r).unwrap() == Ordering::Equal),
                    Ne => PrimVal::from_bool(l.try_cmp(r).unwrap() != Ordering::Equal),
                    Lt => PrimVal::from_bool(l.try_cmp(r).unwrap() == Ordering::Less),
                    Le => PrimVal::from_bool(l.try_cmp(r).unwrap() != Ordering::Greater),
                    Gt => PrimVal::from_bool(l.try_cmp(r).unwrap() == Ordering::Greater),
                    Ge => PrimVal::from_bool(l.try_cmp(r).unwrap() != Ordering::Less),
                    Add => PrimVal::Bytes((l + r).unwrap().bits),
                    Sub => PrimVal::Bytes((l - r).unwrap().bits),
                    Mul => PrimVal::Bytes((l * r).unwrap().bits),
                    Div => PrimVal::Bytes((l / r).unwrap().bits),
                    Rem => PrimVal::Bytes((l % r).unwrap().bits),
                    _ => bug!("invalid float op: `{:?}`", op),
                }
            };
    
            let val = match (bin_op, left_kind) {
                (_, F32) => float_op(bin_op, l, r, FloatTy::F32),
                (_, F64) => float_op(bin_op, l, r, FloatTy::F64),
    
                (Eq, _) => PrimVal::from_bool(l == r),
                (Ne, _) => PrimVal::from_bool(l != r),
                (Lt, k) if k.is_signed_int() => PrimVal::from_bool((l as i128) < (r as i128)),
                (Lt, _) => PrimVal::from_bool(l <  r),
                (Le, k) if k.is_signed_int() => PrimVal::from_bool((l as i128) <= (r as i128)),
                (Le, _) => PrimVal::from_bool(l <= r),
                (Gt, k) if k.is_signed_int() => PrimVal::from_bool((l as i128) > (r as i128)),
                (Gt, _) => PrimVal::from_bool(l >  r),
                (Ge, k) if k.is_signed_int() => PrimVal::from_bool((l as i128) >= (r as i128)),
                (Ge, _) => PrimVal::from_bool(l >= r),
    
                (BitOr,  _) => PrimVal::Bytes(l | r),
                (BitAnd, _) => PrimVal::Bytes(l & r),
                (BitXor, _) => PrimVal::Bytes(l ^ r),
    
                (Add, k) if k.is_int() => return int_arithmetic!(k, overflowing_add, l, r),
                (Sub, k) if k.is_int() => return int_arithmetic!(k, overflowing_sub, l, r),
                (Mul, k) if k.is_int() => return int_arithmetic!(k, overflowing_mul, l, r),
                (Div, k) if k.is_int() => return int_arithmetic!(k, overflowing_div, l, r),
                (Rem, k) if k.is_int() => return int_arithmetic!(k, overflowing_rem, l, r),
    
                _ => {
                    let msg = format!("unimplemented binary op {:?}: {:?} ({:?}), {:?} ({:?})", bin_op, left, left_kind, right, right_kind);
                    return Err(EvalError::Unimplemented(msg));
                }
            };
    
            Ok((val, false))
        }
    
        fn ptr_int_arithmetic(
            &self,
            bin_op: mir::BinOp,
            left: MemoryPointer,
            right: i128,
            signed: bool,
        ) -> EvalResult<'tcx, (PrimVal, bool)> {
            use rustc::mir::BinOp::*;
    
            fn map_to_primval((res, over) : (MemoryPointer, bool)) -> (PrimVal, bool) {
                (PrimVal::Ptr(res), over)
            }
    
            let left_offset = match left.offset {
                PointerOffset::Concrete(n) => n,
                _ => unimplemented!(),
            };
    
            Ok(match bin_op {
                Sub =>
                    // The only way this can overflow is by underflowing, so signdeness of the right operands does not matter
                    map_to_primval(left.overflowing_signed_offset(-right, self.memory.layout)),
                Add if signed =>
                    map_to_primval(left.overflowing_signed_offset(right, self.memory.layout)),
                Add if !signed =>
                    map_to_primval(left.overflowing_offset(right as u64, self.memory.layout)),
    
                BitAnd if !signed => {
                    let base_mask : u64 = !(self.memory.get(left.alloc_id)?.align - 1);
                    let right = right as u64;
                    if right & base_mask == base_mask {
                        // Case 1: The base address bits are all preserved, i.e., right is all-1 there
                        (PrimVal::Ptr(MemoryPointer::new(left.alloc_id, left_offset & right)), false)
                    } else if right & base_mask == 0 {
                        // Case 2: The base address bits are all taken away, i.e., right is all-0 there
                        (PrimVal::from_u128((left_offset & right) as u128), false)
                    } else {
                        return Err(EvalError::ReadPointerAsBytes);
                    }
                }
    
                _ => {
                    let msg = format!("unimplemented binary op on pointer {:?}: {:?}, {:?} ({})", bin_op, left, right, if signed { "signed" } else { "unsigned" });
                    return Err(EvalError::Unimplemented(msg));
                }
            })
        }
    
        pub fn abstract_binary_op(
            &mut self,
            bin_op: mir::BinOp,
            left: PrimVal,
            left_kind: PrimValKind,
            right: PrimVal,
            mut right_kind: PrimValKind,
        ) -> EvalResult<'tcx, (PrimVal, bool)> {
    
            // These ops can have an RHS with a different numeric type.
            if bin_op == mir::BinOp::Shl || bin_op == mir::BinOp::Shr {
                match (left, right) {
                    (PrimVal::Abstract(abytes), PrimVal::Bytes(rn)) if rn % 8 == 0 => {
                        let num_bytes = (rn / 8) as usize;
                        match bin_op {
                            mir::BinOp::Shl => {
                                let mut buffer = [SByte::Concrete(0); 8];
                                for idx in num_bytes .. 8 {
                                    buffer[idx] = abytes[idx - num_bytes];
                                }
                                return Ok((PrimVal::Abstract(buffer), false));
                            }
                            mir::BinOp::Shr => {
                                if !left_kind.is_signed_int() {
                                    let mut buffer = [SByte::Concrete(0); 8];
                                    for idx in num_bytes .. 8 {
                                        buffer[idx - num_bytes] = abytes[idx];
                                    }
                                    return Ok((PrimVal::Abstract(buffer), false));
                                }
                            }
                            _ => unimplemented!(),
                        }
                    }
                    _ => (),
                }
    
                if right_kind.num_bytes() == left_kind.num_bytes() {
                    right_kind = left_kind;
                } else if right_kind.num_bytes() < left_kind.num_bytes() {
                    right_kind = left_kind;
                } else {
                    if let PrimVal::Bytes(n) = right {
                        if n < 256 {
                            // HACK
                            right_kind = left_kind;
                        } else {
                            unimplemented!()
                        }
                    } else {
                        unimplemented!()
                    }
                }
            }
    
            if left_kind != right_kind {
                let msg = format!("unimplemented binary op: {:?}, {:?}, {:?}", left, right, bin_op);
                return Err(EvalError::Unimplemented(msg));
            }
            Ok((self.memory.constraints.add_binop_constraint(bin_op, left, right, left_kind), false))
        }
    
        fn abstract_ptr_ops(
            &mut self,
            bin_op: mir::BinOp,
            left: MemoryPointer,
            _left_kind: PrimValKind,
            right: MemoryPointer,
            _right_kind: PrimValKind,
        ) -> EvalResult<'tcx, (PrimVal, bool)> {
            use rustc::mir::BinOp::*;
            use value::PrimValKind::*;
            if left.alloc_id != right.alloc_id {
                if let Eq = bin_op {
                    unimplemented!()
                } else {
                    unimplemented!()
                }
            } else {
                let result = self.memory.constraints.add_binop_constraint(
                    bin_op, left.offset.as_primval(), right.offset.as_primval(), U64);
                Ok((result, false))
            }
        }
    
        pub fn unary_op(
            &mut self,
            un_op: mir::UnOp,
            val: PrimVal,
            val_kind: PrimValKind,
        ) -> EvalResult<'tcx, PrimVal> {
            use rustc::mir::UnOp::*;
            use value::PrimValKind::*;
    
            if !val.is_concrete() {
                return
                    Ok(self.memory.constraints.add_unop_constraint(
                        un_op, val, val_kind))
            }
    
            let bytes = val.to_bytes()?;
    
            let result_bytes = match (un_op, val_kind) {
                (Not, Bool) => !bytes_to_bool(bytes) as u128,
    
                (Not, U8)  => !(bytes as u8) as u128,
                (Not, U16) => !(bytes as u16) as u128,
                (Not, U32) => !(bytes as u32) as u128,
                (Not, U64) => !(bytes as u64) as u128,
                (Not, U128) => !bytes,
    
                (Not, I8)  => !(bytes as i8) as u128,
                (Not, I16) => !(bytes as i16) as u128,
                (Not, I32) => !(bytes as i32) as u128,
                (Not, I64) => !(bytes as i64) as u128,
                (Not, I128) => !(bytes as i128) as u128,
    
                (Neg, I8)  => -(bytes as i8) as u128,
                (Neg, I16) => -(bytes as i16) as u128,
                (Neg, I32) => -(bytes as i32) as u128,
                (Neg, I64) => -(bytes as i64) as u128,
                (Neg, I128) => -(bytes as i128) as u128,
    
                (Neg, F32) => f32_to_bytes(-bytes_to_f32(bytes)),
                (Neg, F64) => f64_to_bytes(-bytes_to_f64(bytes)),
    
                _ => {
                    let msg = format!("unimplemented unary op: {:?}, {:?}", un_op, val);
                    return Err(EvalError::Unimplemented(msg));
                }
            };
    
            Ok(PrimVal::Bytes(result_bytes))
        }
    }