diff --git a/src/diagnostics.rs b/src/diagnostics.rs index 1a39a1ff33..56cd3bb83d 100644 --- a/src/diagnostics.rs +++ b/src/diagnostics.rs @@ -7,7 +7,8 @@ use log::trace; use rustc_middle::ty; use rustc_span::{source_map::DUMMY_SP, Span, SpanData, Symbol}; -use crate::stacked_borrows::{AccessKind, SbTag}; +use crate::helpers::HexRange; +use crate::stacked_borrows::{diagnostics::TagHistory, AccessKind, SbTag}; use crate::*; /// Details of premature program termination. @@ -19,6 +20,7 @@ pub enum TerminationInfo { msg: String, help: Option, url: String, + history: Option, }, Deadlock, MultipleSymbolDefinitions { @@ -155,12 +157,46 @@ pub fn report_error<'tcx, 'mir>( (None, format!("pass the flag `-Zmiri-disable-isolation` to disable isolation;")), (None, format!("or pass `-Zmiri-isolation-error=warn` to configure Miri to return an error code from isolated operations (if supported for that operation) and continue with a warning")), ], - ExperimentalUb { url, help, .. } => { + ExperimentalUb { url, help, history, .. } => { msg.extend(help.clone()); - vec![ + let mut helps = vec![ (None, format!("this indicates a potential bug in the program: it performed an invalid operation, but the rules it violated are still experimental")), - (None, format!("see {} for further information", url)) - ] + (None, format!("see {} for further information", url)), + ]; + match history { + Some(TagHistory::Tagged {tag, created: (created_range, created_span), invalidated, protected }) => { + let msg = format!("{:?} was created by a retag at offsets {}", tag, HexRange(*created_range)); + helps.push((Some(created_span.clone()), msg)); + if let Some((invalidated_range, invalidated_span)) = invalidated { + let msg = format!("{:?} was later invalidated at offsets {}", tag, HexRange(*invalidated_range)); + helps.push((Some(invalidated_span.clone()), msg)); + } + if let Some((protecting_tag, protecting_tag_span, protection_span)) = protected { + helps.push((Some(protecting_tag_span.clone()), format!("{:?} was protected due to {:?} which was created here", tag, protecting_tag))); + helps.push((Some(protection_span.clone()), "this protector is live for this call".to_string())); + } + } + Some(TagHistory::Untagged{ recently_created, recently_invalidated, matching_created, protected }) => { + if let Some((range, span)) = recently_created { + let msg = format!("tag was most recently created at offsets {}", HexRange(*range)); + helps.push((Some(span.clone()), msg)); + } + if let Some((range, span)) = recently_invalidated { + let msg = format!("tag was later invalidated at offsets {}", HexRange(*range)); + helps.push((Some(span.clone()), msg)); + } + if let Some((range, span)) = matching_created { + let msg = format!("this tag was also created here at offsets {}", HexRange(*range)); + helps.push((Some(span.clone()), msg)); + } + if let Some((protecting_tag, protecting_tag_span, protection_span)) = protected { + helps.push((Some(protecting_tag_span.clone()), format!("{:?} was protected due to a tag which was created here", protecting_tag))); + helps.push((Some(protection_span.clone()), "this protector is live for this call".to_string())); + } + } + None => {} + } + helps } MultipleSymbolDefinitions { first, first_crate, second, second_crate, .. } => vec![ diff --git a/src/helpers.rs b/src/helpers.rs index 107a255199..2198bba4fe 100644 --- a/src/helpers.rs +++ b/src/helpers.rs @@ -1,5 +1,6 @@ use std::mem; use std::num::NonZeroUsize; +use std::rc::Rc; use std::time::Duration; use log::trace; @@ -797,7 +798,7 @@ pub fn isolation_abort_error(name: &str) -> InterpResult<'static> { /// Retrieve the list of local crates that should have been passed by cargo-miri in /// MIRI_LOCAL_CRATES and turn them into `CrateNum`s. -pub fn get_local_crates(tcx: &TyCtxt<'_>) -> Vec { +pub fn get_local_crates(tcx: &TyCtxt<'_>) -> Rc<[CrateNum]> { // Convert the local crate names from the passed-in config into CrateNums so that they can // be looked up quickly during execution let local_crate_names = std::env::var("MIRI_LOCAL_CRATES") @@ -811,5 +812,14 @@ pub fn get_local_crates(tcx: &TyCtxt<'_>) -> Vec { local_crates.push(crate_num); } } - local_crates + Rc::from(local_crates.as_slice()) +} + +/// Formats an AllocRange like [0x1..0x3], for use in diagnostics. +pub struct HexRange(pub AllocRange); + +impl std::fmt::Display for HexRange { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "[{:#x}..{:#x}]", self.0.start.bytes(), self.0.end().bytes()) + } } diff --git a/src/machine.rs b/src/machine.rs index 0a8a229c8a..f215f465c0 100644 --- a/src/machine.rs +++ b/src/machine.rs @@ -6,6 +6,7 @@ use std::cell::RefCell; use std::collections::HashSet; use std::fmt; use std::num::NonZeroU64; +use std::rc::Rc; use std::time::Instant; use rand::rngs::StdRng; @@ -273,7 +274,7 @@ pub struct Evaluator<'mir, 'tcx> { pub(crate) backtrace_style: BacktraceStyle, /// Crates which are considered local for the purposes of error reporting. - pub(crate) local_crates: Vec, + pub(crate) local_crates: Rc<[CrateNum]>, /// Mapping extern static names to their base pointer. extern_statics: FxHashMap>, @@ -568,7 +569,14 @@ impl<'mir, 'tcx> Machine<'mir, 'tcx> for Evaluator<'mir, 'tcx> { let kind = kind.expect("we set our STATIC_KIND so this cannot be None"); let alloc = alloc.into_owned(); let stacks = if let Some(stacked_borrows) = &ecx.machine.stacked_borrows { - Some(Stacks::new_allocation(id, alloc.size(), stacked_borrows, kind)) + Some(Stacks::new_allocation( + id, + alloc.size(), + stacked_borrows, + kind, + &ecx.machine.threads, + ecx.machine.local_crates.clone(), + )) } else { None }; @@ -633,6 +641,7 @@ impl<'mir, 'tcx> Machine<'mir, 'tcx> for Evaluator<'mir, 'tcx> { tag, range, machine.stacked_borrows.as_ref().unwrap(), + &machine.threads, ) } else { Ok(()) @@ -655,7 +664,8 @@ impl<'mir, 'tcx> Machine<'mir, 'tcx> for Evaluator<'mir, 'tcx> { alloc_id, tag, range, - machine.stacked_borrows.as_mut().unwrap(), + machine.stacked_borrows.as_ref().unwrap(), + &machine.threads, ) } else { Ok(()) @@ -681,7 +691,7 @@ impl<'mir, 'tcx> Machine<'mir, 'tcx> for Evaluator<'mir, 'tcx> { alloc_id, tag, range, - machine.stacked_borrows.as_mut().unwrap(), + machine.stacked_borrows.as_ref().unwrap(), ) } else { Ok(()) diff --git a/src/stacked_borrows.rs b/src/stacked_borrows.rs index d6caba8171..8dda4a9e22 100644 --- a/src/stacked_borrows.rs +++ b/src/stacked_borrows.rs @@ -5,6 +5,7 @@ use log::trace; use std::cell::RefCell; use std::fmt; use std::num::NonZeroU64; +use std::rc::Rc; use rustc_data_structures::fx::{FxHashMap, FxHashSet}; use rustc_hir::Mutability; @@ -13,12 +14,18 @@ use rustc_middle::ty::{ self, layout::{HasParamEnv, LayoutOf}, }; +use rustc_span::def_id::CrateNum; use rustc_span::DUMMY_SP; use rustc_target::abi::Size; use std::collections::HashSet; use crate::*; +pub mod diagnostics; +use diagnostics::AllocHistory; + +use diagnostics::TagHistory; + pub type PtrId = NonZeroU64; pub type CallId = NonZeroU64; pub type AllocExtra = Stacks; @@ -90,6 +97,8 @@ pub struct Stack { pub struct Stacks { // Even reading memory can have effects on the stack, so we need a `RefCell` here. stacks: RefCell>, + /// Stores past operations on this allocation + history: RefCell, } /// Extra global state, available to the memory access hooks. @@ -112,6 +121,7 @@ pub struct GlobalStateInner { /// Whether to track raw pointers. tag_raw: bool, } + /// We need interior mutable access to the global state. pub type GlobalState = RefCell; @@ -221,13 +231,18 @@ impl GlobalStateInner { } /// Error reporting -fn err_sb_ub(msg: String, help: Option) -> InterpError<'static> { +pub fn err_sb_ub( + msg: String, + help: Option, + history: Option, +) -> InterpError<'static> { err_machine_stop!(TerminationInfo::ExperimentalUb { msg, help, url: format!( "https://github.com/rust-lang/unsafe-code-guidelines/blob/master/wip/stacked-borrows.md" ), + history }) } @@ -306,33 +321,44 @@ impl<'tcx> Stack { /// The `provoking_access` argument is only used to produce diagnostics. /// It is `Some` when we are granting the contained access for said tag, and it is /// `None` during a deallocation. + /// Within `provoking_access, the `AllocRange` refers the entire operation, and + /// the `Size` refers to the specific location in the `AllocRange` that we are + /// currently checking. fn check_protector( item: &Item, - provoking_access: Option<(SbTag, AccessKind)>, + provoking_access: Option<(SbTag, AllocRange, Size, AccessKind)>, // just for debug printing and error messages global: &GlobalStateInner, + alloc_history: &mut AllocHistory, ) -> InterpResult<'tcx> { if let SbTag::Tagged(id) = item.tag { if global.tracked_pointer_tags.contains(&id) { register_diagnostic(NonHaltingDiagnostic::PoppedPointerTag( *item, - provoking_access, + provoking_access.map(|(tag, _alloc_range, _size, access)| (tag, access)), )); } } if let Some(call) = item.protector { if global.is_active(call) { - if let Some((tag, _)) = provoking_access { + if let Some((tag, alloc_range, offset, _access)) = provoking_access { Err(err_sb_ub( format!( "not granting access to tag {:?} because incompatible item is protected: {:?}", tag, item ), None, + alloc_history.get_logs_relevant_to( + tag, + alloc_range, + offset, + Some(item.tag), + ), ))? } else { Err(err_sb_ub( format!("deallocating while item is protected: {:?}", item), None, + None, ))? } } @@ -348,15 +374,17 @@ impl<'tcx> Stack { &mut self, access: AccessKind, tag: SbTag, - (alloc_id, range, offset): (AllocId, AllocRange, Size), // just for debug printing and error messages - global: &GlobalStateInner, + (alloc_id, alloc_range, offset): (AllocId, AllocRange, Size), // just for debug printing and error messages + global: &mut GlobalStateInner, + threads: &ThreadManager<'_, 'tcx>, + alloc_history: &mut AllocHistory, ) -> InterpResult<'tcx> { // Two main steps: Find granting item, remove incompatible items above. // Step 1: Find granting item. - let granting_idx = self - .find_granting(access, tag) - .ok_or_else(|| self.access_error(access, tag, alloc_id, range, offset))?; + let granting_idx = self.find_granting(access, tag).ok_or_else(|| { + alloc_history.access_error(access, tag, alloc_id, alloc_range, offset, self) + })?; // Step 2: Remove incompatible items above them. Make sure we do not remove protected // items. Behavior differs for reads and writes. @@ -366,7 +394,13 @@ impl<'tcx> Stack { let first_incompatible_idx = self.find_first_write_incompatible(granting_idx); for item in self.borrows.drain(first_incompatible_idx..).rev() { trace!("access: popping item {:?}", item); - Stack::check_protector(&item, Some((tag, access)), global)?; + Stack::check_protector( + &item, + Some((tag, alloc_range, offset, access)), + global, + alloc_history, + )?; + alloc_history.log_invalidation(item.tag, alloc_range, threads); } } else { // On a read, *disable* all `Unique` above the granting item. This ensures U2 for read accesses. @@ -381,8 +415,14 @@ impl<'tcx> Stack { let item = &mut self.borrows[idx]; if item.perm == Permission::Unique { trace!("access: disabling item {:?}", item); - Stack::check_protector(item, Some((tag, access)), global)?; + Stack::check_protector( + item, + Some((tag, alloc_range, offset, access)), + global, + alloc_history, + )?; item.perm = Permission::Disabled; + alloc_history.log_invalidation(item.tag, alloc_range, threads); } } } @@ -396,20 +436,24 @@ impl<'tcx> Stack { fn dealloc( &mut self, tag: SbTag, - dbg_ptr: Pointer, // just for debug printing and error messages + (alloc_id, alloc_range, offset): (AllocId, AllocRange, Size), // just for debug printing and error messages global: &GlobalStateInner, + alloc_history: &mut AllocHistory, ) -> InterpResult<'tcx> { // Step 1: Find granting item. self.find_granting(AccessKind::Write, tag).ok_or_else(|| { err_sb_ub(format!( "no item granting write access for deallocation to tag {:?} at {:?} found in borrow stack", - tag, dbg_ptr, - ), None) + tag, alloc_id, + ), + None, + alloc_history.get_logs_relevant_to(tag, alloc_range, offset, None), + ) })?; // Step 2: Remove all items. Also checks for protectors. for item in self.borrows.drain(..).rev() { - Stack::check_protector(&item, None, global)?; + Stack::check_protector(&item, None, global, alloc_history)?; } Ok(()) @@ -426,16 +470,18 @@ impl<'tcx> Stack { derived_from: SbTag, new: Item, (alloc_id, alloc_range, offset): (AllocId, AllocRange, Size), // just for debug printing and error messages - global: &GlobalStateInner, + global: &mut GlobalStateInner, + threads: &ThreadManager<'_, 'tcx>, + alloc_history: &mut AllocHistory, ) -> InterpResult<'tcx> { // Figure out which access `perm` corresponds to. let access = if new.perm.grants(AccessKind::Write) { AccessKind::Write } else { AccessKind::Read }; // Now we figure out which item grants our parent (`derived_from`) this kind of access. // We use that to determine where to put the new item. - let granting_idx = self - .find_granting(access, derived_from) - .ok_or_else(|| self.grant_error(derived_from, new, alloc_id, alloc_range, offset))?; + let granting_idx = self.find_granting(access, derived_from).ok_or_else(|| { + alloc_history.grant_error(derived_from, new, alloc_id, alloc_range, offset, self) + })?; // Compute where to put the new item. // Either way, we ensure that we insert the new item in a way such that between @@ -454,7 +500,14 @@ impl<'tcx> Stack { // A "safe" reborrow for a pointer that actually expects some aliasing guarantees. // Here, creating a reference actually counts as an access. // This ensures F2b for `Unique`, by removing offending `SharedReadOnly`. - self.access(access, derived_from, (alloc_id, alloc_range, offset), global)?; + self.access( + access, + derived_from, + (alloc_id, alloc_range, offset), + global, + threads, + alloc_history, + )?; // We insert "as far up as possible": We know only compatible items are remaining // on top of `derived_from`, and we want the new item at the top so that we @@ -474,94 +527,32 @@ impl<'tcx> Stack { Ok(()) } - - /// Report a descriptive error when `new` could not be granted from `derived_from`. - fn grant_error( - &self, - derived_from: SbTag, - new: Item, - alloc_id: AllocId, - alloc_range: AllocRange, - error_offset: Size, - ) -> InterpError<'static> { - let action = format!( - "trying to reborrow {:?} for {:?} permission at {}[{:#x}]", - derived_from, - new.perm, - alloc_id, - error_offset.bytes(), - ); - err_sb_ub( - format!("{}{}", action, self.error_cause(derived_from)), - Some(Self::operation_summary("a reborrow", alloc_id, alloc_range)), - ) - } - - /// Report a descriptive error when `access` is not permitted based on `tag`. - fn access_error( - &self, - access: AccessKind, - tag: SbTag, - alloc_id: AllocId, - alloc_range: AllocRange, - error_offset: Size, - ) -> InterpError<'static> { - let action = format!( - "attempting a {} using {:?} at {}[{:#x}]", - access, - tag, - alloc_id, - error_offset.bytes(), - ); - err_sb_ub( - format!("{}{}", action, self.error_cause(tag)), - Some(Self::operation_summary("an access", alloc_id, alloc_range)), - ) - } - - fn operation_summary( - operation: &'static str, - alloc_id: AllocId, - alloc_range: AllocRange, - ) -> String { - format!( - "this error occurs as part of {} at {:?}[{:#x}..{:#x}]", - operation, - alloc_id, - alloc_range.start.bytes(), - alloc_range.end().bytes() - ) - } - - fn error_cause(&self, tag: SbTag) -> &'static str { - if self.borrows.iter().any(|item| item.tag == tag && item.perm != Permission::Disabled) { - ", but that tag only grants SharedReadOnly permission for this location" - } else { - ", but that tag does not exist in the borrow stack for this location" - } - } } // # Stacked Borrows Core End /// Map per-stack operations to higher-level per-location-range operations. impl<'tcx> Stacks { /// Creates new stack with initial tag. - fn new(size: Size, perm: Permission, tag: SbTag) -> Self { + fn new(size: Size, perm: Permission, tag: SbTag, local_crates: Rc<[CrateNum]>) -> Self { let item = Item { perm, tag, protector: None }; let stack = Stack { borrows: vec![item] }; - Stacks { stacks: RefCell::new(RangeMap::new(size, stack)) } + Stacks { + stacks: RefCell::new(RangeMap::new(size, stack)), + history: RefCell::new(AllocHistory::new(local_crates)), + } } /// Call `f` on every stack in the range. fn for_each( &self, range: AllocRange, - f: impl Fn(Size, &mut Stack) -> InterpResult<'tcx>, + mut f: impl FnMut(Size, &mut Stack, &mut AllocHistory) -> InterpResult<'tcx>, ) -> InterpResult<'tcx> { let mut stacks = self.stacks.borrow_mut(); + let history = &mut *self.history.borrow_mut(); for (offset, stack) in stacks.iter_mut(range.start, range.size) { - f(offset, stack)?; + f(offset, stack, history)?; } Ok(()) } @@ -570,11 +561,12 @@ impl<'tcx> Stacks { fn for_each_mut( &mut self, range: AllocRange, - f: impl Fn(Size, &mut Stack) -> InterpResult<'tcx>, + mut f: impl FnMut(Size, &mut Stack, &mut AllocHistory) -> InterpResult<'tcx>, ) -> InterpResult<'tcx> { let stacks = self.stacks.get_mut(); + let history = &mut *self.history.borrow_mut(); for (offset, stack) in stacks.iter_mut(range.start, range.size) { - f(offset, stack)?; + f(offset, stack, history)?; } Ok(()) } @@ -587,6 +579,8 @@ impl Stacks { size: Size, state: &GlobalState, kind: MemoryKind, + threads: &ThreadManager<'_, '_>, + local_crates: Rc<[CrateNum]>, ) -> Self { let mut extra = state.borrow_mut(); let (base_tag, perm) = match kind { @@ -620,7 +614,14 @@ impl Stacks { (tag, Permission::SharedReadWrite) } }; - Stacks::new(size, perm, base_tag) + let stacks = Stacks::new(size, perm, base_tag, local_crates); + stacks.history.borrow_mut().log_creation( + None, + base_tag, + alloc_range(Size::ZERO, size), + threads, + ); + stacks } #[inline(always)] @@ -630,6 +631,7 @@ impl Stacks { tag: SbTag, range: AllocRange, state: &GlobalState, + threads: &ThreadManager<'_, 'tcx>, ) -> InterpResult<'tcx> { trace!( "read access with tag {:?}: {:?}, size {}", @@ -637,9 +639,16 @@ impl Stacks { Pointer::new(alloc_id, range.start), range.size.bytes() ); - let global = &*state.borrow(); - self.for_each(range, move |offset, stack| { - stack.access(AccessKind::Read, tag, (alloc_id, range, offset), global) + let mut state = state.borrow_mut(); + self.for_each(range, |offset, stack, history| { + stack.access( + AccessKind::Read, + tag, + (alloc_id, range, offset), + &mut state, + threads, + history, + ) }) } @@ -649,7 +658,8 @@ impl Stacks { alloc_id: AllocId, tag: SbTag, range: AllocRange, - state: &mut GlobalState, + state: &GlobalState, + threads: &ThreadManager<'_, 'tcx>, ) -> InterpResult<'tcx> { trace!( "write access with tag {:?}: {:?}, size {}", @@ -657,9 +667,16 @@ impl Stacks { Pointer::new(alloc_id, range.start), range.size.bytes() ); - let global = state.get_mut(); - self.for_each_mut(range, move |offset, stack| { - stack.access(AccessKind::Write, tag, (alloc_id, range, offset), global) + let mut state = state.borrow_mut(); + self.for_each_mut(range, |offset, stack, history| { + stack.access( + AccessKind::Write, + tag, + (alloc_id, range, offset), + &mut state, + threads, + history, + ) }) } @@ -669,13 +686,14 @@ impl Stacks { alloc_id: AllocId, tag: SbTag, range: AllocRange, - state: &mut GlobalState, + state: &GlobalState, ) -> InterpResult<'tcx> { trace!("deallocation with tag {:?}: {:?}, size {}", tag, alloc_id, range.size.bytes()); - let global = state.get_mut(); - self.for_each_mut(range, move |offset, stack| { - stack.dealloc(tag, Pointer::new(alloc_id, offset), global) - }) + let mut state = state.borrow_mut(); + self.for_each_mut(range, |offset, stack, history| { + stack.dealloc(tag, (alloc_id, range, offset), &mut state, history) + })?; + Ok(()) } } @@ -705,6 +723,22 @@ trait EvalContextPrivExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx } let (alloc_id, base_offset, orig_tag) = this.ptr_get_alloc_id(place.ptr)?; + { + let extra = this.get_alloc_extra(alloc_id)?; + let stacked_borrows = + extra.stacked_borrows.as_ref().expect("we should have Stacked Borrows data"); + let mut alloc_history = stacked_borrows.history.borrow_mut(); + alloc_history.log_creation( + Some(orig_tag), + new_tag, + alloc_range(base_offset, base_offset + size), + &this.machine.threads, + ); + if protect { + alloc_history.log_protector(orig_tag, new_tag, &this.machine.threads); + } + } + // Ensure we bail out if the pointer goes out-of-bounds (see miri#1050). let (alloc_size, _) = this.get_alloc_size_and_align(alloc_id, AllocCheck::Dereferenceable)?; @@ -753,7 +787,6 @@ trait EvalContextPrivExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx let extra = this.get_alloc_extra(alloc_id)?; let stacked_borrows = extra.stacked_borrows.as_ref().expect("we should have Stacked Borrows data"); - let global = this.machine.stacked_borrows.as_ref().unwrap().borrow(); this.visit_freeze_sensitive(place, size, |mut range, frozen| { // Adjust range. range.start += base_offset; @@ -764,8 +797,16 @@ trait EvalContextPrivExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx Permission::SharedReadWrite }; let item = Item { perm, tag: new_tag, protector }; - stacked_borrows.for_each(range, |offset, stack| { - stack.grant(orig_tag, item, (alloc_id, range, offset), &*global) + let mut global = this.machine.stacked_borrows.as_ref().unwrap().borrow_mut(); + stacked_borrows.for_each(range, |offset, stack, history| { + stack.grant( + orig_tag, + item, + (alloc_id, range, offset), + &mut *global, + &this.machine.threads, + history, + ) }) })?; return Ok(()); @@ -774,15 +815,23 @@ trait EvalContextPrivExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx // Here we can avoid `borrow()` calls because we have mutable references. // Note that this asserts that the allocation is mutable -- but since we are creating a // mutable pointer, that seems reasonable. - let (alloc_extra, memory_extra) = this.get_alloc_extra_mut(alloc_id)?; + let (alloc_extra, machine) = this.get_alloc_extra_mut(alloc_id)?; let stacked_borrows = alloc_extra.stacked_borrows.as_mut().expect("we should have Stacked Borrows data"); - let global = memory_extra.stacked_borrows.as_mut().unwrap().get_mut(); let item = Item { perm, tag: new_tag, protector }; let range = alloc_range(base_offset, size); - stacked_borrows.for_each_mut(alloc_range(base_offset, size), |offset, stack| { - stack.grant(orig_tag, item, (alloc_id, range, offset), global) + let mut global = machine.stacked_borrows.as_ref().unwrap().borrow_mut(); + stacked_borrows.for_each_mut(range, |offset, stack, history| { + stack.grant( + orig_tag, + item, + (alloc_id, range, offset), + &mut global, + &machine.threads, + history, + ) })?; + Ok(()) } diff --git a/src/stacked_borrows/diagnostics.rs b/src/stacked_borrows/diagnostics.rs new file mode 100644 index 0000000000..734c3a14e3 --- /dev/null +++ b/src/stacked_borrows/diagnostics.rs @@ -0,0 +1,274 @@ +use smallvec::SmallVec; +use std::rc::Rc; + +use rustc_middle::mir::interpret::{AllocId, AllocRange}; +use rustc_span::def_id::CrateNum; +use rustc_span::{Span, SpanData}; +use rustc_target::abi::Size; + +use crate::helpers::HexRange; +use crate::stacked_borrows::{err_sb_ub, AccessKind, Permission}; +use crate::Item; +use crate::SbTag; +use crate::Stack; +use crate::ThreadManager; + +use rustc_middle::mir::interpret::InterpError; + +#[derive(Clone, Debug)] +pub struct AllocHistory { + // The time tags can be compressed down to one bit per event, by just storing a Vec + // where each bit is set to indicate if the event was a creation or a retag + current_time: usize, + creations: smallvec::SmallVec<[Event; 2]>, + invalidations: smallvec::SmallVec<[Event; 1]>, + protectors: smallvec::SmallVec<[Protection; 1]>, + /// This field is a clone of the `local_crates` field on `Evaluator`. + local_crates: Rc<[CrateNum]>, +} + +#[derive(Clone, Debug)] +struct Protection { + orig_tag: SbTag, + tag: SbTag, + span: Span, +} + +#[derive(Clone, Debug)] +struct Event { + time: usize, + parent: Option, + tag: SbTag, + range: AllocRange, + span: Span, +} + +pub enum TagHistory { + Tagged { + tag: SbTag, + created: (AllocRange, SpanData), + invalidated: Option<(AllocRange, SpanData)>, + protected: Option<(SbTag, SpanData, SpanData)>, + }, + Untagged { + recently_created: Option<(AllocRange, SpanData)>, + recently_invalidated: Option<(AllocRange, SpanData)>, + matching_created: Option<(AllocRange, SpanData)>, + protected: Option<(SbTag, SpanData, SpanData)>, + }, +} + +impl AllocHistory { + pub fn new(local_crates: Rc<[CrateNum]>) -> Self { + Self { + current_time: 0, + creations: SmallVec::new(), + invalidations: SmallVec::new(), + protectors: SmallVec::new(), + local_crates, + } + } + + fn current_span(&self, threads: &ThreadManager<'_, '_>) -> Span { + threads + .active_thread_stack() + .into_iter() + .rev() + .find(|frame| { + let def_id = frame.instance.def_id(); + def_id.is_local() || self.local_crates.contains(&def_id.krate) + }) + .map(|frame| frame.current_span()) + .unwrap_or(rustc_span::DUMMY_SP) + } + + pub fn log_creation( + &mut self, + parent: Option, + tag: SbTag, + range: AllocRange, + threads: &ThreadManager<'_, '_>, + ) { + let span = self.current_span(threads); + self.creations.push(Event { parent, tag, range, span, time: self.current_time }); + self.current_time += 1; + } + + pub fn log_invalidation( + &mut self, + tag: SbTag, + range: AllocRange, + threads: &ThreadManager<'_, '_>, + ) { + let span = self.current_span(threads); + self.invalidations.push(Event { parent: None, tag, range, span, time: self.current_time }); + self.current_time += 1; + } + + pub fn log_protector(&mut self, orig_tag: SbTag, tag: SbTag, threads: &ThreadManager<'_, '_>) { + let span = self.current_span(threads); + self.protectors.push(Protection { orig_tag, tag, span }); + self.current_time += 1; + } + + pub fn get_logs_relevant_to( + &self, + tag: SbTag, + alloc_range: AllocRange, + offset: Size, + protector_tag: Option, + ) -> Option { + let protected = protector_tag + .and_then(|protector| { + self.protectors.iter().find_map(|protection| { + if protection.tag == protector { + Some((protection.orig_tag, protection.span.data())) + } else { + None + } + }) + }) + .and_then(|(tag, call_span)| { + self.creations.iter().rev().find_map(|event| { + if event.tag == tag { + Some((event.parent?, event.span.data(), call_span)) + } else { + None + } + }) + }); + + if let SbTag::Tagged(_) = tag { + let get_matching = |events: &[Event]| { + events.iter().rev().find_map(|event| { + if event.tag == tag { Some((event.range, event.span.data())) } else { None } + }) + }; + Some(TagHistory::Tagged { + tag, + created: get_matching(&self.creations)?, + invalidated: get_matching(&self.invalidations), + protected, + }) + } else { + let mut created_time = 0; + // Find the most recently created tag that satsfies this offset + let recently_created = self.creations.iter().rev().find_map(|event| { + if event.tag == tag && offset >= event.range.start && offset < event.range.end() { + created_time = event.time; + Some((event.range, event.span.data())) + } else { + None + } + }); + + // Find a different recently created tag that satisfies this whole operation, predates + // the recently created tag, and has a different span. + // We're trying to make a guess at which span the user wanted to provide the tag that + // they're using. + let matching_created = recently_created.and_then(|(_created_range, created_span)| { + self.creations.iter().rev().find_map(|event| { + if event.tag == tag + && alloc_range.start >= event.range.start + && alloc_range.end() <= event.range.end() + && event.span.data() != created_span + && event.time != created_time + { + Some((event.range, event.span.data())) + } else { + None + } + }) + }); + + // Find the most recent invalidation of this tag which post-dates the creation + let recently_invalidated = recently_created.and_then(|_| { + self.invalidations + .iter() + .rev() + .take_while(|event| event.time > created_time) + .find_map(|event| { + if event.tag == tag + && offset >= event.range.start + && offset < event.range.end() + { + Some((event.range, event.span.data())) + } else { + None + } + }) + }); + + Some(TagHistory::Untagged { + recently_created, + matching_created, + recently_invalidated, + protected, + }) + } + } + + /// Report a descriptive error when `new` could not be granted from `derived_from`. + pub fn grant_error( + &self, + derived_from: SbTag, + new: Item, + alloc_id: AllocId, + alloc_range: AllocRange, + error_offset: Size, + stack: &Stack, + ) -> InterpError<'static> { + let action = format!( + "trying to reborrow {:?} for {:?} permission at {}[{:#x}]", + derived_from, + new.perm, + alloc_id, + error_offset.bytes(), + ); + err_sb_ub( + format!("{}{}", action, error_cause(stack, derived_from)), + Some(operation_summary("a reborrow", alloc_id, alloc_range)), + self.get_logs_relevant_to(derived_from, alloc_range, error_offset, None), + ) + } + + /// Report a descriptive error when `access` is not permitted based on `tag`. + pub fn access_error( + &self, + access: AccessKind, + tag: SbTag, + alloc_id: AllocId, + alloc_range: AllocRange, + error_offset: Size, + stack: &Stack, + ) -> InterpError<'static> { + let action = format!( + "attempting a {} using {:?} at {}[{:#x}]", + access, + tag, + alloc_id, + error_offset.bytes(), + ); + err_sb_ub( + format!("{}{}", action, error_cause(stack, tag)), + Some(operation_summary("an access", alloc_id, alloc_range)), + self.get_logs_relevant_to(tag, alloc_range, error_offset, None), + ) + } +} + +fn operation_summary( + operation: &'static str, + alloc_id: AllocId, + alloc_range: AllocRange, +) -> String { + format!("this error occurs as part of {} at {:?}{}", operation, alloc_id, HexRange(alloc_range)) +} + +fn error_cause(stack: &Stack, tag: SbTag) -> &'static str { + if stack.borrows.iter().any(|item| item.tag == tag && item.perm != Permission::Disabled) { + ", but that tag only grants SharedReadOnly permission for this location" + } else { + ", but that tag does not exist in the borrow stack for this location" + } +} diff --git a/src/thread.rs b/src/thread.rs index 8edd6672a7..5673af048f 100644 --- a/src/thread.rs +++ b/src/thread.rs @@ -263,7 +263,7 @@ impl<'mir, 'tcx: 'mir> ThreadManager<'mir, 'tcx> { } /// Borrow the stack of the active thread. - fn active_thread_stack(&self) -> &[Frame<'mir, 'tcx, Tag, FrameData<'tcx>>] { + pub fn active_thread_stack(&self) -> &[Frame<'mir, 'tcx, Tag, FrameData<'tcx>>] { &self.threads[self.active_thread].stack }