diff --git a/src/error.rs b/src/error.rs index 7d252658ba..49fd8564be 100644 --- a/src/error.rs +++ b/src/error.rs @@ -6,6 +6,7 @@ use memory::Pointer; #[derive(Clone, Debug)] pub enum EvalError { DanglingPointerDeref, + InvalidFunctionPointer, InvalidBool, InvalidDiscriminant, PointerOutOfBounds { @@ -19,6 +20,8 @@ pub enum EvalError { ReadUndefBytes, InvalidBoolOp(mir::BinOp), Unimplemented(String), + DerefFunctionPointer, + ExecuteMemory, } pub type EvalResult = Result; @@ -28,6 +31,8 @@ impl Error for EvalError { match *self { EvalError::DanglingPointerDeref => "dangling pointer was dereferenced", + EvalError::InvalidFunctionPointer => + "tried to use a pointer as a function pointer", EvalError::InvalidBool => "invalid boolean value read", EvalError::InvalidDiscriminant => @@ -45,6 +50,10 @@ impl Error for EvalError { EvalError::InvalidBoolOp(_) => "invalid boolean operation", EvalError::Unimplemented(ref msg) => msg, + EvalError::DerefFunctionPointer => + "tried to dereference a function pointer", + EvalError::ExecuteMemory => + "tried to treat a memory pointer as a function pointer", } } diff --git a/src/interpreter/mod.rs b/src/interpreter/mod.rs index f77ddae6aa..1b0b416e0e 100644 --- a/src/interpreter/mod.rs +++ b/src/interpreter/mod.rs @@ -6,7 +6,7 @@ use rustc::traits::{self, ProjectionMode}; use rustc::ty::fold::TypeFoldable; use rustc::ty::layout::{self, Layout, Size}; use rustc::ty::subst::{self, Subst, Substs}; -use rustc::ty::{self, Ty, TyCtxt}; +use rustc::ty::{self, Ty, TyCtxt, BareFnTy}; use rustc::util::nodemap::DefIdMap; use rustc_data_structures::indexed_vec::Idx; use std::cell::RefCell; @@ -15,7 +15,7 @@ use std::rc::Rc; use std::{iter, mem}; use syntax::ast; use syntax::attr; -use syntax::codemap::{self, DUMMY_SP}; +use syntax::codemap::{self, DUMMY_SP, Span}; use error::{EvalError, EvalResult}; use memory::{Memory, Pointer}; @@ -40,7 +40,7 @@ pub struct EvalContext<'a, 'tcx: 'a> { mir_cache: RefCell>>>, /// The virtual memory system. - memory: Memory, + memory: Memory<'tcx>, /// Precomputed statics, constants and promoteds statics: HashMap, Pointer>, @@ -283,6 +283,7 @@ impl<'a, 'tcx> EvalContext<'a, 'tcx> { } fn load_mir(&self, def_id: DefId) -> CachedMir<'a, 'tcx> { + use rustc_trans::back::symbol_names::def_id_to_string; match self.tcx.map.as_local_node_id(def_id) { Some(node_id) => CachedMir::Ref(self.mir_map.map.get(&node_id).unwrap()), None => { @@ -293,7 +294,7 @@ impl<'a, 'tcx> EvalContext<'a, 'tcx> { let cs = &self.tcx.sess.cstore; let mir = cs.maybe_get_item_mir(self.tcx, def_id).unwrap_or_else(|| { - panic!("no mir for {:?}", def_id); + panic!("no mir for `{}`", def_id_to_string(self.tcx, def_id)); }); let cached = Rc::new(mir); mir_cache.insert(def_id, cached.clone()); @@ -429,84 +430,17 @@ impl<'a, 'tcx> EvalContext<'a, 'tcx> { let func_ty = self.operand_ty(func); match func_ty.sty { + ty::TyFnPtr(bare_fn_ty) => { + let ptr = self.eval_operand(func)?; + assert_eq!(ptr.offset, 0); + let fn_ptr = self.memory.read_ptr(ptr)?; + let (def_id, substs) = self.memory.get_fn(fn_ptr.alloc_id)?; + self.eval_fn_call(def_id, substs, bare_fn_ty, return_ptr, args, + terminator.source_info.span)? + }, ty::TyFnDef(def_id, substs, fn_ty) => { - use syntax::abi::Abi; - match fn_ty.abi { - Abi::RustIntrinsic => { - let name = self.tcx.item_name(def_id).as_str(); - match fn_ty.sig.0.output { - ty::FnConverging(ty) => { - let size = self.type_size(ty); - let ret = return_ptr.unwrap(); - self.call_intrinsic(&name, substs, args, ret, size)? - } - ty::FnDiverging => unimplemented!(), - } - } - - Abi::C => { - match fn_ty.sig.0.output { - ty::FnConverging(ty) => { - let size = self.type_size(ty); - self.call_c_abi(def_id, args, return_ptr.unwrap(), size)? - } - ty::FnDiverging => unimplemented!(), - } - } - - Abi::Rust | Abi::RustCall => { - // TODO(solson): Adjust the first argument when calling a Fn or - // FnMut closure via FnOnce::call_once. - - // Only trait methods can have a Self parameter. - let (resolved_def_id, resolved_substs) = if substs.self_ty().is_some() { - self.trait_method(def_id, substs) - } else { - (def_id, substs) - }; - - let mut arg_srcs = Vec::new(); - for arg in args { - let src = self.eval_operand(arg)?; - let src_ty = self.operand_ty(arg); - arg_srcs.push((src, src_ty)); - } - - if fn_ty.abi == Abi::RustCall && !args.is_empty() { - arg_srcs.pop(); - let last_arg = args.last().unwrap(); - let last = self.eval_operand(last_arg)?; - let last_ty = self.operand_ty(last_arg); - let last_layout = self.type_layout(last_ty); - match (&last_ty.sty, last_layout) { - (&ty::TyTuple(fields), - &Layout::Univariant { ref variant, .. }) => { - let offsets = iter::once(0) - .chain(variant.offset_after_field.iter() - .map(|s| s.bytes())); - for (offset, ty) in offsets.zip(fields) { - let src = last.offset(offset as isize); - arg_srcs.push((src, ty)); - } - } - ty => panic!("expected tuple as last argument in function with 'rust-call' ABI, got {:?}", ty), - } - } - - let mir = self.load_mir(resolved_def_id); - self.push_stack_frame( - def_id, terminator.source_info.span, mir, resolved_substs, - return_ptr - ); - - for (i, (src, src_ty)) in arg_srcs.into_iter().enumerate() { - let dest = self.frame().locals[i]; - self.move_(src, dest, src_ty)?; - } - } - - abi => return Err(EvalError::Unimplemented(format!("can't handle function with {:?} ABI", abi))), - } + self.eval_fn_call(def_id, substs, fn_ty, return_ptr, args, + terminator.source_info.span)? } _ => return Err(EvalError::Unimplemented(format!("can't handle callee of type {:?}", func_ty))), @@ -538,6 +472,93 @@ impl<'a, 'tcx> EvalContext<'a, 'tcx> { Ok(()) } + pub fn eval_fn_call( + &mut self, + def_id: DefId, + substs: &'tcx Substs<'tcx>, + fn_ty: &'tcx BareFnTy, + return_ptr: Option, + args: &[mir::Operand<'tcx>], + span: Span, + ) -> EvalResult<()> { + use syntax::abi::Abi; + match fn_ty.abi { + Abi::RustIntrinsic => { + let name = self.tcx.item_name(def_id).as_str(); + match fn_ty.sig.0.output { + ty::FnConverging(ty) => { + let size = self.type_size(ty); + let ret = return_ptr.unwrap(); + self.call_intrinsic(&name, substs, args, ret, size) + } + ty::FnDiverging => unimplemented!(), + } + } + + Abi::C => { + match fn_ty.sig.0.output { + ty::FnConverging(ty) => { + let size = self.type_size(ty); + self.call_c_abi(def_id, args, return_ptr.unwrap(), size) + } + ty::FnDiverging => unimplemented!(), + } + } + + Abi::Rust | Abi::RustCall => { + // TODO(solson): Adjust the first argument when calling a Fn or + // FnMut closure via FnOnce::call_once. + + // Only trait methods can have a Self parameter. + let (resolved_def_id, resolved_substs) = if substs.self_ty().is_some() { + self.trait_method(def_id, substs) + } else { + (def_id, substs) + }; + + let mut arg_srcs = Vec::new(); + for arg in args { + let src = self.eval_operand(arg)?; + let src_ty = self.operand_ty(arg); + arg_srcs.push((src, src_ty)); + } + + if fn_ty.abi == Abi::RustCall && !args.is_empty() { + arg_srcs.pop(); + let last_arg = args.last().unwrap(); + let last = self.eval_operand(last_arg)?; + let last_ty = self.operand_ty(last_arg); + let last_layout = self.type_layout(last_ty); + match (&last_ty.sty, last_layout) { + (&ty::TyTuple(fields), + &Layout::Univariant { ref variant, .. }) => { + let offsets = iter::once(0) + .chain(variant.offset_after_field.iter() + .map(|s| s.bytes())); + for (offset, ty) in offsets.zip(fields) { + let src = last.offset(offset as isize); + arg_srcs.push((src, ty)); + } + } + ty => panic!("expected tuple as last argument in function with 'rust-call' ABI, got {:?}", ty), + } + } + + let mir = self.load_mir(resolved_def_id); + self.push_stack_frame(def_id, span, mir, resolved_substs, return_ptr); + + for (i, (src, src_ty)) in arg_srcs.into_iter().enumerate() { + let dest = self.frame().locals[i]; + self.move_(src, dest, src_ty)?; + } + + Ok(()) + } + + abi => Err(EvalError::Unimplemented(format!("can't handle function with {:?} ABI", abi))), + } + } + fn drop(&mut self, ptr: Pointer, ty: Ty<'tcx>) -> EvalResult<()> { if !self.type_needs_drop(ty) { debug!("no need to drop {:?}", ty); @@ -1033,12 +1054,11 @@ impl<'a, 'tcx> EvalContext<'a, 'tcx> { } Cast(kind, ref operand, dest_ty) => { - let src = self.eval_operand(operand)?; - let src_ty = self.operand_ty(operand); - use rustc::mir::repr::CastKind::*; match kind { Unsize => { + let src = self.eval_operand(operand)?; + let src_ty = self.operand_ty(operand); self.move_(src, dest, src_ty)?; let src_pointee_ty = pointee_type(src_ty).unwrap(); let dest_pointee_ty = pointee_type(dest_ty).unwrap(); @@ -1054,6 +1074,8 @@ impl<'a, 'tcx> EvalContext<'a, 'tcx> { } Misc => { + let src = self.eval_operand(operand)?; + let src_ty = self.operand_ty(operand); // FIXME(solson): Wrong for almost everything. warn!("misc cast from {:?} to {:?}", src_ty, dest_ty); let dest_size = self.type_size(dest_ty); @@ -1072,6 +1094,14 @@ impl<'a, 'tcx> EvalContext<'a, 'tcx> { } } + ReifyFnPointer => match self.operand_ty(operand).sty { + ty::TyFnDef(def_id, substs, _) => { + let fn_ptr = self.memory.create_fn_ptr(def_id, substs); + self.memory.write_ptr(dest, fn_ptr)?; + }, + ref other => panic!("reify fn pointer on {:?}", other), + }, + _ => return Err(EvalError::Unimplemented(format!("can't handle cast: {:?}", rvalue))), } } @@ -1159,7 +1189,8 @@ impl<'a, 'tcx> EvalContext<'a, 'tcx> { Value { ref value } => Ok(self.const_to_ptr(value)?), Item { def_id, substs } => { if let ty::TyFnDef(..) = ty.sty { - Err(EvalError::Unimplemented("unimplemented: mentions of function items".to_string())) + // function items are zero sized + Ok(self.memory.allocate(0)) } else { let cid = ConstantId { def_id: def_id, diff --git a/src/interpreter/stepper.rs b/src/interpreter/stepper.rs index cba76eaf5a..8603054d12 100644 --- a/src/interpreter/stepper.rs +++ b/src/interpreter/stepper.rs @@ -129,7 +129,7 @@ impl<'a, 'b, 'tcx> Visitor<'tcx> for ConstantExtractor<'a, 'b, 'tcx> { mir::Literal::Value { .. } => {} mir::Literal::Item { def_id, substs } => { if let ty::TyFnDef(..) = constant.ty.sty { - // No need to do anything here, even if function pointers are implemented, + // No need to do anything here, // because the type is the actual function, not the signature of the function. // Thus we can simply create a zero sized allocation in `evaluate_operand` } else { diff --git a/src/lib.rs b/src/lib.rs index 9d2203d115..4bc5a07e3c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -13,6 +13,7 @@ #[macro_use] extern crate rustc; extern crate rustc_data_structures; extern crate rustc_mir; +extern crate rustc_trans; extern crate syntax; #[macro_use] extern crate log; extern crate log_settings; diff --git a/src/memory.rs b/src/memory.rs index 6ef742f600..35ee99eab9 100644 --- a/src/memory.rs +++ b/src/memory.rs @@ -3,6 +3,9 @@ use std::collections::Bound::{Included, Excluded}; use std::collections::{btree_map, BTreeMap, HashMap, HashSet, VecDeque}; use std::{fmt, iter, mem, ptr}; +use rustc::hir::def_id::DefId; +use rustc::ty::subst::Substs; + use error::{EvalError, EvalResult}; use primval::PrimVal; @@ -42,22 +45,40 @@ impl Pointer { // Top-level interpreter memory //////////////////////////////////////////////////////////////////////////////// -pub struct Memory { +pub struct Memory<'tcx> { + /// Actual memory allocations (arbitrary bytes, may contain pointers into other allocations) alloc_map: HashMap, + /// Function "allocations". They exist solely so pointers have something to point to, and + /// we can figure out what they point to. + functions: HashMap)>, next_id: AllocId, pub pointer_size: usize, } -impl Memory { +impl<'tcx> Memory<'tcx> { // FIXME: pass tcx.data_layout (This would also allow it to use primitive type alignments to diagnose unaligned memory accesses.) pub fn new(pointer_size: usize) -> Self { Memory { alloc_map: HashMap::new(), + functions: HashMap::new(), next_id: AllocId(0), pointer_size: pointer_size, } } + // FIXME: never create two pointers to the same def_id + substs combination + // maybe re-use the statics cache of the EvalContext? + pub fn create_fn_ptr(&mut self, def_id: DefId, substs: &'tcx Substs<'tcx>) -> Pointer { + let id = self.next_id; + debug!("creating fn ptr: {}", id); + self.next_id.0 += 1; + self.functions.insert(id, (def_id, substs)); + Pointer { + alloc_id: id, + offset: 0, + } + } + pub fn allocate(&mut self, size: usize) -> Pointer { let alloc = Allocation { bytes: vec![0; size], @@ -119,11 +140,34 @@ impl Memory { //////////////////////////////////////////////////////////////////////////////// pub fn get(&self, id: AllocId) -> EvalResult<&Allocation> { - self.alloc_map.get(&id).ok_or(EvalError::DanglingPointerDeref) + match self.alloc_map.get(&id) { + Some(alloc) => Ok(alloc), + None => match self.functions.get(&id) { + Some(_) => Err(EvalError::DerefFunctionPointer), + None => Err(EvalError::DanglingPointerDeref), + } + } } pub fn get_mut(&mut self, id: AllocId) -> EvalResult<&mut Allocation> { - self.alloc_map.get_mut(&id).ok_or(EvalError::DanglingPointerDeref) + match self.alloc_map.get_mut(&id) { + Some(alloc) => Ok(alloc), + None => match self.functions.get(&id) { + Some(_) => Err(EvalError::DerefFunctionPointer), + None => Err(EvalError::DanglingPointerDeref), + } + } + } + + pub fn get_fn(&self, id: AllocId) -> EvalResult<(DefId, &'tcx Substs<'tcx>)> { + debug!("reading fn ptr: {}", id); + match self.functions.get(&id) { + Some(&fn_id) => Ok(fn_id), + None => match self.alloc_map.get(&id) { + Some(_) => Err(EvalError::ExecuteMemory), + None => Err(EvalError::InvalidFunctionPointer), + } + } } /// Print an allocation and all allocations it points to, recursively. @@ -138,12 +182,18 @@ impl Memory { print!("{}", prefix); let mut relocations = vec![]; - let alloc = match self.alloc_map.get(&id) { - Some(a) => a, - None => { + let alloc = match (self.alloc_map.get(&id), self.functions.get(&id)) { + (Some(a), None) => a, + (None, Some(_)) => { + // FIXME: print function name + println!("function pointer"); + continue; + }, + (None, None) => { println!("(deallocated)"); continue; - } + }, + (Some(_), Some(_)) => unreachable!(), }; for i in 0..alloc.bytes.len() { diff --git a/tests/compile-fail/deref_fn_ptr.rs b/tests/compile-fail/deref_fn_ptr.rs new file mode 100644 index 0000000000..52c7c2b8f9 --- /dev/null +++ b/tests/compile-fail/deref_fn_ptr.rs @@ -0,0 +1,13 @@ +#![feature(custom_attribute)] +#![allow(dead_code, unused_attributes)] + +fn f() {} + +#[miri_run] +fn deref_fn_ptr() -> i32 { + unsafe { + *std::mem::transmute::(f) //~ ERROR: tried to dereference a function pointer + } +} + +fn main() {} diff --git a/tests/compile-fail/execute_memory.rs b/tests/compile-fail/execute_memory.rs new file mode 100644 index 0000000000..4e06fd8db8 --- /dev/null +++ b/tests/compile-fail/execute_memory.rs @@ -0,0 +1,14 @@ +#![feature(custom_attribute, box_syntax)] +#![allow(dead_code, unused_attributes)] + +#[miri_run] +fn deref_fn_ptr() { + //FIXME: this span is wrong + let x = box 42; //~ ERROR: tried to treat a memory pointer as a function pointer + unsafe { + let f = std::mem::transmute::, fn()>(x); + f() + } +} + +fn main() {} diff --git a/tests/compile-fail/unimplemented.rs b/tests/compile-fail/unimplemented.rs index 754d3d9ee7..7752650ade 100644 --- a/tests/compile-fail/unimplemented.rs +++ b/tests/compile-fail/unimplemented.rs @@ -1,7 +1,7 @@ #![feature(custom_attribute)] #![allow(dead_code, unused_attributes)] -//error-pattern:unimplemented: mentions of function items +//error-pattern:begin_panic_fmt #[miri_run] diff --git a/tests/run-pass/function_pointers.rs b/tests/run-pass/function_pointers.rs new file mode 100644 index 0000000000..55a6f9fbea --- /dev/null +++ b/tests/run-pass/function_pointers.rs @@ -0,0 +1,17 @@ +#![feature(custom_attribute)] +#![allow(dead_code, unused_attributes)] + +fn f() -> i32 { + 42 +} + +fn return_fn_ptr() -> fn() -> i32 { + f +} + +#[miri_run] +fn call_fn_ptr() -> i32 { + return_fn_ptr()() +} + +fn main() {} diff --git a/tests/run-pass/zst.rs b/tests/run-pass/zst.rs new file mode 100644 index 0000000000..d4f1d4023b --- /dev/null +++ b/tests/run-pass/zst.rs @@ -0,0 +1,17 @@ +#![feature(custom_attribute)] +#![allow(dead_code, unused_attributes)] + +struct A; + +#[miri_run] +fn zst_ret() -> A { + A +} + +#[miri_run] +fn use_zst() -> A { + let a = A; + a +} + +fn main() {}