diff --git a/src/error.rs b/src/error.rs index 7d252658ba2d..a4ae8cbdda25 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 { @@ -28,6 +29,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 => diff --git a/src/interpreter/mod.rs b/src/interpreter/mod.rs index 37113f387319..251ebb768e0d 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()); @@ -421,84 +422,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, self.substs()); - 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.substs()); - 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, self.substs()); - 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))), @@ -530,6 +464,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, self.substs()); + 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.substs()); + 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, self.substs()); + 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); @@ -1023,12 +1044,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(); @@ -1044,11 +1064,20 @@ impl<'a, 'tcx> EvalContext<'a, 'tcx> { } Misc => { + let src = self.eval_operand(operand)?; // FIXME(solson): Wrong for almost everything. let size = dest_layout.size(&self.tcx.data_layout).bytes() as usize; self.memory.copy(src, dest, size)?; } + 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))), } } @@ -1136,7 +1165,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 cba76eaf5a3c..8603054d124c 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 9d2203d115c1..4bc5a07e3c2f 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 ab527a47c0bf..d9999821e1b0 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,37 @@ impl Pointer { // Top-level interpreter memory //////////////////////////////////////////////////////////////////////////////// -pub struct Memory { +pub struct Memory<'tcx> { alloc_map: HashMap, + 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 gecx? + 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], @@ -125,6 +143,11 @@ impl Memory { self.alloc_map.get_mut(&id).ok_or(EvalError::DanglingPointerDeref) } + pub fn get_fn(&self, id: AllocId) -> EvalResult<(DefId, &'tcx Substs<'tcx>)> { + debug!("reading fn ptr: {}", id); + self.functions.get(&id).map(|&did| did).ok_or(EvalError::InvalidFunctionPointer) + } + /// Print an allocation and all allocations it points to, recursively. pub fn dump(&self, id: AllocId) { let mut allocs_seen = HashSet::new(); @@ -137,12 +160,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/unimplemented.rs b/tests/compile-fail/unimplemented.rs index 754d3d9ee7e6..7752650ade88 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 000000000000..55a6f9fbeac4 --- /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 000000000000..d4f1d4023ba7 --- /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() {}