From 36505c7b40b461744061c9da49a1c76996d46c4c Mon Sep 17 00:00:00 2001 From: Ralf Jung Date: Mon, 5 Jun 2017 17:11:51 -0700 Subject: [PATCH] fix bitops being accidentally allowed on pointers from the same allocation --- src/operator.rs | 102 +++++++++++++--------- src/value.rs | 8 ++ tests/compile-fail/overflowing-lsh-neg.rs | 16 ++++ tests/compile-fail/ptr_bitops.rs | 7 ++ 4 files changed, 94 insertions(+), 39 deletions(-) create mode 100644 tests/compile-fail/overflowing-lsh-neg.rs create mode 100644 tests/compile-fail/ptr_bitops.rs diff --git a/src/operator.rs b/src/operator.rs index 0109cddb573..b58098e4196 100644 --- a/src/operator.rs +++ b/src/operator.rs @@ -155,57 +155,48 @@ fn normalize(val: PrimVal) -> PrimVal { } let (left, right) = (normalize(left), normalize(right)); + let left_kind = self.ty_to_primval_kind(left_ty)?; + let right_kind = self.ty_to_primval_kind(right_ty)?; + // Offset is handled early, before we dispatch to unrelated_ptr_ops. We have to also catch the case where both arguments *are* convertible to integers. if bin_op == Offset { - let pointee_ty = left_ty.builtin_deref(true, ty::LvaluePreference::NoPreference).expect("Offset called on non-ptr type").ty; - let ptr = self.pointer_offset(left.to_ptr()?, pointee_ty, right.to_bytes()? as i64)?; - return Ok((PrimVal::Ptr(ptr), false)); + if left_kind == Ptr && right_kind == PrimValKind::from_uint_size(self.memory.pointer_size()) { + let pointee_ty = left_ty.builtin_deref(true, ty::LvaluePreference::NoPreference).expect("Offset called on non-ptr type").ty; + let ptr = self.pointer_offset(left.to_ptr()?, pointee_ty, right.to_bytes()? as i64)?; + return Ok((PrimVal::Ptr(ptr), false)); + } else { + bug!("Offset used with wrong type"); + } } let (l, r) = match (left, right) { (PrimVal::Bytes(left_bytes), PrimVal::Bytes(right_bytes)) => (left_bytes, right_bytes), + // One argument is a pointer value -- this is handled separately (PrimVal::Ptr(left_ptr), PrimVal::Ptr(right_ptr)) => { - if left_ptr.alloc_id == right_ptr.alloc_id { - // If the pointers are into the same allocation, fall through to the more general - // match later, which will do comparisons on the pointer offsets. - (left_ptr.offset as u128, right_ptr.offset as u128) - } else { - return Ok((unrelated_ptr_ops(bin_op, left_ptr, right_ptr)?, false)); - } + return self.ptr_ops(bin_op, left_ptr, left_kind, right_ptr, right_kind); + } + (PrimVal::Ptr(ptr), PrimVal::Bytes(bytes)) => { + return self.ptr_ops(bin_op, ptr, left_kind, Pointer::from_int(bytes as u64), right_kind); } - - (PrimVal::Ptr(ptr), PrimVal::Bytes(bytes)) | (PrimVal::Bytes(bytes), PrimVal::Ptr(ptr)) => { - return Ok((unrelated_ptr_ops(bin_op, ptr, Pointer::from_int(bytes as u64))?, false)); + return self.ptr_ops(bin_op, Pointer::from_int(bytes as u64), left_kind, ptr, right_kind); } (PrimVal::Undef, _) | (_, PrimVal::Undef) => return Err(EvalError::ReadUndefBytes), }; - let left_kind = self.ty_to_primval_kind(left_ty)?; - let right_kind = self.ty_to_primval_kind(right_ty)?; - // These ops can have an RHS with a different numeric type. - if bin_op == Shl || bin_op == Shr { + if right_kind.is_int() && (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 bin_op == Offset { - // We permit offset-by-0 in any case. Drop glue actually does this, and it's probably (TM) fine for LLVM. - if left_kind == PrimValKind::Ptr && right_kind.is_int() && r == 0 { - return Ok((PrimVal::Bytes(l), false)); - } else { - let msg = format!("unimplemented Offset: {:?}, {:?}", left, right); - return Err(EvalError::Unimplemented(msg)); - } - } if left_kind != right_kind { - let msg = format!("unimplemented binary op: {:?}, {:?}, {:?}", left, right, bin_op); + let msg = format!("unimplemented binary op {:?}: {:?} ({:?}), {:?} ({:?})", bin_op, left, left_kind, right, right_kind); return Err(EvalError::Unimplemented(msg)); } @@ -258,25 +249,58 @@ fn normalize(val: PrimVal) -> PrimVal { (Rem, k) if k.is_int() => return int_arithmetic!(k, overflowing_rem, l, r), _ => { - let msg = format!("unimplemented binary op: {:?}, {:?}, {:?}", left, right, bin_op); + let msg = format!("unimplemented binary op {:?}: {:?} ({:?}), {:?} ({:?})", bin_op, left, left_kind, right, right_kind); return Err(EvalError::Unimplemented(msg)); } }; Ok((val, false)) } -} -fn unrelated_ptr_ops<'tcx>(bin_op: mir::BinOp, left: Pointer, right: Pointer) -> EvalResult<'tcx, PrimVal> { - use rustc::mir::BinOp::*; - match bin_op { - Eq => Ok(PrimVal::from_bool(false)), - Ne => Ok(PrimVal::from_bool(true)), - Lt | Le | Gt | Ge => Err(EvalError::InvalidPointerMath), - _ if left.to_int().is_ok() ^ right.to_int().is_ok() => { - Err(EvalError::ReadPointerAsBytes) - }, - _ => bug!(), + fn ptr_ops( + &self, + bin_op: mir::BinOp, + left: Pointer, + left_kind: PrimValKind, + right: Pointer, + right_kind: PrimValKind, + ) -> EvalResult<'tcx, (PrimVal, bool)> { + use rustc::mir::BinOp::*; + use value::PrimValKind::*; + + if left_kind != right_kind { + let msg = format!("unimplemented binary op {:?}: {:?} ({:?}), {:?} ({:?})", bin_op, left, left_kind, right, right_kind); + return Err(EvalError::Unimplemented(msg)); + } + + let val = match (bin_op, left_kind) { + (Eq, k) if k.is_ptr() => PrimVal::from_bool(left == right), + (Ne, k) if k.is_ptr() => PrimVal::from_bool(left != right), + (Lt, k) | (Le, k) | (Gt, k) | (Ge, k) if k.is_ptr() => { + if left.alloc_id == right.alloc_id { + PrimVal::from_bool(match bin_op { + Lt => left.offset < right.offset, + Le => left.offset <= right.offset, + Gt => left.offset > right.offset, + Ge => left.offset >= right.offset, + _ => bug!("We already established it has to be a comparison operator."), + }) + } else { + return Err(EvalError::InvalidPointerMath); + } + } + (Sub, k) if k == PrimValKind::from_uint_size(self.memory.pointer_size()) => { + if left.alloc_id == right.alloc_id { + return int_arithmetic!(k, overflowing_sub, left.offset, right.offset); + } else { + return Err(EvalError::InvalidPointerMath); + } + } + _ => { + return Err(EvalError::ReadPointerAsBytes); + } + }; + Ok((val, false)) } } diff --git a/src/value.rs b/src/value.rs index efe5aac71f5..fe4c6608ed3 100644 --- a/src/value.rs +++ b/src/value.rs @@ -243,4 +243,12 @@ pub fn from_int_size(size: u64) -> Self { _ => bug!("can't make int with size {}", size), } } + + pub fn is_ptr(self) -> bool { + use self::PrimValKind::*; + match self { + Ptr | FnPtr => true, + _ => false, + } + } } diff --git a/tests/compile-fail/overflowing-lsh-neg.rs b/tests/compile-fail/overflowing-lsh-neg.rs new file mode 100644 index 00000000000..3a889be741e --- /dev/null +++ b/tests/compile-fail/overflowing-lsh-neg.rs @@ -0,0 +1,16 @@ +// Copyright 2015 The Rust Project Developers. See the COPYRIGHT +// file at the top-level directory of this distribution and at +// http://rust-lang.org/COPYRIGHT. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +#![allow(exceeding_bitshifts)] +#![allow(const_err)] + +fn main() { + let _n = 2i64 << -1; //~ Overflow(Shl) +} diff --git a/tests/compile-fail/ptr_bitops.rs b/tests/compile-fail/ptr_bitops.rs new file mode 100644 index 00000000000..78fd8e912b5 --- /dev/null +++ b/tests/compile-fail/ptr_bitops.rs @@ -0,0 +1,7 @@ +fn main() { + let bytes = [0i8, 1, 2, 3, 4, 5, 6, 7, 8, 9]; + let one = bytes.as_ptr().wrapping_offset(1); + let three = bytes.as_ptr().wrapping_offset(3); + let res = (one as usize) | (three as usize); //~ ERROR a raw memory access tried to access part of a pointer value as raw bytes + println!("{}", res); +}