From 66b4bb7cf2e7136ca0a31d04a2abe1754e89a5b6 Mon Sep 17 00:00:00 2001 From: Ralf Jung Date: Tue, 16 Oct 2018 18:01:50 +0200 Subject: [PATCH] stacked borrows: track refs and derefs --- src/fn_call.rs | 2 +- src/intrinsic.rs | 5 +- src/lib.rs | 49 ++++++++- src/operator.rs | 2 +- src/range_map.rs | 150 +++++++++++++++++--------- src/stacked_borrows.rs | 238 ++++++++++++++++++++++++++++++++++++++++- src/tls.rs | 8 +- 7 files changed, 388 insertions(+), 66 deletions(-) diff --git a/src/fn_call.rs b/src/fn_call.rs index 280cc2bdea5..1613da4a487 100644 --- a/src/fn_call.rs +++ b/src/fn_call.rs @@ -44,7 +44,7 @@ fn find_fn( fn write_null(&mut self, dest: PlaceTy<'tcx, Borrow>) -> EvalResult<'tcx>; } -impl<'a, 'mir, 'tcx: 'mir + 'a> EvalContextExt<'tcx, 'mir> for EvalContext<'a, 'mir, 'tcx, super::Evaluator<'tcx>> { +impl<'a, 'mir, 'tcx: 'mir + 'a> EvalContextExt<'tcx, 'mir> for super::MiriEvalContext<'a, 'mir, 'tcx> { fn find_fn( &mut self, instance: ty::Instance<'tcx>, diff --git a/src/intrinsic.rs b/src/intrinsic.rs index 11c0bd7907c..719bc939395 100644 --- a/src/intrinsic.rs +++ b/src/intrinsic.rs @@ -3,10 +3,9 @@ use rustc::ty; use rustc::mir::interpret::{EvalResult, PointerArithmetic}; -use rustc_mir::interpret::{EvalContext, PlaceTy, OpTy}; use super::{ - Value, Scalar, ScalarMaybeUndef, Borrow, + PlaceTy, OpTy, Value, Scalar, ScalarMaybeUndef, Borrow, FalibleScalarExt, OperatorEvalContextExt }; @@ -19,7 +18,7 @@ fn call_intrinsic( ) -> EvalResult<'tcx>; } -impl<'a, 'mir, 'tcx> EvalContextExt<'tcx> for EvalContext<'a, 'mir, 'tcx, super::Evaluator<'tcx>> { +impl<'a, 'mir, 'tcx> EvalContextExt<'tcx> for super::MiriEvalContext<'a, 'mir, 'tcx> { fn call_intrinsic( &mut self, instance: ty::Instance<'tcx>, diff --git a/src/lib.rs b/src/lib.rs index 47eaee6dfac..97b73e60692 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -16,7 +16,7 @@ use std::collections::HashMap; use std::borrow::Cow; -use rustc::ty::{self, TyCtxt, query::TyCtxtAt}; +use rustc::ty::{self, Ty, TyCtxt, query::TyCtxtAt}; use rustc::ty::layout::{TyLayout, LayoutOf, Size}; use rustc::hir::def_id::DefId; use rustc::mir; @@ -43,7 +43,7 @@ use range_map::RangeMap; use helpers::FalibleScalarExt; use mono_hash_map::MonoHashMap; -use stacked_borrows::Borrow; +use stacked_borrows::{EvalContextExt as StackedBorEvalContextExt, Borrow}; pub fn create_ecx<'a, 'mir: 'a, 'tcx: 'mir>( tcx: TyCtxt<'a, 'tcx, 'tcx>, @@ -232,7 +232,6 @@ fn may_leak(self) -> bool { } } -#[derive(Clone, PartialEq, Eq)] pub struct Evaluator<'tcx> { /// Environment variables set by `setenv` /// Miri does not expose env vars from the host to the emulated program @@ -243,6 +242,9 @@ pub struct Evaluator<'tcx> { /// Whether to enforce the validity invariant pub(crate) validate: bool, + + /// Stacked Borrows state + pub(crate) stacked_borrows: stacked_borrows::State, } impl<'tcx> Evaluator<'tcx> { @@ -251,13 +253,18 @@ fn new(validate: bool) -> Self { env_vars: HashMap::default(), tls: TlsData::default(), validate, + stacked_borrows: stacked_borrows::State::new(), } } } +#[allow(dead_code)] // FIXME https://github.com/rust-lang/rust/issues/47131 +type MiriEvalContext<'a, 'mir, 'tcx> = EvalContext<'a, 'mir, 'tcx, Evaluator<'tcx>>; + + impl<'a, 'mir, 'tcx> Machine<'a, 'mir, 'tcx> for Evaluator<'tcx> { type MemoryKinds = MiriMemoryKind; - type AllocExtra = (); + type AllocExtra = stacked_borrows::Stacks; type PointerTag = Borrow; type MemoryMap = MonoHashMap, Allocation)>; @@ -288,6 +295,7 @@ fn enforce_validity(ecx: &EvalContext<'a, 'mir, 'tcx, Self>) -> bool { } /// Returns Ok() when the function was handled, fail otherwise + #[inline(always)] fn find_fn( ecx: &mut EvalContext<'a, 'mir, 'tcx, Self>, instance: ty::Instance<'tcx>, @@ -298,6 +306,7 @@ fn find_fn( ecx.find_fn(instance, args, dest, ret) } + #[inline(always)] fn call_intrinsic( ecx: &mut rustc_mir::interpret::EvalContext<'a, 'mir, 'tcx, Self>, instance: ty::Instance<'tcx>, @@ -307,6 +316,7 @@ fn call_intrinsic( ecx.call_intrinsic(instance, args, dest) } + #[inline(always)] fn ptr_op( ecx: &rustc_mir::interpret::EvalContext<'a, 'mir, 'tcx, Self>, bin_op: mir::BinOp, @@ -380,6 +390,7 @@ fn find_foreign_static( Ok(Cow::Owned(alloc)) } + #[inline(always)] fn before_terminator(_ecx: &mut EvalContext<'a, 'mir, 'tcx, Self>) -> EvalResult<'tcx> { // We are not interested in detecting loops @@ -403,4 +414,34 @@ fn static_with_default_tag( }; Cow::Owned(alloc) } + + #[inline(always)] + fn tag_reference( + ecx: &mut EvalContext<'a, 'mir, 'tcx, Self>, + ptr: Pointer, + pointee_ty: Ty<'tcx>, + pointee_size: Size, + borrow_kind: mir::BorrowKind, + ) -> EvalResult<'tcx, Self::PointerTag> { + if !ecx.machine.validate { + // No tracking + Ok(Borrow::default()) + } else { + ecx.tag_reference(ptr, pointee_ty, pointee_size, borrow_kind) + } + } + + #[inline(always)] + fn tag_dereference( + ecx: &EvalContext<'a, 'mir, 'tcx, Self>, + ptr: Pointer, + ptr_ty: Ty<'tcx>, + ) -> EvalResult<'tcx, Self::PointerTag> { + if !ecx.machine.validate { + // No tracking + Ok(Borrow::default()) + } else { + ecx.tag_dereference(ptr, ptr_ty) + } + } } diff --git a/src/operator.rs b/src/operator.rs index dd79f293134..a11f8f34e7e 100644 --- a/src/operator.rs +++ b/src/operator.rs @@ -36,7 +36,7 @@ fn pointer_offset_inbounds( ) -> EvalResult<'tcx, Scalar>; } -impl<'a, 'mir, 'tcx> EvalContextExt<'tcx> for EvalContext<'a, 'mir, 'tcx, super::Evaluator<'tcx>> { +impl<'a, 'mir, 'tcx> EvalContextExt<'tcx> for super::MiriEvalContext<'a, 'mir, 'tcx> { fn ptr_op( &self, bin_op: mir::BinOp, diff --git a/src/range_map.rs b/src/range_map.rs index e55534e36fd..1d9a38cb5ef 100644 --- a/src/range_map.rs +++ b/src/range_map.rs @@ -9,11 +9,20 @@ use std::collections::BTreeMap; use std::ops; +use rustc::ty::layout::Size; + #[derive(Clone, Debug, PartialEq, Eq)] pub struct RangeMap { map: BTreeMap, } +impl Default for RangeMap { + #[inline(always)] + fn default() -> Self { + RangeMap::new() + } +} + // The derived `Ord` impl sorts first by the first field, then, if the fields are the same, // by the second field. // This is exactly what we need for our purposes, since a range query on a BTReeSet/BTreeMap will give us all @@ -21,14 +30,19 @@ pub struct RangeMap { // At the same time the `end` is irrelevant for the sorting and range searching, but used for the check. // This kind of search breaks, if `end < start`, so don't do that! #[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Debug)] -pub struct Range { +struct Range { start: u64, end: u64, // Invariant: end > start } impl Range { + /// Compute a range of ranges that contains all ranges overlaping with [offset, offset+len) fn range(offset: u64, len: u64) -> ops::Range { - assert!(len > 0); + if len == 0 { + // We can produce an empty range, nothing overlaps with this. + let r = Range { start: 0, end: 1 }; + return r..r; + } // We select all elements that are within // the range given by the offset into the allocation and the length. // This is sound if all ranges that intersect with the argument range, are in the @@ -46,14 +60,20 @@ fn range(offset: u64, len: u64) -> ops::Range { left..right } - /// Tests if all of [offset, offset+len) are contained in this range. + /// Tests if any element of [offset, offset+len) is contained in this range. + #[inline(always)] fn overlaps(&self, offset: u64, len: u64) -> bool { - assert!(len > 0); - offset < self.end && offset + len >= self.start + if len == 0 { + // `offset` totally does not matter, we cannot overlap with an empty interval + false + } else { + offset < self.end && offset.checked_add(len).unwrap() >= self.start + } } } impl RangeMap { + #[inline(always)] pub fn new() -> RangeMap { RangeMap { map: BTreeMap::new() } } @@ -63,10 +83,9 @@ fn iter_with_range<'a>( offset: u64, len: u64, ) -> impl Iterator + 'a { - assert!(len > 0); self.map.range(Range::range(offset, len)).filter_map( - move |(range, - data)| { + move |(range, data)| { + debug_assert!(len > 0); if range.overlaps(offset, len) { Some((range, data)) } else { @@ -76,8 +95,8 @@ fn iter_with_range<'a>( ) } - pub fn iter<'a>(&'a self, offset: u64, len: u64) -> impl Iterator + 'a { - self.iter_with_range(offset, len).map(|(_, data)| data) + pub fn iter<'a>(&'a self, offset: Size, len: Size) -> impl Iterator + 'a { + self.iter_with_range(offset.bytes(), len.bytes()).map(|(_, data)| data) } fn split_entry_at(&mut self, offset: u64) @@ -114,28 +133,30 @@ fn split_entry_at(&mut self, offset: u64) } } - pub fn iter_mut_all<'a>(&'a mut self) -> impl Iterator + 'a { - self.map.values_mut() - } - /// Provide mutable iteration over everything in the given range. As a side-effect, /// this will split entries in the map that are only partially hit by the given range, /// to make sure that when they are mutated, the effect is constrained to the given range. + /// If there are gaps, leave them be. pub fn iter_mut_with_gaps<'a>( &'a mut self, - offset: u64, - len: u64, + offset: Size, + len: Size, ) -> impl Iterator + 'a where T: Clone, { - assert!(len > 0); - // Preparation: Split first and last entry as needed. - self.split_entry_at(offset); - self.split_entry_at(offset + len); + let offset = offset.bytes(); + let len = len.bytes(); + + if len > 0 { + // Preparation: Split first and last entry as needed. + self.split_entry_at(offset); + self.split_entry_at(offset + len); + } // Now we can provide a mutable iterator self.map.range_mut(Range::range(offset, len)).filter_map( move |(&range, data)| { + debug_assert!(len > 0); if range.overlaps(offset, len) { assert!( offset <= range.start && offset + len >= range.end, @@ -151,35 +172,41 @@ pub fn iter_mut_with_gaps<'a>( } /// Provide a mutable iterator over everything in the given range, with the same side-effects as - /// iter_mut_with_gaps. Furthermore, if there are gaps between ranges, fill them with the given default. + /// iter_mut_with_gaps. Furthermore, if there are gaps between ranges, fill them with the given default + /// before yielding them in the iterator. /// This is also how you insert. - pub fn iter_mut<'a>(&'a mut self, offset: u64, len: u64) -> impl Iterator + 'a + pub fn iter_mut<'a>(&'a mut self, offset: Size, len: Size) -> impl Iterator + 'a where T: Clone + Default, { - // Do a first iteration to collect the gaps - let mut gaps = Vec::new(); - let mut last_end = offset; - for (range, _) in self.iter_with_range(offset, len) { - if last_end < range.start { + if len.bytes() > 0 { + let offset = offset.bytes(); + let len = len.bytes(); + + // Do a first iteration to collect the gaps + let mut gaps = Vec::new(); + let mut last_end = offset; + for (range, _) in self.iter_with_range(offset, len) { + if last_end < range.start { + gaps.push(Range { + start: last_end, + end: range.start, + }); + } + last_end = range.end; + } + if last_end < offset + len { gaps.push(Range { start: last_end, - end: range.start, + end: offset + len, }); } - last_end = range.end; - } - if last_end < offset + len { - gaps.push(Range { - start: last_end, - end: offset + len, - }); - } - // Add default for all gaps - for gap in gaps { - let old = self.map.insert(gap, Default::default()); - assert!(old.is_none()); + // Add default for all gaps + for gap in gaps { + let old = self.map.insert(gap, Default::default()); + assert!(old.is_none()); + } } // Now provide mutable iteration @@ -208,10 +235,16 @@ mod tests { use super::*; /// Query the map at every offset in the range and collect the results. - fn to_vec(map: &RangeMap, offset: u64, len: u64) -> Vec { + fn to_vec(map: &RangeMap, offset: u64, len: u64, default: Option) -> Vec { (offset..offset + len) .into_iter() - .map(|i| *map.iter(i, 1).next().unwrap()) + .map(|i| map + .iter(Size::from_bytes(i), Size::from_bytes(1)) + .next() + .map(|&t| t) + .or(default) + .unwrap() + ) .collect() } @@ -219,34 +252,47 @@ fn to_vec(map: &RangeMap, offset: u64, len: u64) -> Vec { fn basic_insert() { let mut map = RangeMap::::new(); // Insert - for x in map.iter_mut(10, 1) { + for x in map.iter_mut(Size::from_bytes(10), Size::from_bytes(1)) { *x = 42; } // Check - assert_eq!(to_vec(&map, 10, 1), vec![42]); + assert_eq!(to_vec(&map, 10, 1, None), vec![42]); + + // Insert with size 0 + for x in map.iter_mut(Size::from_bytes(10), Size::from_bytes(0)) { + *x = 19; + } + for x in map.iter_mut(Size::from_bytes(11), Size::from_bytes(0)) { + *x = 19; + } + assert_eq!(to_vec(&map, 10, 2, Some(-1)), vec![42, -1]); } #[test] fn gaps() { let mut map = RangeMap::::new(); - for x in map.iter_mut(11, 1) { + for x in map.iter_mut(Size::from_bytes(11), Size::from_bytes(1)) { *x = 42; } - for x in map.iter_mut(15, 1) { - *x = 42; + for x in map.iter_mut(Size::from_bytes(15), Size::from_bytes(1)) { + *x = 43; } + assert_eq!( + to_vec(&map, 10, 10, Some(-1)), + vec![-1, 42, -1, -1, -1, 43, -1, -1, -1, -1] + ); // Now request a range that needs three gaps filled - for x in map.iter_mut(10, 10) { - if *x != 42 { + for x in map.iter_mut(Size::from_bytes(10), Size::from_bytes(10)) { + if *x < 42 { *x = 23; } } assert_eq!( - to_vec(&map, 10, 10), - vec![23, 42, 23, 23, 23, 42, 23, 23, 23, 23] + to_vec(&map, 10, 10, None), + vec![23, 42, 23, 23, 23, 43, 23, 23, 23, 23] ); - assert_eq!(to_vec(&map, 13, 5), vec![23, 23, 42, 23, 23]); + assert_eq!(to_vec(&map, 13, 5, None), vec![23, 23, 43, 23, 23]); } } diff --git a/src/stacked_borrows.rs b/src/stacked_borrows.rs index dd9f1b37089..c3514efdbe7 100644 --- a/src/stacked_borrows.rs +++ b/src/stacked_borrows.rs @@ -1,4 +1,12 @@ -use super::RangeMap; +use std::cell::RefCell; + +use rustc::ty::{Ty, layout::Size}; +use rustc::mir; + +use super::{ + RangeMap, EvalResult, + Pointer, +}; pub type Timestamp = u64; @@ -11,6 +19,24 @@ pub enum Mut { Raw, } +impl Mut { + #[inline(always)] + fn is_raw(self) -> bool { + match self { + Mut::Raw => true, + _ => false, + } + } + + #[inline(always)] + fn is_uniq(self) -> bool { + match self { + Mut::Uniq(_) => true, + _ => false, + } + } +} + /// Information about any kind of borrow #[derive(Copy, Clone, Debug, Hash, PartialEq, Eq)] pub enum Borrow { @@ -20,6 +46,24 @@ pub enum Borrow { Frz(Timestamp) } +impl Borrow { + #[inline(always)] + fn is_mut(self) -> bool { + match self { + Borrow::Mut(_) => true, + _ => false, + } + } + + #[inline(always)] + fn is_uniq(self) -> bool { + match self { + Borrow::Mut(Mut::Uniq(_)) => true, + _ => false, + } + } +} + /// An item in the borrow stack #[derive(Copy, Clone, Debug, Hash, PartialEq, Eq)] pub enum BorStackItem { @@ -34,3 +78,195 @@ fn default() -> Self { Borrow::Mut(Mut::Raw) } } + +/// Extra global machine state +#[derive(Clone, Debug)] +pub struct State { + clock: Timestamp +} + +impl State { + pub fn new() -> State { + State { clock: 0 } + } +} + +/// Extra per-location state +#[derive(Clone, Debug)] +struct Stack { + borrows: Vec, // used as a stack + frozen_since: Option, +} + +impl Default for Stack { + fn default() -> Self { + Stack { + borrows: Vec::new(), + frozen_since: None, + } + } +} + +/// Extra per-allocation state +#[derive(Clone, Debug, Default)] +pub struct Stacks { + stacks: RefCell>, +} + +/// Core operations +impl<'tcx> Stack { + fn check(&self, bor: Borrow) -> bool { + match bor { + Borrow::Frz(acc_t) => + // Must be frozen at least as long as the `acc_t` says. + self.frozen_since.map_or(false, |loc_t| loc_t <= acc_t), + Borrow::Mut(acc_m) => + // Raw pointers are fine with frozen locations. This is important because &Cell is raw! + if self.frozen_since.is_some() { + acc_m.is_raw() + } else { + self.borrows.last().map_or(false, |&loc_itm| loc_itm == BorStackItem::Mut(acc_m)) + } + } + } + + /// Reactive `bor` for this stack. If `force_mut` is set, we want to aggressively + /// unfreeze this location (because we are about to push a `Uniq`). + fn reactivate(&mut self, bor: Borrow, force_mut: bool) -> EvalResult<'tcx> { + assert!(!force_mut || bor.is_mut()); // if `force_mut` is set, this must be a mutable borrow + // Do NOT change anything if `bor` is already active -- in particular, if + // it is a `Mut(Raw)` and we are frozen. + if !force_mut && self.check(bor) { + return Ok(()); + } + + let acc_m = match bor { + Borrow::Frz(_) => return err!(MachineError(format!("Location should be frozen but it is not"))), + Borrow::Mut(acc_m) => acc_m, + }; + // We definitely have to unfreeze this, even if we use the topmost item. + self.frozen_since = None; + // Pop until we see the one we are looking for. + while let Some(&itm) = self.borrows.last() { + match itm { + BorStackItem::FnBarrier(_) => { + return err!(MachineError(format!("Trying to reactivate a borrow that lives behind a barrier"))); + } + BorStackItem::Mut(loc_m) => { + if loc_m == acc_m { return Ok(()); } + self.borrows.pop(); + } + } + } + // Nothing to be found. Simulate a "virtual raw" element at the bottom of the stack. + if acc_m.is_raw() { + Ok(()) + } else { + err!(MachineError(format!("Borrow-to-reactivate does not exist on the stack"))) + } + } + + fn initiate(&mut self, bor: Borrow) -> EvalResult<'tcx> { + match bor { + Borrow::Frz(t) => { + match self.frozen_since { + None => self.frozen_since = Some(t), + Some(since) => assert!(since <= t), + } + } + Borrow::Mut(m) => { + match self.frozen_since { + None => self.borrows.push(BorStackItem::Mut(m)), + Some(_) => + // FIXME: Do we want an exception for raw borrows? + return err!(MachineError(format!("Trying to mutate frozen location"))) + } + } + } + Ok(()) + } +} + +impl State { + fn increment_clock(&mut self) -> Timestamp { + self.clock += 1; + self.clock + } +} + +/// Machine hooks +pub trait EvalContextExt<'tcx> { + fn tag_reference( + &mut self, + ptr: Pointer, + pointee_ty: Ty<'tcx>, + size: Size, + borrow_kind: mir::BorrowKind, + ) -> EvalResult<'tcx, Borrow>; + + fn tag_dereference( + &self, + ptr: Pointer, + ptr_ty: Ty<'tcx>, + ) -> EvalResult<'tcx, Borrow>; +} + +impl<'a, 'mir, 'tcx> EvalContextExt<'tcx> for super::MiriEvalContext<'a, 'mir, 'tcx> { + fn tag_reference( + &mut self, + ptr: Pointer, + pointee_ty: Ty<'tcx>, + size: Size, + borrow_kind: mir::BorrowKind, + ) -> EvalResult<'tcx, Borrow> { + let old_bor = ptr.tag; + let time = self.machine.stacked_borrows.increment_clock(); + // FIXME This does not do enough checking when only part of the data lacks + // interior mutability. + let new_bor = match borrow_kind { + mir::BorrowKind::Mut { .. } => Borrow::Mut(Mut::Uniq(time)), + _ => + if self.type_is_freeze(pointee_ty) { + Borrow::Frz(time) + } else { + Borrow::Mut(Mut::Raw) + } + }; + trace!("tag_reference: Creating new tag for {:?} (pointee {}, size {}): {:?}", ptr, pointee_ty, size.bytes(), new_bor); + + // Make sure this reference is not dangling or so + self.memory.check_bounds(ptr, size, false)?; + + // Update the stacks. We cannot use `get_mut` becuse this might be immutable + // memory. + let alloc = self.memory.get(ptr.alloc_id).expect("We checked that the ptr is fine!"); + let mut stacks = alloc.extra.stacks.borrow_mut(); + for stack in stacks.iter_mut(ptr.offset, size) { + if stack.check(new_bor) { + // The new borrow is already active! This can happen when creating multiple + // shared references from the same mutable reference. Do nothing. + } else { + // FIXME: The blog post says we should `reset` if this is a local. + stack.reactivate(old_bor, /*force_mut*/new_bor.is_uniq())?; + stack.initiate(new_bor)?; + } + } + + Ok(new_bor) + } + + fn tag_dereference( + &self, + ptr: Pointer, + ptr_ty: Ty<'tcx>, + ) -> EvalResult<'tcx, Borrow> { + // If this is a raw ptr, forget about the tag. + Ok(if ptr_ty.is_unsafe_ptr() { + trace!("tag_dereference: Erasing tag for {:?} ({})", ptr, ptr_ty); + Borrow::Mut(Mut::Raw) + } else { + // FIXME: Do we want to adjust the tag if it does not match the type? + ptr.tag + }) + } +} diff --git a/src/tls.rs b/src/tls.rs index c5d119ec7f5..2bddc43df8c 100644 --- a/src/tls.rs +++ b/src/tls.rs @@ -4,19 +4,19 @@ use rustc::{ty, ty::layout::HasDataLayout, mir}; use super::{ - EvalResult, EvalErrorKind, StackPopCleanup, EvalContext, Evaluator, + EvalResult, EvalErrorKind, StackPopCleanup, MPlaceTy, Scalar, Borrow, }; pub type TlsKey = u128; -#[derive(Copy, Clone, Debug, PartialEq, Eq)] +#[derive(Copy, Clone, Debug)] pub struct TlsEntry<'tcx> { pub(crate) data: Scalar, // Will eventually become a map from thread IDs to `Scalar`s, if we ever support more than one thread. pub(crate) dtor: Option>, } -#[derive(Debug, Clone, PartialEq, Eq)] +#[derive(Debug)] pub struct TlsData<'tcx> { /// The Key to use for the next thread-local allocation. pub(crate) next_key: TlsKey, @@ -133,7 +133,7 @@ fn fetch_tls_dtor( } } -impl<'a, 'mir, 'tcx: 'mir + 'a> EvalContextExt<'tcx> for EvalContext<'a, 'mir, 'tcx, Evaluator<'tcx>> { +impl<'a, 'mir, 'tcx: 'mir + 'a> EvalContextExt<'tcx> for super::MiriEvalContext<'a, 'mir, 'tcx> { fn run_tls_dtors(&mut self) -> EvalResult<'tcx> { let mut dtor = self.machine.tls.fetch_tls_dtor(None, *self.tcx); // FIXME: replace loop by some structure that works with stepping -- 2.44.0