From e5799a6af35f5c0a257ff375498c5237e4a5466b Mon Sep 17 00:00:00 2001 From: Oliver Schneider Date: Fri, 28 Jul 2017 09:52:19 +0200 Subject: [PATCH] Reduce the chance of accidentally calling functions in CTFE previously miri had a check for const fn and other cases that CTFE requires. Instead the function call is completely processed inside the machine. This allows CTFE to have full control over what is called and miri to not have useless CTFE-checks in normal mode. --- Cargo.toml | 1 + miri/fn_call.rs | 564 +++++++++++++++++++ miri/lib.rs | 20 +- src/librustc_mir/interpret/const_eval.rs | 44 +- src/librustc_mir/interpret/machine.rs | 20 +- src/librustc_mir/interpret/terminator/mod.rs | 53 +- 6 files changed, 624 insertions(+), 78 deletions(-) create mode 100644 miri/fn_call.rs diff --git a/Cargo.toml b/Cargo.toml index 5081cb1081b..4ee9dc974bd 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -35,3 +35,4 @@ compiletest_rs = "0.2.6" [workspace] members = ["src/librustc_mir"] +exclude = ["xargo"] diff --git a/miri/fn_call.rs b/miri/fn_call.rs new file mode 100644 index 00000000000..d8e92e7291f --- /dev/null +++ b/miri/fn_call.rs @@ -0,0 +1,564 @@ +use rustc::ty::{self, Ty}; +use rustc::hir::def_id::{DefId, CRATE_DEF_INDEX}; +use rustc::mir; +use syntax::attr; +use syntax::abi::Abi; +use syntax::codemap::Span; + +use std::mem; + +use rustc_miri::interpret::*; + +use super::{ + TlsKey, + EvalContext, + MemoryExt, +}; + +pub trait EvalContextExt<'tcx> { + fn call_c_abi( + &mut self, + def_id: DefId, + arg_operands: &[mir::Operand<'tcx>], + dest: Lvalue<'tcx>, + dest_ty: Ty<'tcx>, + dest_block: mir::BasicBlock, + ) -> EvalResult<'tcx>; + + fn resolve_path(&self, path: &[&str]) -> EvalResult<'tcx, ty::Instance<'tcx>>; + + fn call_missing_fn( + &mut self, + instance: ty::Instance<'tcx>, + destination: Option<(Lvalue<'tcx>, mir::BasicBlock)>, + arg_operands: &[mir::Operand<'tcx>], + sig: ty::FnSig<'tcx>, + path: String, + ) -> EvalResult<'tcx>; + + fn eval_fn_call( + &mut self, + instance: ty::Instance<'tcx>, + destination: Option<(Lvalue<'tcx>, mir::BasicBlock)>, + arg_operands: &[mir::Operand<'tcx>], + span: Span, + sig: ty::FnSig<'tcx>, + ) -> EvalResult<'tcx, bool>; +} + +impl<'a, 'tcx> EvalContextExt<'tcx> for EvalContext<'a, 'tcx, super::Evaluator> { + fn eval_fn_call( + &mut self, + instance: ty::Instance<'tcx>, + destination: Option<(Lvalue<'tcx>, mir::BasicBlock)>, + arg_operands: &[mir::Operand<'tcx>], + span: Span, + sig: ty::FnSig<'tcx>, + ) -> EvalResult<'tcx, bool> { + trace!("eval_fn_call: {:#?}, {:#?}", instance, destination); + + let mir = match self.load_mir(instance.def) { + Ok(mir) => mir, + Err(EvalError::NoMirFor(path)) => { + self.call_missing_fn(instance, destination, arg_operands, sig, path)?; + return Ok(true); + }, + Err(other) => return Err(other), + }; + + let (return_lvalue, return_to_block) = match destination { + Some((lvalue, block)) => (lvalue, StackPopCleanup::Goto(block)), + None => (Lvalue::undef(), StackPopCleanup::None), + }; + + self.push_stack_frame( + instance, + span, + mir, + return_lvalue, + return_to_block, + )?; + + Ok(false) + } + + fn call_c_abi( + &mut self, + def_id: DefId, + arg_operands: &[mir::Operand<'tcx>], + dest: Lvalue<'tcx>, + dest_ty: Ty<'tcx>, + dest_block: mir::BasicBlock, + ) -> EvalResult<'tcx> { + let name = self.tcx.item_name(def_id); + let attrs = self.tcx.get_attrs(def_id); + let link_name = attr::first_attr_value_str_by_name(&attrs, "link_name") + .unwrap_or(name) + .as_str(); + + let args_res: EvalResult> = arg_operands.iter() + .map(|arg| self.eval_operand(arg)) + .collect(); + let args = args_res?; + + let usize = self.tcx.types.usize; + + match &link_name[..] { + "malloc" => { + let size = self.value_to_primval(args[0], usize)?.to_u64()?; + if size == 0 { + self.write_null(dest, dest_ty)?; + } else { + let align = self.memory.pointer_size(); + let ptr = self.memory.allocate(size, align, Kind::C)?; + self.write_primval(dest, PrimVal::Ptr(ptr), dest_ty)?; + } + } + + "free" => { + let ptr = args[0].into_ptr(&mut self.memory)?; + if !ptr.is_null()? { + self.memory.deallocate(ptr.to_ptr()?, None, Kind::C)?; + } + } + + "syscall" => { + match self.value_to_primval(args[0], usize)?.to_u64()? { + 511 => return Err(EvalError::Unimplemented("miri does not support random number generators".to_owned())), + id => return Err(EvalError::Unimplemented(format!("miri does not support syscall id {}", id))), + } + } + + "dlsym" => { + let _handle = args[0].into_ptr(&mut self.memory)?; + let symbol = args[1].into_ptr(&mut self.memory)?.to_ptr()?; + let symbol_name = self.memory.read_c_str(symbol)?; + let err = format!("bad c unicode symbol: {:?}", symbol_name); + let symbol_name = ::std::str::from_utf8(symbol_name).unwrap_or(&err); + return Err(EvalError::Unimplemented(format!("miri does not support dynamically loading libraries (requested symbol: {})", symbol_name))); + } + + "__rust_maybe_catch_panic" => { + // fn __rust_maybe_catch_panic(f: fn(*mut u8), data: *mut u8, data_ptr: *mut usize, vtable_ptr: *mut usize) -> u32 + // We abort on panic, so not much is going on here, but we still have to call the closure + let u8_ptr_ty = self.tcx.mk_mut_ptr(self.tcx.types.u8); + let f = args[0].into_ptr(&mut self.memory)?.to_ptr()?; + let data = args[1].into_ptr(&mut self.memory)?; + let f_instance = self.memory.get_fn(f)?; + self.write_null(dest, dest_ty)?; + + // Now we make a function call. TODO: Consider making this re-usable? EvalContext::step does sth. similar for the TLS dtors, + // and of course eval_main. + let mir = self.load_mir(f_instance.def)?; + self.push_stack_frame( + f_instance, + mir.span, + mir, + Lvalue::undef(), + StackPopCleanup::Goto(dest_block), + )?; + + let arg_local = self.frame().mir.args_iter().next().ok_or(EvalError::AbiViolation("Argument to __rust_maybe_catch_panic does not take enough arguments.".to_owned()))?; + let arg_dest = self.eval_lvalue(&mir::Lvalue::Local(arg_local))?; + self.write_ptr(arg_dest, data, u8_ptr_ty)?; + + // We ourselves return 0 + self.write_null(dest, dest_ty)?; + + // Don't fall through + return Ok(()); + } + + "__rust_start_panic" => { + return Err(EvalError::Panic); + } + + "memcmp" => { + let left = args[0].into_ptr(&mut self.memory)?; + let right = args[1].into_ptr(&mut self.memory)?; + let n = self.value_to_primval(args[2], usize)?.to_u64()?; + + let result = { + let left_bytes = self.memory.read_bytes(left, n)?; + let right_bytes = self.memory.read_bytes(right, n)?; + + use std::cmp::Ordering::*; + match left_bytes.cmp(right_bytes) { + Less => -1i8, + Equal => 0, + Greater => 1, + } + }; + + self.write_primval(dest, PrimVal::Bytes(result as u128), dest_ty)?; + } + + "memrchr" => { + let ptr = args[0].into_ptr(&mut self.memory)?; + let val = self.value_to_primval(args[1], usize)?.to_u64()? as u8; + let num = self.value_to_primval(args[2], usize)?.to_u64()?; + if let Some(idx) = self.memory.read_bytes(ptr, num)?.iter().rev().position(|&c| c == val) { + let new_ptr = ptr.offset(num - idx as u64 - 1, &self)?; + self.write_ptr(dest, new_ptr, dest_ty)?; + } else { + self.write_null(dest, dest_ty)?; + } + } + + "memchr" => { + let ptr = args[0].into_ptr(&mut self.memory)?; + let val = self.value_to_primval(args[1], usize)?.to_u64()? as u8; + let num = self.value_to_primval(args[2], usize)?.to_u64()?; + if let Some(idx) = self.memory.read_bytes(ptr, num)?.iter().position(|&c| c == val) { + let new_ptr = ptr.offset(idx as u64, &self)?; + self.write_ptr(dest, new_ptr, dest_ty)?; + } else { + self.write_null(dest, dest_ty)?; + } + } + + "getenv" => { + let result = { + let name_ptr = args[0].into_ptr(&mut self.memory)?.to_ptr()?; + let name = self.memory.read_c_str(name_ptr)?; + match self.machine_data.env_vars.get(name) { + Some(&var) => PrimVal::Ptr(var), + None => PrimVal::Bytes(0), + } + }; + self.write_primval(dest, result, dest_ty)?; + } + + "unsetenv" => { + let mut success = None; + { + let name_ptr = args[0].into_ptr(&mut self.memory)?; + if !name_ptr.is_null()? { + let name = self.memory.read_c_str(name_ptr.to_ptr()?)?; + if !name.is_empty() && !name.contains(&b'=') { + success = Some(self.machine_data.env_vars.remove(name)); + } + } + } + if let Some(old) = success { + if let Some(var) = old { + self.memory.deallocate(var, None, Kind::Env)?; + } + self.write_null(dest, dest_ty)?; + } else { + self.write_primval(dest, PrimVal::from_i128(-1), dest_ty)?; + } + } + + "setenv" => { + let mut new = None; + { + let name_ptr = args[0].into_ptr(&mut self.memory)?; + let value_ptr = args[1].into_ptr(&mut self.memory)?.to_ptr()?; + let value = self.memory.read_c_str(value_ptr)?; + if !name_ptr.is_null()? { + let name = self.memory.read_c_str(name_ptr.to_ptr()?)?; + if !name.is_empty() && !name.contains(&b'=') { + new = Some((name.to_owned(), value.to_owned())); + } + } + } + if let Some((name, value)) = new { + // +1 for the null terminator + let value_copy = self.memory.allocate((value.len() + 1) as u64, 1, Kind::Env)?; + self.memory.write_bytes(value_copy.into(), &value)?; + let trailing_zero_ptr = value_copy.offset(value.len() as u64, &self)?.into(); + self.memory.write_bytes(trailing_zero_ptr, &[0])?; + if let Some(var) = self.machine_data.env_vars.insert(name.to_owned(), value_copy) { + self.memory.deallocate(var, None, Kind::Env)?; + } + self.write_null(dest, dest_ty)?; + } else { + self.write_primval(dest, PrimVal::from_i128(-1), dest_ty)?; + } + } + + "write" => { + let fd = self.value_to_primval(args[0], usize)?.to_u64()?; + let buf = args[1].into_ptr(&mut self.memory)?; + let n = self.value_to_primval(args[2], usize)?.to_u64()?; + trace!("Called write({:?}, {:?}, {:?})", fd, buf, n); + let result = if fd == 1 || fd == 2 { // stdout/stderr + use std::io::{self, Write}; + + let buf_cont = self.memory.read_bytes(buf, n)?; + let res = if fd == 1 { io::stdout().write(buf_cont) } else { io::stderr().write(buf_cont) }; + match res { Ok(n) => n as isize, Err(_) => -1 } + } else { + info!("Ignored output to FD {}", fd); + n as isize // pretend it all went well + }; // now result is the value we return back to the program + self.write_primval(dest, PrimVal::Bytes(result as u128), dest_ty)?; + } + + "strlen" => { + let ptr = args[0].into_ptr(&mut self.memory)?.to_ptr()?; + let n = self.memory.read_c_str(ptr)?.len(); + self.write_primval(dest, PrimVal::Bytes(n as u128), dest_ty)?; + } + + // Some things needed for sys::thread initialization to go through + "signal" | "sigaction" | "sigaltstack" => { + self.write_primval(dest, PrimVal::Bytes(0), dest_ty)?; + } + + "sysconf" => { + let name = self.value_to_primval(args[0], usize)?.to_u64()?; + trace!("sysconf() called with name {}", name); + // cache the sysconf integers via miri's global cache + let paths = &[ + (&["libc", "_SC_PAGESIZE"], PrimVal::Bytes(4096)), + (&["libc", "_SC_GETPW_R_SIZE_MAX"], PrimVal::from_i128(-1)), + ]; + let mut result = None; + for &(path, path_value) in paths { + if let Ok(instance) = self.resolve_path(path) { + let cid = GlobalId { instance, promoted: None }; + // compute global if not cached + let val = match self.globals.get(&cid).map(|glob| glob.value) { + Some(value) => self.value_to_primval(value, usize)?.to_u64()?, + None => eval_body_as_primval(self.tcx, instance)?.0.to_u64()?, + }; + if val == name { + result = Some(path_value); + break; + } + } + } + if let Some(result) = result { + self.write_primval(dest, result, dest_ty)?; + } else { + return Err(EvalError::Unimplemented(format!("Unimplemented sysconf name: {}", name))); + } + } + + // Hook pthread calls that go to the thread-local storage memory subsystem + "pthread_key_create" => { + let key_ptr = args[0].into_ptr(&mut self.memory)?; + + // Extract the function type out of the signature (that seems easier than constructing it ourselves...) + let dtor = match args[1].into_ptr(&mut self.memory)?.into_inner_primval() { + PrimVal::Ptr(dtor_ptr) => Some(self.memory.get_fn(dtor_ptr)?), + PrimVal::Bytes(0) => None, + PrimVal::Bytes(_) => return Err(EvalError::ReadBytesAsPointer), + PrimVal::Undef => return Err(EvalError::ReadUndefBytes), + }; + + // Figure out how large a pthread TLS key actually is. This is libc::pthread_key_t. + let key_type = self.operand_ty(&arg_operands[0]).builtin_deref(true, ty::LvaluePreference::NoPreference) + .ok_or(EvalError::AbiViolation("Wrong signature used for pthread_key_create: First argument must be a raw pointer.".to_owned()))?.ty; + let key_size = { + let layout = self.type_layout(key_type)?; + layout.size(&self.tcx.data_layout) + }; + + // Create key and write it into the memory where key_ptr wants it + let key = self.memory.create_tls_key(dtor) as u128; + if key_size.bits() < 128 && key >= (1u128 << key_size.bits() as u128) { + return Err(EvalError::OutOfTls); + } + // TODO: Does this need checking for alignment? + self.memory.write_uint(key_ptr.to_ptr()?, key, key_size.bytes())?; + + // Return success (0) + self.write_null(dest, dest_ty)?; + } + "pthread_key_delete" => { + // The conversion into TlsKey here is a little fishy, but should work as long as usize >= libc::pthread_key_t + let key = self.value_to_primval(args[0], usize)?.to_u64()? as TlsKey; + self.memory.delete_tls_key(key)?; + // Return success (0) + self.write_null(dest, dest_ty)?; + } + "pthread_getspecific" => { + // The conversion into TlsKey here is a little fishy, but should work as long as usize >= libc::pthread_key_t + let key = self.value_to_primval(args[0], usize)?.to_u64()? as TlsKey; + let ptr = self.memory.load_tls(key)?; + self.write_ptr(dest, ptr, dest_ty)?; + } + "pthread_setspecific" => { + // The conversion into TlsKey here is a little fishy, but should work as long as usize >= libc::pthread_key_t + let key = self.value_to_primval(args[0], usize)?.to_u64()? as TlsKey; + let new_ptr = args[1].into_ptr(&mut self.memory)?; + self.memory.store_tls(key, new_ptr)?; + + // Return success (0) + self.write_null(dest, dest_ty)?; + } + + // Stub out all the other pthread calls to just return 0 + link_name if link_name.starts_with("pthread_") => { + warn!("ignoring C ABI call: {}", link_name); + self.write_null(dest, dest_ty)?; + }, + + _ => { + return Err(EvalError::Unimplemented(format!("can't call C ABI function: {}", link_name))); + } + } + + // Since we pushed no stack frame, the main loop will act + // as if the call just completed and it's returning to the + // current frame. + self.dump_local(dest); + self.goto_block(dest_block); + Ok(()) + } + + /// Get an instance for a path. + fn resolve_path(&self, path: &[&str]) -> EvalResult<'tcx, ty::Instance<'tcx>> { + let cstore = &self.tcx.sess.cstore; + + let crates = cstore.crates(); + crates.iter() + .find(|&&krate| cstore.crate_name(krate) == path[0]) + .and_then(|krate| { + let krate = DefId { + krate: *krate, + index: CRATE_DEF_INDEX, + }; + let mut items = cstore.item_children(krate, self.tcx.sess); + let mut path_it = path.iter().skip(1).peekable(); + + while let Some(segment) = path_it.next() { + for item in &mem::replace(&mut items, vec![]) { + if item.ident.name == *segment { + if path_it.peek().is_none() { + return Some(ty::Instance::mono(self.tcx, item.def.def_id())); + } + + items = cstore.item_children(item.def.def_id(), self.tcx.sess); + break; + } + } + } + None + }) + .ok_or_else(|| { + let path = path.iter() + .map(|&s| s.to_owned()) + .collect(); + EvalError::PathNotFound(path) + }) + } + + fn call_missing_fn( + &mut self, + instance: ty::Instance<'tcx>, + destination: Option<(Lvalue<'tcx>, mir::BasicBlock)>, + arg_operands: &[mir::Operand<'tcx>], + sig: ty::FnSig<'tcx>, + path: String, + ) -> EvalResult<'tcx> { + // In some cases in non-MIR libstd-mode, not having a destination is legit. Handle these early. + match &path[..] { + "std::panicking::rust_panic_with_hook" | + "std::rt::begin_panic_fmt" => return Err(EvalError::Panic), + _ => {}, + } + + let dest_ty = sig.output(); + let (dest, dest_block) = destination.ok_or_else(|| EvalError::NoMirFor(path.clone()))?; + + if sig.abi == Abi::C { + // An external C function + // TODO: That functions actually has a similar preamble to what follows here. May make sense to + // unify these two mechanisms for "hooking into missing functions". + self.call_c_abi(instance.def_id(), arg_operands, dest, dest_ty, dest_block)?; + return Ok(()); + } + + let args_res: EvalResult> = arg_operands.iter() + .map(|arg| self.eval_operand(arg)) + .collect(); + let args = args_res?; + + let usize = self.tcx.types.usize; + + match &path[..] { + // Allocators are magic. They have no MIR, even when the rest of libstd does. + "alloc::heap::::__rust_alloc" => { + let size = self.value_to_primval(args[0], usize)?.to_u64()?; + let align = self.value_to_primval(args[1], usize)?.to_u64()?; + if size == 0 { + return Err(EvalError::HeapAllocZeroBytes); + } + if !align.is_power_of_two() { + return Err(EvalError::HeapAllocNonPowerOfTwoAlignment(align)); + } + let ptr = self.memory.allocate(size, align, Kind::Rust)?; + self.write_primval(dest, PrimVal::Ptr(ptr), dest_ty)?; + } + "alloc::heap::::__rust_alloc_zeroed" => { + let size = self.value_to_primval(args[0], usize)?.to_u64()?; + let align = self.value_to_primval(args[1], usize)?.to_u64()?; + if size == 0 { + return Err(EvalError::HeapAllocZeroBytes); + } + if !align.is_power_of_two() { + return Err(EvalError::HeapAllocNonPowerOfTwoAlignment(align)); + } + let ptr = self.memory.allocate(size, align, Kind::Rust)?; + self.memory.write_repeat(ptr.into(), 0, size)?; + self.write_primval(dest, PrimVal::Ptr(ptr), dest_ty)?; + } + "alloc::heap::::__rust_dealloc" => { + let ptr = args[0].into_ptr(&mut self.memory)?.to_ptr()?; + let old_size = self.value_to_primval(args[1], usize)?.to_u64()?; + let align = self.value_to_primval(args[2], usize)?.to_u64()?; + if old_size == 0 { + return Err(EvalError::HeapAllocZeroBytes); + } + if !align.is_power_of_two() { + return Err(EvalError::HeapAllocNonPowerOfTwoAlignment(align)); + } + self.memory.deallocate(ptr, Some((old_size, align)), Kind::Rust)?; + } + "alloc::heap::::__rust_realloc" => { + let ptr = args[0].into_ptr(&mut self.memory)?.to_ptr()?; + let old_size = self.value_to_primval(args[1], usize)?.to_u64()?; + let old_align = self.value_to_primval(args[2], usize)?.to_u64()?; + let new_size = self.value_to_primval(args[3], usize)?.to_u64()?; + let new_align = self.value_to_primval(args[4], usize)?.to_u64()?; + if old_size == 0 || new_size == 0 { + return Err(EvalError::HeapAllocZeroBytes); + } + if !old_align.is_power_of_two() { + return Err(EvalError::HeapAllocNonPowerOfTwoAlignment(old_align)); + } + if !new_align.is_power_of_two() { + return Err(EvalError::HeapAllocNonPowerOfTwoAlignment(new_align)); + } + let new_ptr = self.memory.reallocate(ptr, old_size, old_align, new_size, new_align, Kind::Rust)?; + self.write_primval(dest, PrimVal::Ptr(new_ptr), dest_ty)?; + } + + // A Rust function is missing, which means we are running with MIR missing for libstd (or other dependencies). + // Still, we can make many things mostly work by "emulating" or ignoring some functions. + "std::io::_print" => { + trace!("Ignoring output. To run programs that print, make sure you have a libstd with full MIR."); + } + "std::thread::Builder::new" => return Err(EvalError::Unimplemented("miri does not support threading".to_owned())), + "std::env::args" => return Err(EvalError::Unimplemented("miri does not support program arguments".to_owned())), + "std::panicking::panicking" | + "std::rt::panicking" => { + // we abort on panic -> `std::rt::panicking` always returns false + let bool = self.tcx.types.bool; + self.write_primval(dest, PrimVal::from_bool(false), bool)?; + } + _ => return Err(EvalError::NoMirFor(path)), + } + + // Since we pushed no stack frame, the main loop will act + // as if the call just completed and it's returning to the + // current frame. + self.dump_local(dest); + self.goto_block(dest_block); + return Ok(()); + } +} diff --git a/miri/lib.rs b/miri/lib.rs index 4482e8fcb7c..bd17ab6f2cb 100644 --- a/miri/lib.rs +++ b/miri/lib.rs @@ -17,6 +17,8 @@ use rustc::hir::def_id::DefId; use rustc::mir; +use syntax::codemap::Span; + use std::collections::{ HashMap, BTreeMap, @@ -25,10 +27,10 @@ extern crate rustc_miri; pub use rustc_miri::interpret::*; -mod missing_fns; +mod fn_call; mod operator; -use missing_fns::EvalContextExt as MissingFnsEvalContextExt; +use fn_call::EvalContextExt as MissingFnsEvalContextExt; use operator::EvalContextExt as OperatorEvalContextExt; pub fn eval_main<'a, 'tcx: 'a>( @@ -272,17 +274,19 @@ fn fetch_tls_dtor(&mut self, key: Option) -> EvalResult<'tcx, Option<(ty impl<'tcx> Machine<'tcx> for Evaluator { type Data = EvaluatorData; type MemoryData = MemoryData<'tcx>; + /// Returns Ok() when the function was handled, fail otherwise - fn call_missing_fn<'a>( + fn eval_fn_call<'a>( ecx: &mut EvalContext<'a, 'tcx, Self>, instance: ty::Instance<'tcx>, destination: Option<(Lvalue<'tcx>, mir::BasicBlock)>, arg_operands: &[mir::Operand<'tcx>], + span: Span, sig: ty::FnSig<'tcx>, - path: String, - ) -> EvalResult<'tcx> { - ecx.call_missing_fn(instance, destination, arg_operands, sig, path) + ) -> EvalResult<'tcx, bool> { + ecx.eval_fn_call(instance, destination, arg_operands, span, sig) } + fn ptr_op<'a>( ecx: &rustc_miri::interpret::EvalContext<'a, 'tcx, Self>, bin_op: mir::BinOp, @@ -293,8 +297,4 @@ fn ptr_op<'a>( ) -> EvalResult<'tcx, Option<(PrimVal, bool)>> { ecx.ptr_op(bin_op, left, left_ty, right, right_ty) } - - fn check_non_const_fn_call(_instance: ty::Instance<'tcx>) -> EvalResult<'tcx> { - Ok(()) - } } diff --git a/src/librustc_mir/interpret/const_eval.rs b/src/librustc_mir/interpret/const_eval.rs index a9974e5367e..32f5a0a183e 100644 --- a/src/librustc_mir/interpret/const_eval.rs +++ b/src/librustc_mir/interpret/const_eval.rs @@ -3,6 +3,7 @@ use rustc::mir; use syntax::ast::Mutability; +use syntax::codemap::Span; use super::{ EvalResult, EvalError, @@ -127,16 +128,39 @@ fn cause(&self) -> Option<&Error> { impl<'tcx> super::Machine<'tcx> for CompileTimeFunctionEvaluator { type Data = (); type MemoryData = (); - fn call_missing_fn<'a>( - _ecx: &mut EvalContext<'a, 'tcx, Self>, - _instance: ty::Instance<'tcx>, - _destination: Option<(Lvalue<'tcx>, mir::BasicBlock)>, + fn eval_fn_call<'a>( + ecx: &mut EvalContext<'a, 'tcx, Self>, + instance: ty::Instance<'tcx>, + destination: Option<(Lvalue<'tcx>, mir::BasicBlock)>, _arg_operands: &[mir::Operand<'tcx>], + span: Span, _sig: ty::FnSig<'tcx>, - path: String, - ) -> EvalResult<'tcx> { - // some simple things like `malloc` might get accepted in the future - Err(ConstEvalError::NeedsRfc(format!("calling extern function `{}`", path)).into()) + ) -> EvalResult<'tcx, bool> { + if !ecx.tcx.is_const_fn(instance.def_id()) { + return Err(ConstEvalError::NotConst(format!("calling non-const fn `{}`", instance)).into()); + } + let mir = match ecx.load_mir(instance.def) { + Ok(mir) => mir, + Err(EvalError::NoMirFor(path)) => { + // some simple things like `malloc` might get accepted in the future + return Err(ConstEvalError::NeedsRfc(format!("calling extern function `{}`", path)).into()); + }, + Err(other) => return Err(other), + }; + let (return_lvalue, return_to_block) = match destination { + Some((lvalue, block)) => (lvalue, StackPopCleanup::Goto(block)), + None => (Lvalue::undef(), StackPopCleanup::None), + }; + + ecx.push_stack_frame( + instance, + span, + mir, + return_lvalue, + return_to_block, + )?; + + Ok(false) } fn ptr_op<'a>( @@ -149,8 +173,4 @@ fn ptr_op<'a>( ) -> EvalResult<'tcx, Option<(PrimVal, bool)>> { Err(ConstEvalError::NeedsRfc("Pointer arithmetic or comparison".to_string()).into()) } - - fn check_non_const_fn_call(instance: ty::Instance<'tcx>) -> EvalResult<'tcx> { - return Err(ConstEvalError::NotConst(format!("calling non-const fn `{}`", instance)).into()); - } } diff --git a/src/librustc_mir/interpret/machine.rs b/src/librustc_mir/interpret/machine.rs index 5d762c81a9b..ebf15300e8c 100644 --- a/src/librustc_mir/interpret/machine.rs +++ b/src/librustc_mir/interpret/machine.rs @@ -10,6 +10,7 @@ }; use rustc::{mir, ty}; +use syntax::codemap::Span; /// Methods of this trait signifies a point where CTFE evaluation would fail /// and some use case dependent behaviour can instead be applied @@ -20,16 +21,20 @@ pub trait Machine<'tcx>: Sized { /// Additional data that can be accessed via the Memory type MemoryData; - /// Called when a function's MIR is not found. - /// This will happen for `extern "C"` functions. - fn call_missing_fn<'a>( + /// Entry point to all function calls. + /// + /// Returns Ok(true) when the function was handled completely + /// e.g. due to missing mir or + /// + /// Returns Ok(false) if a new stack frame was pushed + fn eval_fn_call<'a>( ecx: &mut EvalContext<'a, 'tcx, Self>, instance: ty::Instance<'tcx>, destination: Option<(Lvalue<'tcx>, mir::BasicBlock)>, arg_operands: &[mir::Operand<'tcx>], + span: Span, sig: ty::FnSig<'tcx>, - path: String, - ) -> EvalResult<'tcx>; + ) -> EvalResult<'tcx, bool>; /// Called when operating on the value of pointers. /// @@ -45,10 +50,5 @@ fn ptr_op<'a>( right: PrimVal, right_ty: ty::Ty<'tcx>, ) -> EvalResult<'tcx, Option<(PrimVal, bool)>>; - - /// Called when adding a frame for a function that's not `const fn` - /// - /// Const eval returns `Err`, miri returns `Ok` - fn check_non_const_fn_call(instance: ty::Instance<'tcx>) -> EvalResult<'tcx>; } diff --git a/src/librustc_mir/interpret/terminator/mod.rs b/src/librustc_mir/interpret/terminator/mod.rs index 779ab8c9874..21e59e9d456 100644 --- a/src/librustc_mir/interpret/terminator/mod.rs +++ b/src/librustc_mir/interpret/terminator/mod.rs @@ -6,7 +6,7 @@ use super::{ EvalError, EvalResult, - EvalContext, StackPopCleanup, eval_context, TyAndPacked, + EvalContext, eval_context, TyAndPacked, Lvalue, MemoryPointer, PrimVal, Value, @@ -233,7 +233,8 @@ fn eval_fn_call( let arg_ty = self.operand_ty(arg); args.push((arg_val, arg_ty)); } - if self.eval_fn_call_inner( + if M::eval_fn_call( + self, instance, destination, arg_operands, @@ -276,7 +277,8 @@ fn eval_fn_call( } // Push the stack frame, and potentially be entirely done if the call got hooked - if self.eval_fn_call_inner( + if M::eval_fn_call( + self, instance, destination, arg_operands, @@ -369,7 +371,8 @@ fn eval_fn_call( let arg_ty = self.operand_ty(arg); args.push((arg_val, arg_ty)); } - if self.eval_fn_call_inner( + if M::eval_fn_call( + self, instance, destination, arg_operands, @@ -416,48 +419,6 @@ fn eval_fn_call( } } - /// Returns Ok(true) when the function was handled completely due to mir not being available - fn eval_fn_call_inner( - &mut self, - instance: ty::Instance<'tcx>, - destination: Option<(Lvalue<'tcx>, mir::BasicBlock)>, - arg_operands: &[mir::Operand<'tcx>], - span: Span, - sig: ty::FnSig<'tcx>, - ) -> EvalResult<'tcx, bool> { - trace!("eval_fn_call_inner: {:#?}, {:#?}", instance, destination); - - // Only trait methods can have a Self parameter. - - let mir = match self.load_mir(instance.def) { - Ok(mir) => mir, - Err(EvalError::NoMirFor(path)) => { - M::call_missing_fn(self, instance, destination, arg_operands, sig, path)?; - return Ok(true); - }, - Err(other) => return Err(other), - }; - - if !self.tcx.is_const_fn(instance.def_id()) { - M::check_non_const_fn_call(instance)?; - } - - let (return_lvalue, return_to_block) = match destination { - Some((lvalue, block)) => (lvalue, StackPopCleanup::Goto(block)), - None => (Lvalue::undef(), StackPopCleanup::None), - }; - - self.push_stack_frame( - instance, - span, - mir, - return_lvalue, - return_to_block, - )?; - - Ok(false) - } - pub fn read_discriminant_value(&self, adt_ptr: MemoryPointer, adt_ty: Ty<'tcx>) -> EvalResult<'tcx, u128> { use rustc::ty::layout::Layout::*; let adt_layout = self.type_layout(adt_ty)?; -- 2.44.0