commit
c817e6e4b3
23 changed files with 580 additions and 132 deletions
23
README.md
23
README.md
|
|
@ -54,6 +54,29 @@ 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
|
||||
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
|
||||
```
|
||||
|
||||
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).
|
||||
|
||||
## Contributing and getting help
|
||||
|
||||
Check out the issues on this GitHub repository for some ideas. There's lots that
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -38,6 +38,9 @@ pub enum EvalError<'tcx> {
|
|||
},
|
||||
ExecutionTimeLimitReached,
|
||||
StackFrameLimitReached,
|
||||
OutOfTls,
|
||||
TlsOutOfBounds,
|
||||
AbiViolation(String),
|
||||
AlignmentCheckFailed {
|
||||
required: u64,
|
||||
has: u64,
|
||||
|
|
@ -101,6 +104,11 @@ 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::AbiViolation(ref msg) => msg,
|
||||
EvalError::AlignmentCheckFailed{..} =>
|
||||
"tried to execute a misaligned read or write",
|
||||
EvalError::CalledClosureAsFunction =>
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
|
@ -1557,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);
|
||||
|
|
@ -1676,62 +1678,113 @@ 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<DefId>,
|
||||
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<DefId>,
|
||||
) -> 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 !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)));
|
||||
}
|
||||
|
||||
// Return value
|
||||
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
|
||||
ecx.push_stack_frame(
|
||||
start_instance,
|
||||
start_mir.span,
|
||||
start_mir,
|
||||
Lvalue::from_ptr(ret_ptr),
|
||||
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,
|
||||
)?;
|
||||
}
|
||||
|
||||
while ecx.step()? {}
|
||||
if let Some(cleanup_ptr) = cleanup_ptr {
|
||||
ecx.memory.deallocate(cleanup_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.
|
||||
|
|
|
|||
|
|
@ -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> {
|
||||
|
|
|
|||
|
|
@ -98,6 +98,18 @@ impl Pointer {
|
|||
pub fn never_ptr() -> Self {
|
||||
Pointer::new(NEVER_ALLOC_ID, 0)
|
||||
}
|
||||
|
||||
pub fn is_null_ptr(&self) -> bool {
|
||||
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<ty::Instance<'tcx>>,
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
|
@ -149,6 +161,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<Vec<u8>, 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<TlsKey, TlsEntry<'tcx>>,
|
||||
|
||||
/// The Key to use for the next thread-local allocation.
|
||||
next_thread_local: TlsKey,
|
||||
}
|
||||
|
||||
const ZST_ALLOC_ID: AllocId = AllocId(0);
|
||||
|
|
@ -167,6 +185,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 +365,59 @@ impl<'a, 'tcx> Memory<'a, 'tcx> {
|
|||
pub(crate) fn clear_packed(&mut self) {
|
||||
self.packed.clear();
|
||||
}
|
||||
|
||||
pub(crate) fn create_tls_key(&mut self, dtor: Option<ty::Instance<'tcx>>) -> TlsKey {
|
||||
let new_key = self.next_thread_local;
|
||||
self.next_thread_local += 1;
|
||||
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;
|
||||
}
|
||||
|
||||
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(&TlsEntry { data, .. }) => {
|
||||
trace!("TLS key {} loaded: {:?}", key, data);
|
||||
Ok(data)
|
||||
},
|
||||
None => Err(EvalError::TlsOutOfBounds)
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn store_tls(&mut self, key: TlsKey, new_data: Pointer) -> EvalResult<'tcx> {
|
||||
return match self.thread_local.get_mut(&key) {
|
||||
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
|
||||
|
|
|
|||
32
src/step.rs
32
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> {
|
||||
|
|
@ -31,6 +32,23 @@ 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: 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,
|
||||
mir.span,
|
||||
mir,
|
||||
Lvalue::from_ptr(Pointer::zst_ptr()),
|
||||
StackPopCleanup::None,
|
||||
)?;
|
||||
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);
|
||||
}
|
||||
|
||||
|
|
@ -48,11 +66,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);
|
||||
}
|
||||
|
||||
|
|
@ -65,11 +83,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)
|
||||
}
|
||||
|
||||
|
|
@ -158,6 +176,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 +201,7 @@ impl<'a, 'b, 'tcx> ConstantExtractor<'a, 'b, 'tcx> {
|
|||
)
|
||||
});
|
||||
}
|
||||
|
||||
fn try<F: FnOnce(&mut Self) -> EvalResult<'tcx>>(&mut self, f: F) {
|
||||
if let Ok(ref mut n) = *self.new_constants {
|
||||
*n += 1;
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
use rustc::hir::def_id::DefId;
|
||||
use rustc::mir;
|
||||
use rustc::ty::{self, Ty};
|
||||
use rustc::ty::{self, TypeVariants, Ty, 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;
|
||||
|
|
@ -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,70 @@ impl<'a, 'tcx> EvalContext<'a, 'tcx> {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
/// 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>,
|
||||
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>,
|
||||
|
|
@ -172,7 +229,9 @@ impl<'a, 'tcx> EvalContext<'a, 'tcx> {
|
|||
if self.eval_fn_call_inner(
|
||||
instance,
|
||||
destination,
|
||||
arg_operands,
|
||||
span,
|
||||
sig,
|
||||
)? {
|
||||
return Ok(());
|
||||
}
|
||||
|
|
@ -202,18 +261,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)?;
|
||||
|
|
@ -221,25 +268,23 @@ 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,
|
||||
arg_operands,
|
||||
span,
|
||||
sig,
|
||||
)? {
|
||||
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::<Vec<_>>());
|
||||
trace!("arg_operands: {:?}", arg_operands);
|
||||
match sig.abi {
|
||||
Abi::Rust => {
|
||||
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);
|
||||
|
||||
|
|
@ -282,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(())
|
||||
},
|
||||
|
|
@ -314,7 +364,9 @@ impl<'a, 'tcx> EvalContext<'a, 'tcx> {
|
|||
if self.eval_fn_call_inner(
|
||||
instance,
|
||||
destination,
|
||||
arg_operands,
|
||||
span,
|
||||
sig,
|
||||
)? {
|
||||
return Ok(());
|
||||
}
|
||||
|
|
@ -361,7 +413,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);
|
||||
|
||||
|
|
@ -370,28 +424,8 @@ impl<'a, 'tcx> EvalContext<'a, 'tcx> {
|
|||
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
|
||||
"std::io::_print" => {
|
||||
self.goto_block(destination.unwrap().1);
|
||||
return 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::panicking::rust_panic_with_hook" |
|
||||
"std::rt::begin_panic_fmt" => return 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);
|
||||
return Ok(true);
|
||||
}
|
||||
_ => {},
|
||||
}
|
||||
return Err(EvalError::NoMirFor(path));
|
||||
self.call_missing_fn(instance, destination, arg_operands, sig, path)?;
|
||||
return Ok(true);
|
||||
},
|
||||
Err(other) => return Err(other),
|
||||
};
|
||||
|
|
@ -464,13 +498,56 @@ 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, 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. To run programs that print, make sure you have a libstd with full MIR.");
|
||||
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,
|
||||
def_id: DefId,
|
||||
args: &[mir::Operand<'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);
|
||||
|
|
@ -478,7 +555,7 @@ impl<'a, 'tcx> EvalContext<'a, 'tcx> {
|
|||
.unwrap_or(name)
|
||||
.as_str();
|
||||
|
||||
let args_res: EvalResult<Vec<Value>> = args.iter()
|
||||
let args_res: EvalResult<Vec<Value>> = arg_operands.iter()
|
||||
.map(|arg| self.eval_operand(arg))
|
||||
.collect();
|
||||
let args = args_res?;
|
||||
|
|
@ -517,6 +594,41 @@ impl<'a, 'tcx> EvalContext<'a, 'tcx> {
|
|||
self.write_primval(dest, PrimVal::Ptr(new_ptr), dest_ty)?;
|
||||
}
|
||||
|
||||
"__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)?;
|
||||
let f_instance = self.memory.get_fn(f.alloc_id)?;
|
||||
self.write_primval(dest, PrimVal::Bytes(0), 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::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 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
|
||||
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)?;
|
||||
|
|
@ -543,9 +655,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)?;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -555,9 +667,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)?;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -567,17 +679,102 @@ 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)?;
|
||||
}
|
||||
|
||||
// unix panic code inside libstd will read the return value of this function
|
||||
"pthread_rwlock_rdlock" => {
|
||||
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)?;
|
||||
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)?;
|
||||
}
|
||||
|
||||
// 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_ptr = args[1].read_ptr(&self.memory)?;
|
||||
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]).sty {
|
||||
TypeVariants::TyRawPtr(TypeAndMut { ty, .. }) => {
|
||||
let layout = self.type_layout(ty)?;
|
||||
layout.size(&self.tcx.data_layout)
|
||||
}
|
||||
_ => return Err(EvalError::AbiViolation("Wrong signature used for pthread_key_create: First argument must be a raw pointer.".to_owned()))
|
||||
};
|
||||
|
||||
// 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);
|
||||
}
|
||||
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)?;
|
||||
}
|
||||
|
||||
// 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);
|
||||
return Ok(());
|
||||
self.write_primval(dest, PrimVal::Bytes(0), dest_ty)?;
|
||||
},
|
||||
|
||||
_ => {
|
||||
|
|
@ -588,6 +785,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(())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
9
tests/compile-fail/cast_fn_ptr2.rs
Normal file
9
tests/compile-fail/cast_fn_ptr2.rs
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
fn main() {
|
||||
fn f(_ : (i32,i32)) {}
|
||||
|
||||
let g = unsafe {
|
||||
std::mem::transmute::<fn((i32,i32)), fn(i32)>(f)
|
||||
};
|
||||
|
||||
g(42) //~ ERROR tried to call a function with sig fn((i32, i32)) through a function pointer of type fn(i32)
|
||||
}
|
||||
|
|
@ -1,4 +0,0 @@
|
|||
fn main() {
|
||||
let x = std::env::args(); //~ ERROR miri does not support program arguments
|
||||
assert_eq!(x.count(), 1);
|
||||
}
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
#![feature(custom_attribute, attr_literals)]
|
||||
#![miri(memory_size=0)]
|
||||
#![miri(memory_size=4095)]
|
||||
|
||||
fn main() {
|
||||
let _x = [42; 10];
|
||||
//~^ERROR tried to allocate 40 more bytes, but only 0 bytes are free of the 0 byte memory
|
||||
let _x = [42; 1024];
|
||||
//~^ERROR tried to allocate 4096 more bytes, but only
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
#![feature(box_syntax, custom_attribute, attr_literals)]
|
||||
#![miri(memory_size=1000)]
|
||||
#![miri(memory_size=2048)]
|
||||
|
||||
fn main() {
|
||||
loop {
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
9
tests/run-pass/cast_fn_ptr.rs
Normal file
9
tests/run-pass/cast_fn_ptr.rs
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
fn main() {
|
||||
fn f(_: *const u8) {}
|
||||
|
||||
let g = unsafe {
|
||||
std::mem::transmute::<fn(*const u8), fn(*const i32)>(f)
|
||||
};
|
||||
|
||||
g(&42 as *const _);
|
||||
}
|
||||
9
tests/run-pass/catch.rs
Normal file
9
tests/run-pass/catch.rs
Normal file
|
|
@ -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");
|
||||
}
|
||||
}
|
||||
3
tests/run-pass/format.rs
Normal file
3
tests/run-pass/format.rs
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
fn main() {
|
||||
println!("Hello {}", 13);
|
||||
}
|
||||
3
tests/run-pass/hello.rs
Normal file
3
tests/run-pass/hello.rs
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
fn main() {
|
||||
println!("Hello, world!");
|
||||
}
|
||||
16
tests/run-pass/thread-local-no-dtor.rs
Normal file
16
tests/run-pass/thread-local-no-dtor.rs
Normal file
|
|
@ -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<unsafe extern fn(*mut u8)>) -> Key {
|
||||
let mut key = 0;
|
||||
assert_eq!(libc::pthread_key_create(&mut key, mem::transmute(dtor)), 0);
|
||||
key
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let _ = unsafe { create(None) };
|
||||
}
|
||||
|
|
@ -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;
|
||||
|
||||
|
|
|
|||
4
xargo/Cargo.lock
generated
Normal file
4
xargo/Cargo.lock
generated
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
[root]
|
||||
name = "miri-xargo"
|
||||
version = "0.0.0"
|
||||
|
||||
6
xargo/Cargo.toml
Normal file
6
xargo/Cargo.toml
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
[package]
|
||||
name = "miri-xargo"
|
||||
description = "A dummy project for building libstd with xargo."
|
||||
version = "0.0.0"
|
||||
|
||||
[dependencies]
|
||||
2
xargo/Xargo.toml
Normal file
2
xargo/Xargo.toml
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
[dependencies]
|
||||
std = {features = ["panic_unwind", "jemalloc"]}
|
||||
0
xargo/src/lib.rs
Normal file
0
xargo/src/lib.rs
Normal file
Loading…
Add table
Add a link
Reference in a new issue