From ab90500d8c56bd83def08c5f5dc815074dceae52 Mon Sep 17 00:00:00 2001 From: Ralf Jung Date: Wed, 24 May 2017 15:11:29 -0700 Subject: [PATCH 01/20] Make println!("String") work miri complains about a memory leak when the program terminates. This may be related to thread-local dtors not running. --- src/terminator/mod.rs | 50 ++++++++++++++++++++++++++++++++++--------- 1 file changed, 40 insertions(+), 10 deletions(-) diff --git a/src/terminator/mod.rs b/src/terminator/mod.rs index a3ff8c1b54ba..d098801ae92a 100644 --- a/src/terminator/mod.rs +++ b/src/terminator/mod.rs @@ -367,19 +367,32 @@ impl<'a, 'tcx> EvalContext<'a, 'tcx> { // Only trait methods can have a Self parameter. + // Intercept some methods (even if we can find MIR for them) + if let ty::InstanceDef::Item(def_id) = instance.def { + match &self.tcx.item_path_str(def_id)[..] { + "std::sys::imp::fast_thread_local::register_dtor" => { + // Just don't execute this one, we don't handle all this thread-local stuff anyway. + self.goto_block(destination.unwrap().1); + return Ok(true) + } + _ => {} + } + } + let mir = match self.load_mir(instance.def) { Ok(mir) => mir, Err(EvalError::NoMirFor(path)) => { - match &path[..] { - // let's just ignore all output for now + return match &path[..] { + // Intercept some methods if we cannot find their MIR. "std::io::_print" => { + trace!("Ignoring output."); self.goto_block(destination.unwrap().1); - return Ok(true); + Ok(true) }, - "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::thread::Builder::new" => Err(EvalError::Unimplemented("miri does not support threading".to_owned())), + "std::env::args" => Err(EvalError::Unimplemented("miri does not support program arguments".to_owned())), "std::panicking::rust_panic_with_hook" | - "std::rt::begin_panic_fmt" => return Err(EvalError::Panic), + "std::rt::begin_panic_fmt" => Err(EvalError::Panic), "std::panicking::panicking" | "std::rt::panicking" => { let (lval, block) = destination.expect("std::rt::panicking does not diverge"); @@ -387,11 +400,10 @@ impl<'a, 'tcx> EvalContext<'a, 'tcx> { let bool = self.tcx.types.bool; self.write_primval(lval, PrimVal::from_bool(false), bool)?; self.goto_block(block); - return Ok(true); + Ok(true) } - _ => {}, - } - return Err(EvalError::NoMirFor(path)); + _ => Err(EvalError::NoMirFor(path)), + }; }, Err(other) => return Err(other), }; @@ -569,6 +581,24 @@ impl<'a, 'tcx> EvalContext<'a, 'tcx> { } self.write_value(Value::ByVal(PrimVal::Bytes(0)), dest, dest_ty)?; } + + "write" => { + let fd = self.value_to_primval(args[0], usize)?.to_u64()?; + let buf = args[1].read_ptr(&self.memory)?; + let n = self.value_to_primval(args[2], usize)?.to_u64()?; + trace!("Called write({:?}, {:?}, {:?})", fd, buf, n); + let result = if fd == 1 { // stdout + use std::io::{self, Write}; + + let buf_cont = self.memory.read_bytes(buf, n)?; + let res = io::stdout().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)?; + } // unix panic code inside libstd will read the return value of this function "pthread_rwlock_rdlock" => { From 1ae01b422b7c961b35d2f70550f1f795464824a4 Mon Sep 17 00:00:00 2001 From: Ralf Jung Date: Wed, 24 May 2017 15:36:48 -0700 Subject: [PATCH 02/20] add instructions for how to compile libstd with xargo --- README.md | 22 ++++++++++++++++++++++ xargo/Cargo.lock | 4 ++++ xargo/Cargo.toml | 6 ++++++ xargo/Xargo.toml | 2 ++ xargo/src/lib.rs | 0 5 files changed, 34 insertions(+) create mode 100644 xargo/Cargo.lock create mode 100644 xargo/Cargo.toml create mode 100644 xargo/Xargo.toml create mode 100644 xargo/src/lib.rs diff --git a/README.md b/README.md index aa8e0315b768..aa89df9731fa 100644 --- a/README.md +++ b/README.md @@ -54,6 +54,28 @@ Then, inside your own project, use `cargo +nightly miri` to run your project, if a bin project, or run `cargo +nightly miri test` to run all tests in your project through miri. +## Running miri with full libstd + +Per default libstd does not contain the MIR of non-polymorphic functions. When +miri hits a call to such a function, execution terminates. To fix this, it is +possible to compile libstd with full MIR: + +```sh +cargo install xargo +cd xargo/ +RUSTFLAGS='-Zalways-encode-mir' xargo build --target `rustc -vV | egrep '^host: ' | sed 's/^host: //'` +``` + +Now you can run miri against the libstd compiled by xargo: + +```sh +cargo run --bin miri -- --sysroot ~/.xargo/HOST tests/run-pass/vecs.rs +``` + +Notice that you will have to re-run the last step of the preparations above when +your toolchain changes (e.g., when you update the nightly). Also, xargo doesn't +currently work with nightlies newer than 2017-04-23. + ## Contributing and getting help Check out the issues on this GitHub repository for some ideas. There's lots that diff --git a/xargo/Cargo.lock b/xargo/Cargo.lock new file mode 100644 index 000000000000..031ad9a87954 --- /dev/null +++ b/xargo/Cargo.lock @@ -0,0 +1,4 @@ +[root] +name = "miri-xargo" +version = "0.0.0" + diff --git a/xargo/Cargo.toml b/xargo/Cargo.toml new file mode 100644 index 000000000000..9129c105b112 --- /dev/null +++ b/xargo/Cargo.toml @@ -0,0 +1,6 @@ +[package] +name = "miri-xargo" +description = "A dummy project for building libstd with xargo." +version = "0.0.0" + +[dependencies] diff --git a/xargo/Xargo.toml b/xargo/Xargo.toml new file mode 100644 index 000000000000..32f45c4a9816 --- /dev/null +++ b/xargo/Xargo.toml @@ -0,0 +1,2 @@ +[dependencies] +std = {features = ["panic_unwind", "jemalloc"]} diff --git a/xargo/src/lib.rs b/xargo/src/lib.rs new file mode 100644 index 000000000000..e69de29bb2d1 From 33d42f4b8236cef23980dbecef6e86e3e9b27c57 Mon Sep 17 00:00:00 2001 From: Ralf Jung Date: Wed, 24 May 2017 21:01:13 -0700 Subject: [PATCH 03/20] also support writing to stderr --- src/terminator/mod.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/terminator/mod.rs b/src/terminator/mod.rs index d098801ae92a..4834bba5b484 100644 --- a/src/terminator/mod.rs +++ b/src/terminator/mod.rs @@ -587,11 +587,11 @@ impl<'a, 'tcx> EvalContext<'a, 'tcx> { let buf = args[1].read_ptr(&self.memory)?; let n = self.value_to_primval(args[2], usize)?.to_u64()?; trace!("Called write({:?}, {:?}, {:?})", fd, buf, n); - let result = if fd == 1 { // stdout + 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 = io::stdout().write(buf_cont); + 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); From 452cc9b396507dc94e2047cb4d2ad843b23eccc7 Mon Sep 17 00:00:00 2001 From: Ralf Jung Date: Thu, 25 May 2017 15:19:38 -0700 Subject: [PATCH 04/20] handle statics with linkage: initialize them with NULL --- src/lvalue.rs | 9 +++++++++ src/step.rs | 7 +++++++ 2 files changed, 16 insertions(+) diff --git a/src/lvalue.rs b/src/lvalue.rs index 9660b8f4eec8..cad4ca9d0290 100644 --- a/src/lvalue.rs +++ b/src/lvalue.rs @@ -104,6 +104,15 @@ impl<'tcx> Global<'tcx> { initialized: false, } } + + pub(super) fn initialized(ty: Ty<'tcx>, value: Value, mutable: bool) -> Self { + Global { + value, + mutable, + ty, + initialized: true, + } + } } impl<'a, 'tcx> EvalContext<'a, 'tcx> { diff --git a/src/step.rs b/src/step.rs index 27eb26e333ac..110a7b2252a8 100644 --- a/src/step.rs +++ b/src/step.rs @@ -13,6 +13,7 @@ use error::{EvalResult, EvalError}; use eval_context::{EvalContext, StackPopCleanup}; use lvalue::{Global, GlobalId, Lvalue}; use value::{Value, PrimVal}; +use memory::Pointer; use syntax::codemap::Span; impl<'a, 'tcx> EvalContext<'a, 'tcx> { @@ -158,6 +159,11 @@ impl<'a, 'b, 'tcx> ConstantExtractor<'a, 'b, 'tcx> { if self.ecx.globals.contains_key(&cid) { return; } + if self.ecx.tcx.has_attr(def_id, "linkage") { + trace!("Initializing an extern global with NULL"); + self.ecx.globals.insert(cid, Global::initialized(self.ecx.tcx.type_of(def_id), Value::ByVal(PrimVal::Ptr(Pointer::from_int(0))), !shared)); + return; + } self.try(|this| { let mir = this.ecx.load_mir(instance.def)?; this.ecx.globals.insert(cid, Global::uninitialized(mir.return_ty)); @@ -178,6 +184,7 @@ impl<'a, 'b, 'tcx> ConstantExtractor<'a, 'b, 'tcx> { ) }); } + fn try EvalResult<'tcx>>(&mut self, f: F) { if let Ok(ref mut n) = *self.new_constants { *n += 1; From 238211e1b3a2412f2c36b043071663a17a8c3501 Mon Sep 17 00:00:00 2001 From: Ralf Jung Date: Thu, 25 May 2017 16:40:13 -0700 Subject: [PATCH 05/20] implement TLS --- src/error.rs | 6 ++++ src/memory.rs | 49 +++++++++++++++++++++++++++ src/terminator/mod.rs | 77 ++++++++++++++++++++++++++++++------------- 3 files changed, 110 insertions(+), 22 deletions(-) diff --git a/src/error.rs b/src/error.rs index fd692ef8b64a..25d939be3ab3 100644 --- a/src/error.rs +++ b/src/error.rs @@ -38,6 +38,8 @@ pub enum EvalError<'tcx> { }, ExecutionTimeLimitReached, StackFrameLimitReached, + OutOfTls, + TlsOutOfBounds, AlignmentCheckFailed { required: u64, has: u64, @@ -101,6 +103,10 @@ impl<'tcx> Error for EvalError<'tcx> { "reached the configured maximum execution time", EvalError::StackFrameLimitReached => "reached the configured maximum number of stack frames", + EvalError::OutOfTls => + "reached the maximum number of representable TLS keys", + EvalError::TlsOutOfBounds => + "accessed an invalid (unallocated) TLS key", EvalError::AlignmentCheckFailed{..} => "tried to execute a misaligned read or write", EvalError::CalledClosureAsFunction => diff --git a/src/memory.rs b/src/memory.rs index f2440d3d7267..bb5a1d6f2690 100644 --- a/src/memory.rs +++ b/src/memory.rs @@ -15,6 +15,8 @@ use value::PrimVal; #[derive(Copy, Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)] pub struct AllocId(pub u64); +pub type TlsKey = usize; + impl fmt::Display for AllocId { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "{}", self.0) @@ -149,6 +151,12 @@ pub struct Memory<'a, 'tcx> { /// A cache for basic byte allocations keyed by their contents. This is used to deduplicate /// allocations for string and bytestring literals. literal_alloc_cache: HashMap, AllocId>, + + /// pthreads-style Thread-local storage. We only have one thread, so this is just a map from TLS keys (indices into the vector) to the pointer stored there. + thread_local: HashMap, + + /// The Key to use for the next thread-local allocation. + next_thread_local: TlsKey, } const ZST_ALLOC_ID: AllocId = AllocId(0); @@ -167,6 +175,8 @@ impl<'a, 'tcx> Memory<'a, 'tcx> { packed: BTreeSet::new(), static_alloc: HashSet::new(), literal_alloc_cache: HashMap::new(), + thread_local: HashMap::new(), + next_thread_local: 0, } } @@ -345,6 +355,45 @@ impl<'a, 'tcx> Memory<'a, 'tcx> { pub(crate) fn clear_packed(&mut self) { self.packed.clear(); } + + pub(crate) fn create_tls_key(&mut self) -> TlsKey { + let new_key = self.next_thread_local; + self.next_thread_local += 1; + self.thread_local.insert(new_key, Pointer::from_int(0)); + trace!("New TLS key allocated: {}", new_key); + return new_key; + } + + pub(crate) fn delete_tls_key(&mut self, key: TlsKey) -> EvalResult<'tcx> { + return match self.thread_local.remove(&key) { + Some(_) => { + trace!("TLS key {} removed", key); + Ok(()) + }, + None => Err(EvalError::TlsOutOfBounds) + } + } + + pub(crate) fn load_tls(&mut self, key: TlsKey) -> EvalResult<'tcx, Pointer> { + return match self.thread_local.get(&key) { + Some(&ptr) => { + trace!("TLS key {} loaded: {:?}", key, ptr); + Ok(ptr) + }, + None => Err(EvalError::TlsOutOfBounds) + } + } + + pub(crate) fn store_tls(&mut self, key: TlsKey, new_ptr: Pointer) -> EvalResult<'tcx> { + return match self.thread_local.get_mut(&key) { + Some(ptr) => { + trace!("TLS key {} stored: {:?}", key, new_ptr); + *ptr = new_ptr; + Ok(()) + }, + None => Err(EvalError::TlsOutOfBounds) + } + } } // The derived `Ord` impl sorts first by the first field, then, if the fields are the same diff --git a/src/terminator/mod.rs b/src/terminator/mod.rs index 4834bba5b484..94a221b32c77 100644 --- a/src/terminator/mod.rs +++ b/src/terminator/mod.rs @@ -1,6 +1,6 @@ use rustc::hir::def_id::DefId; use rustc::mir; -use rustc::ty::{self, Ty}; +use rustc::ty::{self, TypeVariants, Ty, TyS, TypeAndMut}; use rustc::ty::layout::Layout; use syntax::codemap::Span; use syntax::attr; @@ -9,7 +9,7 @@ use syntax::abi::Abi; use error::{EvalError, EvalResult}; use eval_context::{EvalContext, IntegerExt, StackPopCleanup, is_inhabited}; use lvalue::Lvalue; -use memory::Pointer; +use memory::{Pointer, TlsKey}; use value::PrimVal; use value::Value; use rustc_data_structures::indexed_vec::Idx; @@ -367,18 +367,6 @@ impl<'a, 'tcx> EvalContext<'a, 'tcx> { // Only trait methods can have a Self parameter. - // Intercept some methods (even if we can find MIR for them) - if let ty::InstanceDef::Item(def_id) = instance.def { - match &self.tcx.item_path_str(def_id)[..] { - "std::sys::imp::fast_thread_local::register_dtor" => { - // Just don't execute this one, we don't handle all this thread-local stuff anyway. - self.goto_block(destination.unwrap().1); - return Ok(true) - } - _ => {} - } - } - let mir = match self.load_mir(instance.def) { Ok(mir) => mir, Err(EvalError::NoMirFor(path)) => { @@ -480,7 +468,7 @@ impl<'a, 'tcx> EvalContext<'a, 'tcx> { fn call_c_abi( &mut self, def_id: DefId, - args: &[mir::Operand<'tcx>], + arg_operands: &[mir::Operand<'tcx>], dest: Lvalue<'tcx>, dest_ty: Ty<'tcx>, ) -> EvalResult<'tcx> { @@ -490,7 +478,7 @@ impl<'a, 'tcx> EvalContext<'a, 'tcx> { .unwrap_or(name) .as_str(); - let args_res: EvalResult> = args.iter() + let args_res: EvalResult> = arg_operands.iter() .map(|arg| self.eval_operand(arg)) .collect(); let args = args_res?; @@ -555,9 +543,9 @@ impl<'a, 'tcx> EvalContext<'a, 'tcx> { 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.write_value(Value::ByVal(PrimVal::Ptr(new_ptr)), dest, dest_ty)?; + self.write_primval(dest, PrimVal::Ptr(new_ptr), dest_ty)?; } else { - self.write_value(Value::ByVal(PrimVal::Bytes(0)), dest, dest_ty)?; + self.write_primval(dest, PrimVal::Bytes(0), dest_ty)?; } } @@ -567,9 +555,9 @@ impl<'a, 'tcx> EvalContext<'a, 'tcx> { 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.write_value(Value::ByVal(PrimVal::Ptr(new_ptr)), dest, dest_ty)?; + self.write_primval(dest, PrimVal::Ptr(new_ptr), dest_ty)?; } else { - self.write_value(Value::ByVal(PrimVal::Bytes(0)), dest, dest_ty)?; + self.write_primval(dest, PrimVal::Bytes(0), dest_ty)?; } } @@ -579,7 +567,7 @@ impl<'a, 'tcx> EvalContext<'a, 'tcx> { let name = self.memory.read_c_str(name_ptr)?; info!("ignored env var request for `{:?}`", ::std::str::from_utf8(name)); } - self.write_value(Value::ByVal(PrimVal::Bytes(0)), dest, dest_ty)?; + self.write_primval(dest, PrimVal::Bytes(0), dest_ty)?; } "write" => { @@ -605,9 +593,54 @@ impl<'a, 'tcx> EvalContext<'a, 'tcx> { self.write_primval(dest, PrimVal::Bytes(0), dest_ty)?; } + // Hook pthread calls that go to the thread-local storage memory subsystem + "pthread_key_create" => { + let key = self.memory.create_tls_key(); + let key_ptr = args[0].read_ptr(&self.memory)?; + + // Figure out how large a pthread TLS key actually is. This is libc::pthread_key_t. + let key_size = match self.operand_ty(&arg_operands[0]) { + &TyS { sty: TypeVariants::TyRawPtr(TypeAndMut { ty, .. }), .. } => { + let layout = self.type_layout(ty)?; + layout.size(&self.tcx.data_layout) + } + _ => return Err(EvalError::Unimplemented("Wrong signature used for pthread_key_create: First argument must be a raw pointer.".to_owned())) + }; + + // Write the key into the memory where key_ptr wants it + if key >= (1 << key_size.bits()) { + return Err(EvalError::OutOfTls); + } + self.memory.write_int(key_ptr, key as i128, key_size.bytes())?; + + // Return success (0) + self.write_primval(dest, PrimVal::Bytes(0), 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_primval(dest, PrimVal::Bytes(0), 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_primval(dest, PrimVal::Ptr(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].read_ptr(&self.memory)?; + self.memory.store_tls(key, new_ptr)?; + + // Return success (0) + self.write_primval(dest, PrimVal::Bytes(0), dest_ty)?; + } + link_name if link_name.starts_with("pthread_") => { warn!("ignoring C ABI call: {}", link_name); - return Ok(()); }, _ => { From a66f359d91d2baf2d4224832456b1f9fa7ed0740 Mon Sep 17 00:00:00 2001 From: Ralf Jung Date: Thu, 25 May 2017 22:38:07 -0700 Subject: [PATCH 06/20] support TLS destructors --- src/error.rs | 2 ++ src/memory.rs | 51 ++++++++++++++++++++++++++++++++----------- src/step.rs | 32 ++++++++++++++++++++++----- src/terminator/mod.rs | 21 ++++++++++++++---- 4 files changed, 84 insertions(+), 22 deletions(-) diff --git a/src/error.rs b/src/error.rs index 25d939be3ab3..7b6542bff934 100644 --- a/src/error.rs +++ b/src/error.rs @@ -40,6 +40,7 @@ pub enum EvalError<'tcx> { StackFrameLimitReached, OutOfTls, TlsOutOfBounds, + AbiViolation(String), AlignmentCheckFailed { required: u64, has: u64, @@ -107,6 +108,7 @@ impl<'tcx> Error for EvalError<'tcx> { "reached the maximum number of representable TLS keys", EvalError::TlsOutOfBounds => "accessed an invalid (unallocated) TLS key", + EvalError::AbiViolation(ref msg) => msg, EvalError::AlignmentCheckFailed{..} => "tried to execute a misaligned read or write", EvalError::CalledClosureAsFunction => diff --git a/src/memory.rs b/src/memory.rs index bb5a1d6f2690..0280ad4c379b 100644 --- a/src/memory.rs +++ b/src/memory.rs @@ -15,8 +15,6 @@ use value::PrimVal; #[derive(Copy, Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)] pub struct AllocId(pub u64); -pub type TlsKey = usize; - impl fmt::Display for AllocId { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "{}", self.0) @@ -100,6 +98,19 @@ impl Pointer { pub fn never_ptr() -> Self { Pointer::new(NEVER_ALLOC_ID, 0) } + + pub fn is_null_ptr(&self) -> bool { + // FIXME: Is this the right way? + return *self == Pointer::from_int(0) + } +} + +pub type TlsKey = usize; + +#[derive(Copy, Clone, Debug)] +pub struct TlsEntry<'tcx> { + data: Pointer, // will eventually become a map from thread IDs to pointers + dtor: Option>, } //////////////////////////////////////////////////////////////////////////////// @@ -153,7 +164,7 @@ pub struct Memory<'a, 'tcx> { literal_alloc_cache: HashMap, AllocId>, /// pthreads-style Thread-local storage. We only have one thread, so this is just a map from TLS keys (indices into the vector) to the pointer stored there. - thread_local: HashMap, + thread_local: HashMap>, /// The Key to use for the next thread-local allocation. next_thread_local: TlsKey, @@ -356,11 +367,11 @@ impl<'a, 'tcx> Memory<'a, 'tcx> { self.packed.clear(); } - pub(crate) fn create_tls_key(&mut self) -> TlsKey { + pub(crate) fn create_tls_key(&mut self, dtor: Option>) -> TlsKey { let new_key = self.next_thread_local; self.next_thread_local += 1; - self.thread_local.insert(new_key, Pointer::from_int(0)); - trace!("New TLS key allocated: {}", new_key); + self.thread_local.insert(new_key, TlsEntry { data: Pointer::from_int(0), dtor }); + trace!("New TLS key allocated: {} with dtor {:?}", new_key, dtor); return new_key; } @@ -376,24 +387,38 @@ impl<'a, 'tcx> Memory<'a, 'tcx> { pub(crate) fn load_tls(&mut self, key: TlsKey) -> EvalResult<'tcx, Pointer> { return match self.thread_local.get(&key) { - Some(&ptr) => { - trace!("TLS key {} loaded: {:?}", key, ptr); - Ok(ptr) + Some(&TlsEntry { data, .. }) => { + trace!("TLS key {} loaded: {:?}", key, data); + Ok(data) }, None => Err(EvalError::TlsOutOfBounds) } } - pub(crate) fn store_tls(&mut self, key: TlsKey, new_ptr: Pointer) -> EvalResult<'tcx> { + pub(crate) fn store_tls(&mut self, key: TlsKey, new_data: Pointer) -> EvalResult<'tcx> { return match self.thread_local.get_mut(&key) { - Some(ptr) => { - trace!("TLS key {} stored: {:?}", key, new_ptr); - *ptr = new_ptr; + Some(&mut TlsEntry { ref mut data, .. }) => { + trace!("TLS key {} stored: {:?}", key, new_data); + *data = new_data; Ok(()) }, None => Err(EvalError::TlsOutOfBounds) } } + + // Returns a dtor and its argument, if one is supposed to run + pub(crate) fn fetch_tls_dtor(&mut self) -> Option<(ty::Instance<'tcx>, Pointer)> { + for (_, &mut TlsEntry { ref mut data, dtor }) in self.thread_local.iter_mut() { + if !data.is_null_ptr() { + if let Some(dtor) = dtor { + let old_data = *data; + *data = Pointer::from_int(0); + return Some((dtor, old_data)); + } + } + } + return None; + } } // The derived `Ord` impl sorts first by the first field, then, if the fields are the same diff --git a/src/step.rs b/src/step.rs index 110a7b2252a8..658a3445c433 100644 --- a/src/step.rs +++ b/src/step.rs @@ -14,7 +14,7 @@ use eval_context::{EvalContext, StackPopCleanup}; use lvalue::{Global, GlobalId, Lvalue}; use value::{Value, PrimVal}; use memory::Pointer; -use syntax::codemap::Span; +use syntax::codemap::{Span, DUMMY_SP}; impl<'a, 'tcx> EvalContext<'a, 'tcx> { pub fn inc_step_counter_and_check_limit(&mut self, n: u64) -> EvalResult<'tcx> { @@ -32,6 +32,28 @@ impl<'a, 'tcx> EvalContext<'a, 'tcx> { self.memory.clear_packed(); self.inc_step_counter_and_check_limit(1)?; if self.stack.is_empty() { + if let Some((instance, ptr)) = self.memory.fetch_tls_dtor() { + trace!("Running TLS dtor {:?} on {:?}", instance, ptr); + // TODO: Potientiually, this has to support all the other possible instances? See eval_fn_call in terminator/mod.rs + let mir = self.load_mir(instance.def)?; + // FIXME: Are these the right dummy values? + self.push_stack_frame( + instance, + DUMMY_SP, + mir, + Lvalue::from_ptr(Pointer::zst_ptr()), + StackPopCleanup::None, + )?; + if let Some(arg_local) = self.frame().mir.args_iter().next() { + let dest = self.eval_lvalue(&mir::Lvalue::Local(arg_local))?; + let ty = self.tcx.mk_mut_ptr(self.tcx.types.u8); + self.write_value(Value::ByVal(PrimVal::Ptr(ptr)), dest, ty)?; + } else { + return Err(EvalError::AbiViolation("TLS dtor does not take enough arguments.".to_owned())); + } + + return Ok(true); + } return Ok(false); } @@ -49,11 +71,11 @@ impl<'a, 'tcx> EvalContext<'a, 'tcx> { mir, new_constants: &mut new, }.visit_statement(block, stmt, mir::Location { block, statement_index: stmt_id }); + // if ConstantExtractor added new frames, we don't execute anything here + // but await the next call to step if new? == 0 { self.statement(stmt)?; } - // if ConstantExtractor added new frames, we don't execute anything here - // but await the next call to step return Ok(true); } @@ -66,11 +88,11 @@ impl<'a, 'tcx> EvalContext<'a, 'tcx> { mir, new_constants: &mut new, }.visit_terminator(block, terminator, mir::Location { block, statement_index: stmt_id }); + // if ConstantExtractor added new frames, we don't execute anything here + // but await the next call to step if new? == 0 { self.terminator(terminator)?; } - // if ConstantExtractor added new frames, we don't execute anything here - // but await the next call to step Ok(true) } diff --git a/src/terminator/mod.rs b/src/terminator/mod.rs index 94a221b32c77..aaf03ea665f6 100644 --- a/src/terminator/mod.rs +++ b/src/terminator/mod.rs @@ -221,6 +221,7 @@ impl<'a, 'tcx> EvalContext<'a, 'tcx> { args.push((arg_val, arg_ty)); } + // Push the stack frame, and potentially be entirely done if the call got hooked if self.eval_fn_call_inner( instance, destination, @@ -229,6 +230,7 @@ impl<'a, 'tcx> EvalContext<'a, 'tcx> { return Ok(()); } + // Pass the arguments let mut arg_locals = self.frame().mir.args_iter(); trace!("ABI: {:?}", sig.abi); trace!("arg_locals: {:?}", self.frame().mir.args_iter().collect::>()); @@ -595,19 +597,30 @@ impl<'a, 'tcx> EvalContext<'a, 'tcx> { // Hook pthread calls that go to the thread-local storage memory subsystem "pthread_key_create" => { - let key = self.memory.create_tls_key(); let key_ptr = args[0].read_ptr(&self.memory)?; - + + // Extract the function type out of the signature (that seems easier than constructing it ourselves...) + // FIXME: Or should we instead construct the type we expect it to have? + let dtor_fn_ty = match self.operand_ty(&arg_operands[1]) { + &TyS { sty: TypeVariants::TyAdt(_, ref substs), .. } => { + substs.type_at(0) + } + _ => return Err(EvalError::AbiViolation("Wrong signature used for pthread_key_create: Second argument must be option of a function pointer.".to_owned())) + }; + let dtor_ptr = self.value_to_primval(args[1], dtor_fn_ty)?.to_ptr()?; + let dtor = if dtor_ptr.is_null_ptr() { None } else { Some(self.memory.get_fn(dtor_ptr.alloc_id)?) }; + // Figure out how large a pthread TLS key actually is. This is libc::pthread_key_t. let key_size = match self.operand_ty(&arg_operands[0]) { &TyS { sty: TypeVariants::TyRawPtr(TypeAndMut { ty, .. }), .. } => { let layout = self.type_layout(ty)?; layout.size(&self.tcx.data_layout) } - _ => return Err(EvalError::Unimplemented("Wrong signature used for pthread_key_create: First argument must be a raw pointer.".to_owned())) + _ => return Err(EvalError::AbiViolation("Wrong signature used for pthread_key_create: First argument must be a raw pointer.".to_owned())) }; - // Write the key into the memory where key_ptr wants it + // Create key and write it into the memory where key_ptr wants it + let key = self.memory.create_tls_key(dtor); if key >= (1 << key_size.bits()) { return Err(EvalError::OutOfTls); } From 14b16dcf45d00875e87e609362efb7d7d6cd84c3 Mon Sep 17 00:00:00 2001 From: Ralf Jung Date: Fri, 26 May 2017 10:19:38 -0700 Subject: [PATCH 07/20] use proper span for TLS dtors; fix some nits --- src/memory.rs | 1 - src/step.rs | 7 +++---- src/terminator/mod.rs | 12 ++++++------ 3 files changed, 9 insertions(+), 11 deletions(-) diff --git a/src/memory.rs b/src/memory.rs index 0280ad4c379b..bfdc45c921d1 100644 --- a/src/memory.rs +++ b/src/memory.rs @@ -100,7 +100,6 @@ impl Pointer { } pub fn is_null_ptr(&self) -> bool { - // FIXME: Is this the right way? return *self == Pointer::from_int(0) } } diff --git a/src/step.rs b/src/step.rs index 658a3445c433..84f849c61225 100644 --- a/src/step.rs +++ b/src/step.rs @@ -14,7 +14,7 @@ use eval_context::{EvalContext, StackPopCleanup}; use lvalue::{Global, GlobalId, Lvalue}; use value::{Value, PrimVal}; use memory::Pointer; -use syntax::codemap::{Span, DUMMY_SP}; +use syntax::codemap::Span; impl<'a, 'tcx> EvalContext<'a, 'tcx> { pub fn inc_step_counter_and_check_limit(&mut self, n: u64) -> EvalResult<'tcx> { @@ -36,10 +36,9 @@ impl<'a, 'tcx> EvalContext<'a, 'tcx> { trace!("Running TLS dtor {:?} on {:?}", instance, ptr); // TODO: Potientiually, this has to support all the other possible instances? See eval_fn_call in terminator/mod.rs let mir = self.load_mir(instance.def)?; - // FIXME: Are these the right dummy values? self.push_stack_frame( instance, - DUMMY_SP, + mir.span, mir, Lvalue::from_ptr(Pointer::zst_ptr()), StackPopCleanup::None, @@ -51,7 +50,7 @@ impl<'a, 'tcx> EvalContext<'a, 'tcx> { } else { return Err(EvalError::AbiViolation("TLS dtor does not take enough arguments.".to_owned())); } - + return Ok(true); } return Ok(false); diff --git a/src/terminator/mod.rs b/src/terminator/mod.rs index aaf03ea665f6..9d55b9720bb5 100644 --- a/src/terminator/mod.rs +++ b/src/terminator/mod.rs @@ -1,6 +1,6 @@ use rustc::hir::def_id::DefId; use rustc::mir; -use rustc::ty::{self, TypeVariants, Ty, TyS, TypeAndMut}; +use rustc::ty::{self, TypeVariants, Ty, TypeAndMut}; use rustc::ty::layout::Layout; use syntax::codemap::Span; use syntax::attr; @@ -600,19 +600,19 @@ impl<'a, 'tcx> EvalContext<'a, 'tcx> { let key_ptr = args[0].read_ptr(&self.memory)?; // Extract the function type out of the signature (that seems easier than constructing it ourselves...) - // FIXME: Or should we instead construct the type we expect it to have? - let dtor_fn_ty = match self.operand_ty(&arg_operands[1]) { - &TyS { sty: TypeVariants::TyAdt(_, ref substs), .. } => { + let dtor_fn_ty = match self.operand_ty(&arg_operands[1]).sty { + TypeVariants::TyAdt(_, ref substs) => { substs.type_at(0) } _ => return Err(EvalError::AbiViolation("Wrong signature used for pthread_key_create: Second argument must be option of a function pointer.".to_owned())) }; let dtor_ptr = self.value_to_primval(args[1], dtor_fn_ty)?.to_ptr()?; + // TODO: The null-pointer case here is entirely untested let dtor = if dtor_ptr.is_null_ptr() { None } else { Some(self.memory.get_fn(dtor_ptr.alloc_id)?) }; // Figure out how large a pthread TLS key actually is. This is libc::pthread_key_t. - let key_size = match self.operand_ty(&arg_operands[0]) { - &TyS { sty: TypeVariants::TyRawPtr(TypeAndMut { ty, .. }), .. } => { + let key_size = match self.operand_ty(&arg_operands[0]).sty { + TypeVariants::TyRawPtr(TypeAndMut { ty, .. }) => { let layout = self.type_layout(ty)?; layout.size(&self.tcx.data_layout) } From 55438fe5bf2eee99511d811898dd48a70a0cecd1 Mon Sep 17 00:00:00 2001 From: Ralf Jung Date: Fri, 26 May 2017 12:25:25 -0700 Subject: [PATCH 08/20] unify the way we intercept missing MIR and C ABI calls; only intercept C ABI calls if MIR is missing --- src/terminator/mod.rs | 90 ++++++++++++++++++++++++++----------------- 1 file changed, 55 insertions(+), 35 deletions(-) diff --git a/src/terminator/mod.rs b/src/terminator/mod.rs index 9d55b9720bb5..119e7249a7c0 100644 --- a/src/terminator/mod.rs +++ b/src/terminator/mod.rs @@ -172,7 +172,9 @@ impl<'a, 'tcx> EvalContext<'a, 'tcx> { if self.eval_fn_call_inner( instance, destination, + arg_operands, span, + sig, )? { return Ok(()); } @@ -202,18 +204,6 @@ impl<'a, 'tcx> EvalContext<'a, 'tcx> { Ok(()) } ty::InstanceDef::Item(_) => { - match sig.abi { - Abi::C => { - let ty = sig.output(); - let (ret, target) = destination.unwrap(); - self.call_c_abi(instance.def_id(), arg_operands, ret, ty)?; - self.dump_local(ret); - self.goto_block(target); - return Ok(()); - }, - Abi::Rust | Abi::RustCall => {}, - _ => unimplemented!(), - } let mut args = Vec::new(); for arg in arg_operands { let arg_val = self.eval_operand(arg)?; @@ -225,7 +215,9 @@ impl<'a, 'tcx> EvalContext<'a, 'tcx> { if self.eval_fn_call_inner( instance, destination, + arg_operands, span, + sig, )? { return Ok(()); } @@ -236,7 +228,7 @@ impl<'a, 'tcx> EvalContext<'a, 'tcx> { trace!("arg_locals: {:?}", self.frame().mir.args_iter().collect::>()); trace!("arg_operands: {:?}", arg_operands); match sig.abi { - Abi::Rust => { + Abi::Rust | Abi::C => { for (arg_local, (arg_val, arg_ty)) in arg_locals.zip(args) { let dest = self.eval_lvalue(&mir::Lvalue::Local(arg_local))?; self.write_value(arg_val, dest, arg_ty)?; @@ -316,7 +308,9 @@ impl<'a, 'tcx> EvalContext<'a, 'tcx> { if self.eval_fn_call_inner( instance, destination, + arg_operands, span, + sig, )? { return Ok(()); } @@ -363,7 +357,9 @@ impl<'a, 'tcx> EvalContext<'a, 'tcx> { &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); @@ -372,28 +368,8 @@ impl<'a, 'tcx> EvalContext<'a, 'tcx> { let mir = match self.load_mir(instance.def) { Ok(mir) => mir, Err(EvalError::NoMirFor(path)) => { - return match &path[..] { - // Intercept some methods if we cannot find their MIR. - "std::io::_print" => { - trace!("Ignoring output."); - self.goto_block(destination.unwrap().1); - Ok(true) - }, - "std::thread::Builder::new" => Err(EvalError::Unimplemented("miri does not support threading".to_owned())), - "std::env::args" => Err(EvalError::Unimplemented("miri does not support program arguments".to_owned())), - "std::panicking::rust_panic_with_hook" | - "std::rt::begin_panic_fmt" => Err(EvalError::Panic), - "std::panicking::panicking" | - "std::rt::panicking" => { - let (lval, block) = destination.expect("std::rt::panicking does not diverge"); - // we abort on panic -> `std::rt::panicking` always returns false - let bool = self.tcx.types.bool; - self.write_primval(lval, PrimVal::from_bool(false), bool)?; - self.goto_block(block); - Ok(true) - } - _ => Err(EvalError::NoMirFor(path)), - }; + self.call_missing_fn(instance, destination, arg_operands, sig, path)?; + return Ok(true); }, Err(other) => return Err(other), }; @@ -466,6 +442,50 @@ impl<'a, 'tcx> EvalContext<'a, 'tcx> { assert!(nndiscr == 0 || nndiscr == 1); Ok(if not_null { nndiscr } else { 1 - nndiscr }) } + + /// Returns Ok() when the function was handled, fail otherwise + 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> { + if sig.abi == Abi::C { + // An external C function + let ty = sig.output(); + let (ret, target) = destination.unwrap(); + self.call_c_abi(instance.def_id(), arg_operands, ret, ty)?; + self.dump_local(ret); + self.goto_block(target); + return Ok(()); + } + + // 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. + match &path[..] { + "std::io::_print" => { + trace!("Ignoring output."); + self.goto_block(destination.unwrap().1); + Ok(()) + }, + "std::thread::Builder::new" => Err(EvalError::Unimplemented("miri does not support threading".to_owned())), + "std::env::args" => Err(EvalError::Unimplemented("miri does not support program arguments".to_owned())), + "std::panicking::rust_panic_with_hook" | + "std::rt::begin_panic_fmt" => Err(EvalError::Panic), + "std::panicking::panicking" | + "std::rt::panicking" => { + let (lval, block) = destination.expect("std::rt::panicking does not diverge"); + // we abort on panic -> `std::rt::panicking` always returns false + let bool = self.tcx.types.bool; + self.write_primval(lval, PrimVal::from_bool(false), bool)?; + self.goto_block(block); + Ok(()) + } + _ => Err(EvalError::NoMirFor(path)), + } + } fn call_c_abi( &mut self, From 720c5f874ea2eeef247eb8dddd2dcf4b93dada7d Mon Sep 17 00:00:00 2001 From: Ralf Jung Date: Fri, 26 May 2017 17:27:39 -0700 Subject: [PATCH 09/20] implement __rust_maybe_catch_panic --- src/terminator/mod.rs | 39 ++++++++++++++++++++++++++++++++++----- tests/run-pass/catch.rs | 9 +++++++++ 2 files changed, 43 insertions(+), 5 deletions(-) create mode 100644 tests/run-pass/catch.rs diff --git a/src/terminator/mod.rs b/src/terminator/mod.rs index 119e7249a7c0..e017a87f0346 100644 --- a/src/terminator/mod.rs +++ b/src/terminator/mod.rs @@ -456,9 +456,7 @@ impl<'a, 'tcx> EvalContext<'a, 'tcx> { // An external C function let ty = sig.output(); let (ret, target) = destination.unwrap(); - self.call_c_abi(instance.def_id(), arg_operands, ret, ty)?; - self.dump_local(ret); - self.goto_block(target); + self.call_c_abi(instance.def_id(), arg_operands, ret, ty, target)?; return Ok(()); } @@ -493,6 +491,7 @@ impl<'a, 'tcx> EvalContext<'a, 'tcx> { 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); @@ -539,6 +538,34 @@ impl<'a, 'tcx> EvalContext<'a, 'tcx> { self.write_primval(dest, PrimVal::Ptr(new_ptr), dest_ty)?; } + "__rust_maybe_catch_panic" => { + // 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].read_ptr(&self.memory)?; + let data = args[1].read_ptr(&self.memory)?; // FIXME: Why does value_to_primval(args[2], u8_ptr_ty)?.to_ptr()? here end up doing the Wrong Thing (TM)? + let f_instance = self.memory.get_fn(f.alloc_id)?; + self.write_primval(dest, PrimVal::Bytes(0), dest_ty)?; + + // Now we make a functon call. TODO: Consider making this re-usable? EvalContext::step does sth. similar for the TLS dtors, + // and of coruse eval_main. + let mir = self.load_mir(f_instance.def)?; + self.push_stack_frame( + f_instance, + mir.span, + mir, + Lvalue::from_ptr(Pointer::zst_ptr()), + 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 dest = self.eval_lvalue(&mir::Lvalue::Local(arg_local))?; + self.write_value(Value::ByVal(PrimVal::Ptr(data)), dest, u8_ptr_ty)?; + + // Don't fall through + // FIXME: Do we have to do self.dump_local(ret) anywhere? + return Ok(()); + } + "memcmp" => { let left = args[0].read_ptr(&self.memory)?; let right = args[1].read_ptr(&self.memory)?; @@ -591,7 +618,7 @@ impl<'a, 'tcx> EvalContext<'a, 'tcx> { } self.write_primval(dest, PrimVal::Bytes(0), dest_ty)?; } - + "write" => { let fd = self.value_to_primval(args[0], usize)?.to_u64()?; let buf = args[1].read_ptr(&self.memory)?; @@ -684,6 +711,8 @@ impl<'a, 'tcx> EvalContext<'a, 'tcx> { // 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(()) - } + } } diff --git a/tests/run-pass/catch.rs b/tests/run-pass/catch.rs new file mode 100644 index 000000000000..439edc82dde2 --- /dev/null +++ b/tests/run-pass/catch.rs @@ -0,0 +1,9 @@ +use std::panic::{catch_unwind, AssertUnwindSafe}; + +fn main() { + let mut i = 3; + let _ = catch_unwind(AssertUnwindSafe(|| {i -= 2;} )); + for _ in 0..i { + println!("I"); + } +} From cd6e3e643133fc1c6cdfd6067445236f9b776c7c Mon Sep 17 00:00:00 2001 From: Ralf Jung Date: Fri, 26 May 2017 17:36:16 -0700 Subject: [PATCH 10/20] If a "start" lang item incl. MIR is present, run that instead of running main directly This fixes the memory leaks when running a simple "Hello World" with MIR-libstd --- src/bin/miri.rs | 6 +- src/eval_context.rs | 147 ++++++++++++++++++++++++++------------ src/step.rs | 12 ++-- src/terminator/mod.rs | 30 +++++--- tests/compile-fail/oom.rs | 4 +- 5 files changed, 134 insertions(+), 65 deletions(-) diff --git a/src/bin/miri.rs b/src/bin/miri.rs index 3f0a6f778b42..4a82d45493b8 100644 --- a/src/bin/miri.rs +++ b/src/bin/miri.rs @@ -84,7 +84,7 @@ fn after_analysis<'a, 'tcx>(state: &mut CompileState<'a, 'tcx>) { if i.attrs.iter().any(|attr| attr.name().map_or(false, |n| n == "test")) { let did = self.1.hir.body_owner_def_id(body_id); println!("running test: {}", self.1.hir.def_path(did).to_string(self.1)); - miri::eval_main(self.1, did, self.0); + miri::eval_main(self.1, did, None, self.0); self.2.session.abort_if_errors(); } } @@ -95,7 +95,9 @@ fn after_analysis<'a, 'tcx>(state: &mut CompileState<'a, 'tcx>) { state.hir_crate.unwrap().visit_all_item_likes(&mut Visitor(limits, tcx, state)); } else if let Some((entry_node_id, _)) = *state.session.entry_fn.borrow() { let entry_def_id = tcx.hir.local_def_id(entry_node_id); - miri::eval_main(tcx, entry_def_id, limits); + let start_wrapper = tcx.lang_items.start_fn() + .and_then(|start_fn| if tcx.is_mir_available(start_fn) { Some(start_fn) } else { None }); + miri::eval_main(tcx, entry_def_id, start_wrapper, limits); state.session.abort_if_errors(); } else { diff --git a/src/eval_context.rs b/src/eval_context.rs index 77d9d9b20cd8..9a57e75da090 100644 --- a/src/eval_context.rs +++ b/src/eval_context.rs @@ -126,6 +126,7 @@ impl Default for ResourceLimits { impl<'a, 'tcx> EvalContext<'a, 'tcx> { pub fn new(tcx: TyCtxt<'a, 'tcx, 'tcx>, limits: ResourceLimits) -> Self { + // Register array drop glue code let source_info = mir::SourceInfo { span: DUMMY_SP, scope: mir::ARGUMENT_VISIBILITY_SCOPE @@ -852,7 +853,7 @@ impl<'a, 'tcx> EvalContext<'a, 'tcx> { let fn_ptr = self.memory.create_fn_alloc(instance); self.write_value(Value::ByVal(PrimVal::Ptr(fn_ptr)), dest, dest_ty)?; }, - ref other => bug!("reify fn pointer on {:?}", other), + ref other => bug!("closure fn pointer on {:?}", other), }, } } @@ -1676,62 +1677,120 @@ impl<'tcx> Frame<'tcx> { pub fn eval_main<'a, 'tcx: 'a>( tcx: TyCtxt<'a, 'tcx, 'tcx>, - def_id: DefId, + main_id: DefId, + start_wrapper: Option, limits: ResourceLimits, ) { - let mut ecx = EvalContext::new(tcx, limits); - let instance = ty::Instance::mono(tcx, def_id); - let mir = ecx.load_mir(instance.def).expect("main function's MIR not found"); + fn run_main<'a, 'tcx: 'a>( + ecx: &mut EvalContext<'a, 'tcx>, + main_id: DefId, + start_wrapper: Option, + ) -> EvalResult<'tcx> { + let main_instance = ty::Instance::mono(ecx.tcx, main_id); + let main_mir = ecx.load_mir(main_instance.def)?; - if !mir.return_ty.is_nil() || mir.arg_count != 0 { - let msg = "miri does not support main functions without `fn()` type signatures"; - tcx.sess.err(&EvalError::Unimplemented(String::from(msg)).to_string()); - return; + if !main_mir.return_ty.is_nil() || main_mir.arg_count != 0 { + return Err(EvalError::Unimplemented("miri does not support main functions without `fn()` type signatures".to_owned())); + } + + if let Some(start_id) = start_wrapper { + let start_instance = ty::Instance::mono(ecx.tcx, start_id); + let start_mir = ecx.load_mir(start_instance.def)?; + + if start_mir.arg_count != 3 { + return Err(EvalError::AbiViolation(format!("'start' lang item should have three arguments, but has {}", start_mir.arg_count))); + } + + // Push our stack frame + ecx.push_stack_frame( + start_instance, + start_mir.span, + start_mir, + Lvalue::from_ptr(Pointer::zst_ptr()), // we'll fix the return lvalue later + StackPopCleanup::None, + )?; + + let mut args = ecx.frame().mir.args_iter(); + + // First argument: pointer to main() + let main_ptr = ecx.memory.create_fn_alloc(main_instance); + let dest = ecx.eval_lvalue(&mir::Lvalue::Local(args.next().unwrap()))?; + let main_ty = main_instance.def.def_ty(ecx.tcx); + let main_ptr_ty = ecx.tcx.mk_fn_ptr(main_ty.fn_sig()); + ecx.write_value(Value::ByVal(PrimVal::Ptr(main_ptr)), dest, main_ptr_ty)?; + + // Second argument (argc): 0 + let dest = ecx.eval_lvalue(&mir::Lvalue::Local(args.next().unwrap()))?; + let ty = ecx.tcx.types.isize; + ecx.write_value(Value::ByVal(PrimVal::Bytes(0)), dest, ty)?; + + // Third argument (argv): 0 + let dest = ecx.eval_lvalue(&mir::Lvalue::Local(args.next().unwrap()))?; + let ty = ecx.tcx.mk_imm_ptr(ecx.tcx.mk_imm_ptr(ecx.tcx.types.u8)); + ecx.write_value(Value::ByVal(PrimVal::Bytes(0)), dest, ty)?; + } else { + ecx.push_stack_frame( + main_instance, + main_mir.span, + main_mir, + Lvalue::from_ptr(Pointer::zst_ptr()), + StackPopCleanup::None, + )?; + } + + // Allocate memory for the return value. We have to do this when a stack frame was already pushed as the type code below + // calls EvalContext::substs, which needs a frame to be allocated (?!?) + let ret_ptr = { + let ty = ecx.tcx.types.isize; + let layout = ecx.type_layout(ty)?; + let size = layout.size(&ecx.tcx.data_layout).bytes(); + let align = layout.align(&ecx.tcx.data_layout).pref(); // FIXME is this right? + ecx.memory.allocate(size, align)? + }; + ecx.frame_mut().return_lvalue = Lvalue::from_ptr(ret_ptr); + + loop { + if !ecx.step()? { + ecx.memory.deallocate(ret_ptr)?; + return Ok(()); + } + } } - ecx.push_stack_frame( - instance, - DUMMY_SP, - mir, - Lvalue::from_ptr(Pointer::zst_ptr()), - StackPopCleanup::None, - ).expect("could not allocate first stack frame"); - - loop { - match ecx.step() { - Ok(true) => {} - Ok(false) => { - let leaks = ecx.memory.leak_report(); - if leaks != 0 { - tcx.sess.err("the evaluated program leaked memory"); - } - return; - } - Err(e) => { - report(tcx, &ecx, e); - return; + let mut ecx = EvalContext::new(tcx, limits); + match run_main(&mut ecx, main_id, start_wrapper) { + Ok(()) => { + let leaks = ecx.memory.leak_report(); + if leaks != 0 { + tcx.sess.err("the evaluated program leaked memory"); } } + Err(e) => { + report(tcx, &ecx, e); + } } } fn report(tcx: TyCtxt, ecx: &EvalContext, e: EvalError) { - let frame = ecx.stack().last().expect("stackframe was empty"); - let block = &frame.mir.basic_blocks()[frame.block]; - let span = if frame.stmt < block.statements.len() { - block.statements[frame.stmt].source_info.span - } else { - block.terminator().source_info.span - }; - let mut err = tcx.sess.struct_span_err(span, &e.to_string()); - for &Frame { instance, span, .. } in ecx.stack().iter().rev() { - if tcx.def_key(instance.def_id()).disambiguated_data.data == DefPathData::ClosureExpr { - err.span_note(span, "inside call to closure"); - continue; + if let Some(frame) = ecx.stack().last() { + let block = &frame.mir.basic_blocks()[frame.block]; + let span = if frame.stmt < block.statements.len() { + block.statements[frame.stmt].source_info.span + } else { + block.terminator().source_info.span + }; + let mut err = tcx.sess.struct_span_err(span, &e.to_string()); + for &Frame { instance, span, .. } in ecx.stack().iter().rev() { + if tcx.def_key(instance.def_id()).disambiguated_data.data == DefPathData::ClosureExpr { + err.span_note(span, "inside call to closure"); + continue; + } + err.span_note(span, &format!("inside call to {}", instance)); } - err.span_note(span, &format!("inside call to {}", instance)); + err.emit(); + } else { + tcx.sess.err(&e.to_string()); } - err.emit(); } // TODO(solson): Upstream these methods into rustc::ty::layout. diff --git a/src/step.rs b/src/step.rs index 84f849c61225..81bac8c985b8 100644 --- a/src/step.rs +++ b/src/step.rs @@ -43,14 +43,10 @@ impl<'a, 'tcx> EvalContext<'a, 'tcx> { Lvalue::from_ptr(Pointer::zst_ptr()), StackPopCleanup::None, )?; - if let Some(arg_local) = self.frame().mir.args_iter().next() { - let dest = self.eval_lvalue(&mir::Lvalue::Local(arg_local))?; - let ty = self.tcx.mk_mut_ptr(self.tcx.types.u8); - self.write_value(Value::ByVal(PrimVal::Ptr(ptr)), dest, ty)?; - } else { - return Err(EvalError::AbiViolation("TLS dtor does not take enough arguments.".to_owned())); - } - + let arg_local = self.frame().mir.args_iter().next().ok_or(EvalError::AbiViolation("TLS dtor does not take enough arguments.".to_owned()))?; + let dest = self.eval_lvalue(&mir::Lvalue::Local(arg_local))?; + let ty = self.tcx.mk_mut_ptr(self.tcx.types.u8); + self.write_value(Value::ByVal(PrimVal::Ptr(ptr)), dest, ty)?; return Ok(true); } return Ok(false); diff --git a/src/terminator/mod.rs b/src/terminator/mod.rs index e017a87f0346..09361aa43b3b 100644 --- a/src/terminator/mod.rs +++ b/src/terminator/mod.rs @@ -637,23 +637,33 @@ impl<'a, 'tcx> EvalContext<'a, 'tcx> { self.write_primval(dest, PrimVal::Bytes(result as u128), dest_ty)?; } - // unix panic code inside libstd will read the return value of this function - "pthread_rwlock_rdlock" => { + // 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); + let result = match name { + 30 => 4096, // _SC_PAGESIZE + _ => return Err(EvalError::Unimplemented(format!("Unimplemented sysconf name: {}", name))) + }; + self.write_primval(dest, PrimVal::Bytes(result), dest_ty)?; + } + + "mmap" => { + // This is a horrible hack, but well... the guard page mechanism calls mmap and expects a particular return value, so we give it that value + let addr = args[0].read_ptr(&self.memory)?; + self.write_primval(dest, PrimVal::Ptr(addr), dest_ty)?; + } + // Hook pthread calls that go to the thread-local storage memory subsystem "pthread_key_create" => { let key_ptr = args[0].read_ptr(&self.memory)?; // Extract the function type out of the signature (that seems easier than constructing it ourselves...) - let dtor_fn_ty = match self.operand_ty(&arg_operands[1]).sty { - TypeVariants::TyAdt(_, ref substs) => { - substs.type_at(0) - } - _ => return Err(EvalError::AbiViolation("Wrong signature used for pthread_key_create: Second argument must be option of a function pointer.".to_owned())) - }; - let dtor_ptr = self.value_to_primval(args[1], dtor_fn_ty)?.to_ptr()?; + let dtor_ptr = args[1].read_ptr(&self.memory)?; // TODO: The null-pointer case here is entirely untested let dtor = if dtor_ptr.is_null_ptr() { None } else { Some(self.memory.get_fn(dtor_ptr.alloc_id)?) }; @@ -699,8 +709,10 @@ impl<'a, 'tcx> EvalContext<'a, 'tcx> { self.write_primval(dest, PrimVal::Bytes(0), 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_primval(dest, PrimVal::Bytes(0), dest_ty)?; }, _ => { diff --git a/tests/compile-fail/oom.rs b/tests/compile-fail/oom.rs index 1fd2c4b2bd6a..d15f1d6ae3e7 100644 --- a/tests/compile-fail/oom.rs +++ b/tests/compile-fail/oom.rs @@ -1,7 +1,7 @@ #![feature(custom_attribute, attr_literals)] -#![miri(memory_size=0)] +#![miri(memory_size=20)] fn main() { let _x = [42; 10]; - //~^ERROR tried to allocate 40 more bytes, but only 0 bytes are free of the 0 byte memory + //~^ERROR tried to allocate 40 more bytes, but only } From 99433a1ffdb1724cbafeafba88d6d52fee579bd1 Mon Sep 17 00:00:00 2001 From: Ralf Jung Date: Fri, 26 May 2017 20:02:51 -0700 Subject: [PATCH 11/20] improve fn pointer signature check to allow some casts that should be permitted Also properly check the "non-capturing Fn to fn" case --- src/terminator/mod.rs | 74 ++++++++++++++++++++++++++---- tests/compile-fail/cast_fn_ptr2.rs | 9 ++++ tests/run-pass/cast_fn_ptr.rs | 9 ++++ 3 files changed, 83 insertions(+), 9 deletions(-) create mode 100644 tests/compile-fail/cast_fn_ptr2.rs create mode 100644 tests/run-pass/cast_fn_ptr.rs diff --git a/src/terminator/mod.rs b/src/terminator/mod.rs index 09361aa43b3b..0fea3a174be1 100644 --- a/src/terminator/mod.rs +++ b/src/terminator/mod.rs @@ -72,15 +72,8 @@ impl<'a, 'tcx> EvalContext<'a, 'tcx> { ty::TyFnDef(_, _, real_sig) => { let sig = self.erase_lifetimes(&sig); let real_sig = self.erase_lifetimes(&real_sig); - match instance.def { - // FIXME: this needs checks for weird transmutes - // we need to bail here, because noncapturing closures as fn ptrs fail the checks - ty::InstanceDef::ClosureOnceShim{..} => {} - _ => if sig.abi != real_sig.abi || - sig.variadic != real_sig.variadic || - sig.inputs_and_output != real_sig.inputs_and_output { - return Err(EvalError::FunctionPointerTyMismatch(real_sig, sig)); - }, + if !self.check_sig_compat(sig, real_sig)? { + return Err(EvalError::FunctionPointerTyMismatch(real_sig, sig)); } }, ref other => bug!("instance def ty: {:?}", other), @@ -138,6 +131,69 @@ impl<'a, 'tcx> EvalContext<'a, 'tcx> { Ok(()) } + /// Decides whether it is okay to call the method with signature `real_sig` using signature `sig` + fn check_sig_compat( + &mut self, + sig: ty::FnSig<'tcx>, + real_sig: ty::FnSig<'tcx>, + ) -> EvalResult<'tcx, bool> { + fn check_ty_compat<'tcx>( + ty: ty::Ty<'tcx>, + real_ty: ty::Ty<'tcx>, + ) -> bool { + if ty == real_ty { return true; } // This is actually a fast pointer comparison + return match (&ty.sty, &real_ty.sty) { + // Permit changing the pointer type of raw pointers and references as well as + // mutability of raw pointers. + // TODO: Should not be allowed when fat pointers are involved. + (&TypeVariants::TyRawPtr(_), &TypeVariants::TyRawPtr(_)) => true, + (&TypeVariants::TyRef(_, _), &TypeVariants::TyRef(_, _)) => + ty.is_mutable_pointer() == real_ty.is_mutable_pointer(), + // rule out everything else + _ => false + } + } + + if sig.abi == real_sig.abi && + sig.variadic == real_sig.variadic && + sig.inputs_and_output.len() == real_sig.inputs_and_output.len() && + sig.inputs_and_output.iter().zip(real_sig.inputs_and_output).all(|(ty, real_ty)| check_ty_compat(ty, real_ty)) { + // Definitely good. + return Ok(true); + } + + if sig.variadic || real_sig.variadic { + // We're not touching this + return Ok(false); + } + + // We need to allow what comes up when a non-capturing closure is cast to a fn(). + match (sig.abi, real_sig.abi) { + (Abi::Rust, Abi::RustCall) // check the ABIs. This makes the test here non-symmetric. + if check_ty_compat(sig.output(), real_sig.output()) && real_sig.inputs_and_output.len() == 3 => { + // First argument of real_sig must be a ZST + let fst_ty = real_sig.inputs_and_output[0]; + let layout = self.type_layout(fst_ty)?; + let size = layout.size(&self.tcx.data_layout).bytes(); + if size == 0 { + // Second argument must be a tuple matching the argument list of sig + let snd_ty = real_sig.inputs_and_output[1]; + match snd_ty.sty { + TypeVariants::TyTuple(tys, _) if sig.inputs().len() == tys.len() => + if sig.inputs().iter().zip(tys).all(|(ty, real_ty)| check_ty_compat(ty, real_ty)) { + return Ok(true) + }, + _ => {} + } + } + } + _ => {} + }; + + // Nope, this doesn't work. + return Ok(false); + } + fn eval_fn_call( &mut self, instance: ty::Instance<'tcx>, diff --git a/tests/compile-fail/cast_fn_ptr2.rs b/tests/compile-fail/cast_fn_ptr2.rs new file mode 100644 index 000000000000..5d902e1f9aaa --- /dev/null +++ b/tests/compile-fail/cast_fn_ptr2.rs @@ -0,0 +1,9 @@ +fn main() { + fn f(_ : (i32,i32)) {} + + let g = unsafe { + std::mem::transmute::(f) + }; + + g(42) //~ ERROR tried to call a function with sig fn((i32, i32)) through a function pointer of type fn(i32) +} diff --git a/tests/run-pass/cast_fn_ptr.rs b/tests/run-pass/cast_fn_ptr.rs new file mode 100644 index 000000000000..109e8dfc2a02 --- /dev/null +++ b/tests/run-pass/cast_fn_ptr.rs @@ -0,0 +1,9 @@ +fn main() { + fn f(_: *const u8) {} + + let g = unsafe { + std::mem::transmute::(f) + }; + + g(&42 as *const _); +} From 1241938f97622e19d79528350670707c53dd26a6 Mon Sep 17 00:00:00 2001 From: Ralf Jung Date: Fri, 26 May 2017 20:27:39 -0700 Subject: [PATCH 12/20] test suite now also passes on MIR-libstd Also enable some tests that were disabled for no apparant reason. (The comment in zst.rs was wrong, the test was disabled also for miri execution.) Delete env_args test as the args can actually be queried with MIR-libstd (currently, they are always empty) --- src/step.rs | 2 +- src/terminator/mod.rs | 4 ++++ tests/compile-fail/env_args.rs | 4 ---- tests/compile-fail/oom.rs | 6 +++--- tests/compile-fail/oom2.rs | 2 +- tests/compile-fail/stack_limit.rs | 12 +++++++++--- tests/run-pass/aux_test.rs | 1 - tests/run-pass/hello.rs | 3 +++ tests/run-pass/zst.rs | 6 ------ 9 files changed, 21 insertions(+), 19 deletions(-) delete mode 100644 tests/compile-fail/env_args.rs create mode 100644 tests/run-pass/hello.rs diff --git a/src/step.rs b/src/step.rs index 81bac8c985b8..cbd9871e83fa 100644 --- a/src/step.rs +++ b/src/step.rs @@ -34,7 +34,7 @@ impl<'a, 'tcx> EvalContext<'a, 'tcx> { if self.stack.is_empty() { if let Some((instance, ptr)) = self.memory.fetch_tls_dtor() { trace!("Running TLS dtor {:?} on {:?}", instance, ptr); - // TODO: Potientiually, this has to support all the other possible instances? See eval_fn_call in terminator/mod.rs + // TODO: Potientially, this has to support all the other possible instances? See eval_fn_call in terminator/mod.rs let mir = self.load_mir(instance.def)?; self.push_stack_frame( instance, diff --git a/src/terminator/mod.rs b/src/terminator/mod.rs index 0fea3a174be1..95901c56e88e 100644 --- a/src/terminator/mod.rs +++ b/src/terminator/mod.rs @@ -622,6 +622,10 @@ impl<'a, 'tcx> EvalContext<'a, 'tcx> { return Ok(()); } + "__rust_start_panic" => { + return Err(EvalError::Panic); + } + "memcmp" => { let left = args[0].read_ptr(&self.memory)?; let right = args[1].read_ptr(&self.memory)?; diff --git a/tests/compile-fail/env_args.rs b/tests/compile-fail/env_args.rs deleted file mode 100644 index fe17e0a7b489..000000000000 --- a/tests/compile-fail/env_args.rs +++ /dev/null @@ -1,4 +0,0 @@ -fn main() { - let x = std::env::args(); //~ ERROR miri does not support program arguments - assert_eq!(x.count(), 1); -} diff --git a/tests/compile-fail/oom.rs b/tests/compile-fail/oom.rs index d15f1d6ae3e7..d4aebb912ee1 100644 --- a/tests/compile-fail/oom.rs +++ b/tests/compile-fail/oom.rs @@ -1,7 +1,7 @@ #![feature(custom_attribute, attr_literals)] -#![miri(memory_size=20)] +#![miri(memory_size=4095)] fn main() { - let _x = [42; 10]; - //~^ERROR tried to allocate 40 more bytes, but only + let _x = [42; 1024]; + //~^ERROR tried to allocate 4096 more bytes, but only } diff --git a/tests/compile-fail/oom2.rs b/tests/compile-fail/oom2.rs index a87e34474cff..1a4a47efe685 100644 --- a/tests/compile-fail/oom2.rs +++ b/tests/compile-fail/oom2.rs @@ -1,5 +1,5 @@ #![feature(box_syntax, custom_attribute, attr_literals)] -#![miri(memory_size=1000)] +#![miri(memory_size=2048)] fn main() { loop { diff --git a/tests/compile-fail/stack_limit.rs b/tests/compile-fail/stack_limit.rs index 2a78fbe5398f..4b61e12d602f 100644 --- a/tests/compile-fail/stack_limit.rs +++ b/tests/compile-fail/stack_limit.rs @@ -1,5 +1,5 @@ #![feature(custom_attribute, attr_literals)] -#![miri(stack_limit=2)] +#![miri(stack_limit=16)] fn bar() { foo(); @@ -10,10 +10,16 @@ fn foo() { } fn cake() { - flubber(); + flubber(3); } -fn flubber() {} +fn flubber(i: u32) { + if i > 0 { + flubber(i-1); + } else { + bar(); + } +} fn main() { bar(); diff --git a/tests/run-pass/aux_test.rs b/tests/run-pass/aux_test.rs index 1b1dbaa68387..aa471f6cf8fd 100644 --- a/tests/run-pass/aux_test.rs +++ b/tests/run-pass/aux_test.rs @@ -1,5 +1,4 @@ // aux-build:dep.rs -// ignore-cross-compile extern crate dep; diff --git a/tests/run-pass/hello.rs b/tests/run-pass/hello.rs new file mode 100644 index 000000000000..e7a11a969c03 --- /dev/null +++ b/tests/run-pass/hello.rs @@ -0,0 +1,3 @@ +fn main() { + println!("Hello, world!"); +} diff --git a/tests/run-pass/zst.rs b/tests/run-pass/zst.rs index 78d3025587f0..06e41e59e602 100644 --- a/tests/run-pass/zst.rs +++ b/tests/run-pass/zst.rs @@ -1,9 +1,3 @@ -// the following flag prevents this test from running on the host machine -// this should only be run on miri, because rust doesn't (yet?) optimize ZSTs of different types -// into the same memory location -// ignore-test - - #[derive(PartialEq, Debug)] struct A; From 633a34d6d35b7d73e929ecb38bd90d9298092fe2 Mon Sep 17 00:00:00 2001 From: Ralf Jung Date: Fri, 26 May 2017 20:38:22 -0700 Subject: [PATCH 13/20] re-disable aux_test -- it passes here, but not on Travis --- tests/run-pass/aux_test.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/run-pass/aux_test.rs b/tests/run-pass/aux_test.rs index aa471f6cf8fd..1b1dbaa68387 100644 --- a/tests/run-pass/aux_test.rs +++ b/tests/run-pass/aux_test.rs @@ -1,4 +1,5 @@ // aux-build:dep.rs +// ignore-cross-compile extern crate dep; From 24a9a14dfa2e14250e8bb60cc70a2a6b20260980 Mon Sep 17 00:00:00 2001 From: Ralf Jung Date: Tue, 30 May 2017 10:24:37 -0700 Subject: [PATCH 14/20] fix various small nits --- src/bin/miri.rs | 4 ++-- src/eval_context.rs | 12 +++++------- src/terminator/mod.rs | 33 ++++++++++++++++++--------------- 3 files changed, 25 insertions(+), 24 deletions(-) diff --git a/src/bin/miri.rs b/src/bin/miri.rs index 4a82d45493b8..3af3bee3c8ba 100644 --- a/src/bin/miri.rs +++ b/src/bin/miri.rs @@ -95,8 +95,8 @@ fn after_analysis<'a, 'tcx>(state: &mut CompileState<'a, 'tcx>) { state.hir_crate.unwrap().visit_all_item_likes(&mut Visitor(limits, tcx, state)); } else if let Some((entry_node_id, _)) = *state.session.entry_fn.borrow() { let entry_def_id = tcx.hir.local_def_id(entry_node_id); - let start_wrapper = tcx.lang_items.start_fn() - .and_then(|start_fn| if tcx.is_mir_available(start_fn) { Some(start_fn) } else { None }); + let start_wrapper = tcx.lang_items.start_fn().and_then(|start_fn| + if tcx.is_mir_available(start_fn) { Some(start_fn) } else { None }); miri::eval_main(tcx, entry_def_id, start_wrapper, limits); state.session.abort_if_errors(); diff --git a/src/eval_context.rs b/src/eval_context.rs index 9a57e75da090..b44116e5b101 100644 --- a/src/eval_context.rs +++ b/src/eval_context.rs @@ -1558,6 +1558,7 @@ impl<'a, 'tcx> EvalContext<'a, 'tcx> { } pub(super) fn dump_local(&self, lvalue: Lvalue<'tcx>) { + // Debug output if let Lvalue::Local { frame, local, field } = lvalue { let mut allocs = Vec::new(); let mut msg = format!("{:?}", local); @@ -1744,17 +1745,14 @@ pub fn eval_main<'a, 'tcx: 'a>( let ty = ecx.tcx.types.isize; let layout = ecx.type_layout(ty)?; let size = layout.size(&ecx.tcx.data_layout).bytes(); - let align = layout.align(&ecx.tcx.data_layout).pref(); // FIXME is this right? + let align = layout.align(&ecx.tcx.data_layout).abi(); ecx.memory.allocate(size, align)? }; ecx.frame_mut().return_lvalue = Lvalue::from_ptr(ret_ptr); - loop { - if !ecx.step()? { - ecx.memory.deallocate(ret_ptr)?; - return Ok(()); - } - } + while ecx.step()? {} + ecx.memory.deallocate(ret_ptr)?; + return Ok(()); } let mut ecx = EvalContext::new(tcx, limits); diff --git a/src/terminator/mod.rs b/src/terminator/mod.rs index 95901c56e88e..799c30f0d4e3 100644 --- a/src/terminator/mod.rs +++ b/src/terminator/mod.rs @@ -131,7 +131,8 @@ impl<'a, 'tcx> EvalContext<'a, 'tcx> { Ok(()) } - /// Decides whether it is okay to call the method with signature `real_sig` using signature `sig` + /// Decides whether it is okay to call the method with signature `real_sig` using signature `sig`. + /// FIXME: This should take into account the platform-dependent ABI description. fn check_sig_compat( &mut self, sig: ty::FnSig<'tcx>, @@ -284,12 +285,6 @@ impl<'a, 'tcx> EvalContext<'a, 'tcx> { trace!("arg_locals: {:?}", self.frame().mir.args_iter().collect::>()); trace!("arg_operands: {:?}", arg_operands); match sig.abi { - Abi::Rust | Abi::C => { - for (arg_local, (arg_val, arg_ty)) in arg_locals.zip(args) { - let dest = self.eval_lvalue(&mir::Lvalue::Local(arg_local))?; - self.write_value(arg_val, dest, arg_ty)?; - } - } Abi::RustCall => { assert_eq!(args.len(), 2); @@ -332,8 +327,13 @@ impl<'a, 'tcx> EvalContext<'a, 'tcx> { } else { bug!("rust-call ABI tuple argument was {:?}, {:?}", arg_ty, layout); } + }, + _ => { + for (arg_local, (arg_val, arg_ty)) in arg_locals.zip(args) { + let dest = self.eval_lvalue(&mir::Lvalue::Local(arg_local))?; + self.write_value(arg_val, dest, arg_ty)?; + } } - _ => unimplemented!(), } Ok(()) }, @@ -520,7 +520,7 @@ impl<'a, 'tcx> EvalContext<'a, 'tcx> { // Still, we can make many things mostly work by "emulating" or ignoring some functions. match &path[..] { "std::io::_print" => { - trace!("Ignoring output."); + trace!("Ignoring output. To run programs that print, make sure you have a libstd with full MIR."); self.goto_block(destination.unwrap().1); Ok(()) }, @@ -595,15 +595,16 @@ impl<'a, 'tcx> EvalContext<'a, 'tcx> { } "__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].read_ptr(&self.memory)?; - let data = args[1].read_ptr(&self.memory)?; // FIXME: Why does value_to_primval(args[2], u8_ptr_ty)?.to_ptr()? here end up doing the Wrong Thing (TM)? + let data = args[1].read_ptr(&self.memory)?; let f_instance = self.memory.get_fn(f.alloc_id)?; self.write_primval(dest, PrimVal::Bytes(0), dest_ty)?; - // Now we make a functon call. TODO: Consider making this re-usable? EvalContext::step does sth. similar for the TLS dtors, - // and of coruse eval_main. + // 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, @@ -614,11 +615,13 @@ impl<'a, 'tcx> EvalContext<'a, 'tcx> { )?; 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 dest = self.eval_lvalue(&mir::Lvalue::Local(arg_local))?; - self.write_value(Value::ByVal(PrimVal::Ptr(data)), dest, u8_ptr_ty)?; + let arg_dest = self.eval_lvalue(&mir::Lvalue::Local(arg_local))?; + self.write_value(Value::ByVal(PrimVal::Ptr(data)), arg_dest, u8_ptr_ty)?; + + // We ourselbes return 0 + self.write_primval(dest, PrimVal::Bytes(0), dest_ty)?; // Don't fall through - // FIXME: Do we have to do self.dump_local(ret) anywhere? return Ok(()); } From dad95474cbf48ad02c768a2a42a2c55150bad67f Mon Sep 17 00:00:00 2001 From: Ralf Jung Date: Tue, 30 May 2017 10:41:22 -0700 Subject: [PATCH 15/20] test thread-local key with no dtor --- src/terminator/mod.rs | 1 - tests/run-pass/thread-local-no-dtor.rs | 16 ++++++++++++++++ 2 files changed, 16 insertions(+), 1 deletion(-) create mode 100644 tests/run-pass/thread-local-no-dtor.rs diff --git a/src/terminator/mod.rs b/src/terminator/mod.rs index 799c30f0d4e3..81927ba32996 100644 --- a/src/terminator/mod.rs +++ b/src/terminator/mod.rs @@ -727,7 +727,6 @@ impl<'a, 'tcx> EvalContext<'a, 'tcx> { // Extract the function type out of the signature (that seems easier than constructing it ourselves...) let dtor_ptr = args[1].read_ptr(&self.memory)?; - // TODO: The null-pointer case here is entirely untested let dtor = if dtor_ptr.is_null_ptr() { None } else { Some(self.memory.get_fn(dtor_ptr.alloc_id)?) }; // Figure out how large a pthread TLS key actually is. This is libc::pthread_key_t. diff --git a/tests/run-pass/thread-local-no-dtor.rs b/tests/run-pass/thread-local-no-dtor.rs new file mode 100644 index 000000000000..8c69be8e2cd7 --- /dev/null +++ b/tests/run-pass/thread-local-no-dtor.rs @@ -0,0 +1,16 @@ +#![feature(libc)] +extern crate libc; + +use std::mem; + +pub type Key = libc::pthread_key_t; + +pub unsafe fn create(dtor: Option) -> Key { + let mut key = 0; + assert_eq!(libc::pthread_key_create(&mut key, mem::transmute(dtor)), 0); + key +} + +fn main() { + let _ = unsafe { create(None) }; +} From cdf7a057f1a6cdb96ddee81cfbcc3a059e27cf2d Mon Sep 17 00:00:00 2001 From: Ralf Jung Date: Tue, 30 May 2017 13:19:15 -0700 Subject: [PATCH 16/20] latest rust nightly contains all the bits needed to re-compile libstd --- README.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index aa89df9731fa..1340ddb5b879 100644 --- a/README.md +++ b/README.md @@ -61,6 +61,8 @@ miri hits a call to such a function, execution terminates. To fix this, it is possible to compile libstd with full MIR: ```sh +rustup component add rust-src +chmod +x -R ~/.rustup/toolchains/*/lib/rustlib/src/rust/src/jemalloc/include/jemalloc/ cargo install xargo cd xargo/ RUSTFLAGS='-Zalways-encode-mir' xargo build --target `rustc -vV | egrep '^host: ' | sed 's/^host: //'` @@ -73,8 +75,7 @@ cargo run --bin miri -- --sysroot ~/.xargo/HOST tests/run-pass/vecs.rs ``` Notice that you will have to re-run the last step of the preparations above when -your toolchain changes (e.g., when you update the nightly). Also, xargo doesn't -currently work with nightlies newer than 2017-04-23. +your toolchain changes (e.g., when you update the nightly). ## Contributing and getting help From b8e0b792222af098f01d778cccb6efb5affde52a Mon Sep 17 00:00:00 2001 From: Ralf Jung Date: Tue, 30 May 2017 14:02:20 -0700 Subject: [PATCH 17/20] add a test for output string formatting --- tests/run-pass/format.rs | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 tests/run-pass/format.rs diff --git a/tests/run-pass/format.rs b/tests/run-pass/format.rs new file mode 100644 index 000000000000..78729b915613 --- /dev/null +++ b/tests/run-pass/format.rs @@ -0,0 +1,3 @@ +fn main() { + println!("Hello {}", 13); +} From b44babf23039ed1132b0332c71e1bb959995949b Mon Sep 17 00:00:00 2001 From: Ralf Jung Date: Tue, 30 May 2017 15:39:55 -0700 Subject: [PATCH 18/20] allocate return pointer only when we start the program via the start lang item --- src/eval_context.rs | 28 +++++++++++++++------------- 1 file changed, 15 insertions(+), 13 deletions(-) diff --git a/src/eval_context.rs b/src/eval_context.rs index b44116e5b101..85c23ef02941 100644 --- a/src/eval_context.rs +++ b/src/eval_context.rs @@ -1689,6 +1689,7 @@ pub fn eval_main<'a, 'tcx: 'a>( ) -> EvalResult<'tcx> { let main_instance = ty::Instance::mono(ecx.tcx, main_id); let main_mir = ecx.load_mir(main_instance.def)?; + let mut cleanup_ptr = None; // Pointer to be deallocated when we are done if !main_mir.return_ty.is_nil() || main_mir.arg_count != 0 { return Err(EvalError::Unimplemented("miri does not support main functions without `fn()` type signatures".to_owned())); @@ -1702,12 +1703,22 @@ pub fn eval_main<'a, 'tcx: 'a>( return Err(EvalError::AbiViolation(format!("'start' lang item should have three arguments, but has {}", start_mir.arg_count))); } + // Return value + let ret_ptr = { + let ty = ecx.tcx.types.isize; + let layout = ecx.type_layout_with_substs(ty, Substs::empty())?; + let size = layout.size(&ecx.tcx.data_layout).bytes(); + let align = layout.align(&ecx.tcx.data_layout).abi(); + ecx.memory.allocate(size, align)? + }; + cleanup_ptr = Some(ret_ptr); + // Push our stack frame ecx.push_stack_frame( start_instance, start_mir.span, start_mir, - Lvalue::from_ptr(Pointer::zst_ptr()), // we'll fix the return lvalue later + Lvalue::from_ptr(ret_ptr), StackPopCleanup::None, )?; @@ -1739,19 +1750,10 @@ pub fn eval_main<'a, 'tcx: 'a>( )?; } - // Allocate memory for the return value. We have to do this when a stack frame was already pushed as the type code below - // calls EvalContext::substs, which needs a frame to be allocated (?!?) - let ret_ptr = { - let ty = ecx.tcx.types.isize; - let layout = ecx.type_layout(ty)?; - let size = layout.size(&ecx.tcx.data_layout).bytes(); - let align = layout.align(&ecx.tcx.data_layout).abi(); - ecx.memory.allocate(size, align)? - }; - ecx.frame_mut().return_lvalue = Lvalue::from_ptr(ret_ptr); - while ecx.step()? {} - ecx.memory.deallocate(ret_ptr)?; + if let Some(cleanup_ptr) = cleanup_ptr { + ecx.memory.deallocate(cleanup_ptr)?; + } return Ok(()); } From 2b37d500f1f7dfce389a1cc93aefc88003899699 Mon Sep 17 00:00:00 2001 From: Ralf Jung Date: Tue, 30 May 2017 17:03:45 -0700 Subject: [PATCH 19/20] simplify determining size and alignment of a pointer --- src/eval_context.rs | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/src/eval_context.rs b/src/eval_context.rs index 85c23ef02941..922a36c892ae 100644 --- a/src/eval_context.rs +++ b/src/eval_context.rs @@ -1704,13 +1704,7 @@ pub fn eval_main<'a, 'tcx: 'a>( } // Return value - let ret_ptr = { - let ty = ecx.tcx.types.isize; - let layout = ecx.type_layout_with_substs(ty, Substs::empty())?; - let size = layout.size(&ecx.tcx.data_layout).bytes(); - let align = layout.align(&ecx.tcx.data_layout).abi(); - ecx.memory.allocate(size, align)? - }; + let ret_ptr = ecx.memory.allocate(ecx.tcx.data_layout.pointer_size.bytes(), ecx.tcx.data_layout.pointer_align.abi())?; cleanup_ptr = Some(ret_ptr); // Push our stack frame From d06c1653689d255bb66f0f778f870067f66b51e8 Mon Sep 17 00:00:00 2001 From: Ralf Jung Date: Tue, 30 May 2017 18:12:06 -0700 Subject: [PATCH 20/20] simplify xargo instructions --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 1340ddb5b879..fa3fea79e940 100644 --- a/README.md +++ b/README.md @@ -65,7 +65,7 @@ rustup component add rust-src chmod +x -R ~/.rustup/toolchains/*/lib/rustlib/src/rust/src/jemalloc/include/jemalloc/ cargo install xargo cd xargo/ -RUSTFLAGS='-Zalways-encode-mir' xargo build --target `rustc -vV | egrep '^host: ' | sed 's/^host: //'` +RUSTFLAGS='-Zalways-encode-mir' xargo build ``` Now you can run miri against the libstd compiled by xargo: