diff --git a/README.md b/README.md index e505496a69..bbf26ff6f7 100644 --- a/README.md +++ b/README.md @@ -160,7 +160,7 @@ Several `-Z` flags are relevant for Miri: the program has access to host resources such as environment variables and randomness (and, eventually, file systems and more). * `-Zmiri-ignore-leaks` disables the memory leak checker. -* `-Zmiri-env-exclude=` keeps the `var` environment variable isolated from +* `-Zmiri-env-exclude=` keeps the `var` environment variable isolated from the host. Can be used multiple times to exclude several variables. The `TERM` environment variable is excluded by default. * `-Zmir-opt-level` controls how many MIR optimizations are performed. Miri @@ -171,6 +171,11 @@ Several `-Z` flags are relevant for Miri: sets this flag per default. * `-Zmir-emit-retag` controls whether `Retag` statements are emitted. Miri enables this per default because it is needed for validation. +* `-Zmiri-track-pointer-tag=` aborts interpretation with a backtrace when the + given pointer tag is popped from a borrow stack (which is where the tag + becomes invalid and any future use of it will error anyway). This helps you + in finding out why UB is happening and where in your code would be a good + place to look for it. Moreover, Miri recognizes some environment variables: diff --git a/benches/helpers/miri_helper.rs b/benches/helpers/miri_helper.rs index aa0f3d9959..e2951110ac 100644 --- a/benches/helpers/miri_helper.rs +++ b/benches/helpers/miri_helper.rs @@ -32,6 +32,7 @@ impl rustc_driver::Callbacks for MiriCompilerCalls<'_> { excluded_env_vars: vec![], args: vec![], seed: None, + tracked_pointer_tag: None, }; eval_main(tcx, entry_def_id, config); }); diff --git a/src/bin/miri-rustc-tests.rs b/src/bin/miri-rustc-tests.rs index df8bb583f8..370d2a2061 100644 --- a/src/bin/miri-rustc-tests.rs +++ b/src/bin/miri-rustc-tests.rs @@ -45,6 +45,7 @@ impl rustc_driver::Callbacks for MiriCompilerCalls { excluded_env_vars: vec![], args: vec![], seed: None, + tracked_pointer_tag: None, }; let did = self.0.hir().body_owner_def_id(body_id); println!("running test: {}", self.0.def_path_debug_str(did)); @@ -64,7 +65,8 @@ impl rustc_driver::Callbacks for MiriCompilerCalls { ignore_leaks: false, excluded_env_vars: vec![], args: vec![], - seed: None + seed: None, + tracked_pointer_tag: None, }; miri::eval_main(tcx, entry_def_id, config); diff --git a/src/bin/miri.rs b/src/bin/miri.rs index e255afc346..48af812962 100644 --- a/src/bin/miri.rs +++ b/src/bin/miri.rs @@ -126,6 +126,7 @@ fn main() { let mut communicate = false; let mut ignore_leaks = false; let mut seed: Option = None; + let mut tracked_pointer_tag: Option = None; let mut rustc_args = vec![]; let mut miri_args = vec![]; let mut after_dashdash = false; @@ -176,6 +177,17 @@ fn main() { arg if arg.starts_with("-Zmiri-env-exclude=") => { excluded_env_vars.push(arg.trim_start_matches("-Zmiri-env-exclude=").to_owned()); }, + arg if arg.starts_with("-Zmiri-track-pointer-tag=") => { + let id: u64 = match arg.trim_start_matches("-Zmiri-track-pointer-tag=").parse() { + Ok(id) => id, + Err(err) => panic!("-Zmiri-track-pointer-tag requires a valid `u64` as the argument: {}", err), + }; + if let Some(id) = miri::PtrId::new(id) { + tracked_pointer_tag = Some(id); + } else { + panic!("-Zmiri-track-pointer-tag must be a nonzero id"); + } + }, _ => { rustc_args.push(arg); } @@ -208,6 +220,7 @@ fn main() { excluded_env_vars, seed, args: miri_args, + tracked_pointer_tag, }; rustc_driver::install_ice_hook(); let result = rustc_driver::catch_fatal_errors(move || { diff --git a/src/eval.rs b/src/eval.rs index 8be42226b5..9a70663716 100644 --- a/src/eval.rs +++ b/src/eval.rs @@ -26,11 +26,14 @@ pub struct MiriConfig { pub args: Vec, /// The seed to use when non-determinism or randomness are required (e.g. ptr-to-int cast, `getrandom()`). pub seed: Option, + /// The stacked borrow id to report about + pub tracked_pointer_tag: Option, } /// Details of premature program termination. pub enum TerminationInfo { Exit(i64), + PoppedTrackedPointerTag(Item), Abort, } @@ -47,7 +50,7 @@ pub fn create_ecx<'mir, 'tcx: 'mir>( tcx.at(syntax::source_map::DUMMY_SP), ty::ParamEnv::reveal_all(), Evaluator::new(config.communicate), - MemoryExtra::new(StdRng::seed_from_u64(config.seed.unwrap_or(0)), config.validate), + MemoryExtra::new(StdRng::seed_from_u64(config.seed.unwrap_or(0)), config.validate, config.tracked_pointer_tag), ); // Complete initialization. EnvVars::init(&mut ecx, config.excluded_env_vars); @@ -216,6 +219,8 @@ pub fn eval_main<'tcx>(tcx: TyCtxt<'tcx>, main_id: DefId, config: MiriConfig) -> .expect("invalid MachineStop payload"); match info { TerminationInfo::Exit(code) => return Some(*code), + TerminationInfo::PoppedTrackedPointerTag(item) => + format!("popped tracked tag for item {:?}", item), TerminationInfo::Abort => format!("the evaluated program aborted execution") } diff --git a/src/lib.rs b/src/lib.rs index abed7ab9df..ee13631727 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -43,7 +43,10 @@ pub use crate::operator::EvalContextExt as OperatorEvalContextExt; pub use crate::range_map::RangeMap; pub use crate::helpers::{EvalContextExt as HelpersEvalContextExt}; pub use crate::mono_hash_map::MonoHashMap; -pub use crate::stacked_borrows::{EvalContextExt as StackedBorEvalContextExt, Tag, Permission, Stack, Stacks, Item}; +pub use crate::stacked_borrows::{ + EvalContextExt as StackedBorEvalContextExt, Tag, Permission, Stack, Stacks, Item, PtrId, + GlobalState, +}; pub use crate::machine::{ PAGE_SIZE, STACK_ADDR, STACK_SIZE, NUM_CPUS, MemoryExtra, AllocExtra, FrameData, MiriMemoryKind, Evaluator, MiriEvalContext, MiriEvalContextExt, diff --git a/src/machine.rs b/src/machine.rs index 65b6bcfb2a..d5cd86d978 100644 --- a/src/machine.rs +++ b/src/machine.rs @@ -77,9 +77,9 @@ pub struct MemoryExtra { } impl MemoryExtra { - pub fn new(rng: StdRng, validate: bool) -> Self { + pub fn new(rng: StdRng, validate: bool, tracked_pointer_tag: Option) -> Self { MemoryExtra { - stacked_borrows: Default::default(), + stacked_borrows: Rc::new(RefCell::new(GlobalState::new(tracked_pointer_tag))), intptrcast: Default::default(), rng: RefCell::new(rng), validate, diff --git a/src/stacked_borrows.rs b/src/stacked_borrows.rs index 23a32fc2ac..8782eb83d1 100644 --- a/src/stacked_borrows.rs +++ b/src/stacked_borrows.rs @@ -12,7 +12,7 @@ use rustc::hir::Mutability::{Mutable, Immutable}; use rustc::mir::RetagKind; use crate::{ - InterpResult, HelpersEvalContextExt, + InterpResult, HelpersEvalContextExt, TerminationInfo, MemoryKind, MiriMemoryKind, RangeMap, AllocId, Pointer, Immediate, ImmTy, PlaceTy, MPlaceTy, }; @@ -105,6 +105,8 @@ pub struct GlobalState { next_call_id: CallId, /// Those call IDs corresponding to functions that are still running. active_calls: HashSet, + /// The id to trace in this execution run + tracked_pointer_tag: Option, } /// Memory extra state gives us interior mutable access to the global state. pub type MemoryExtra = Rc>; @@ -151,18 +153,17 @@ impl fmt::Display for RefKind { } /// Utilities for initialization and ID generation -impl Default for GlobalState { - fn default() -> Self { +impl GlobalState { + pub fn new(tracked_pointer_tag: Option) -> Self { GlobalState { next_ptr_id: NonZeroU64::new(1).unwrap(), base_ptr_ids: HashMap::default(), next_call_id: NonZeroU64::new(1).unwrap(), active_calls: HashSet::default(), + tracked_pointer_tag, } } -} -impl GlobalState { fn new_ptr(&mut self) -> PtrId { let id = self.next_ptr_id; self.next_ptr_id = NonZeroU64::new(id.get() + 1).unwrap(); @@ -270,6 +271,11 @@ impl<'tcx> Stack { /// Check if the given item is protected. fn check_protector(item: &Item, tag: Option, global: &GlobalState) -> InterpResult<'tcx> { + if let Tag::Tagged(id) = item.tag { + if Some(id) == global.tracked_pointer_tag { + throw_machine_stop!(TerminationInfo::PoppedTrackedPointerTag(item.clone())); + } + } if let Some(call) = item.protector { if global.is_active(call) { if let Some(tag) = tag {