use std::cell::RefCell; use std::collections::HashSet; use std::rc::Rc; use std::fmt; use std::num::NonZeroU64; use rustc::ty::{self, layout::Size}; use rustc::hir::{MutMutable, MutImmutable}; use rustc::mir::RetagKind; use crate::{ EvalResult, InterpError, MiriEvalContext, HelpersEvalContextExt, Evaluator, MutValueVisitor, MemoryKind, MiriMemoryKind, RangeMap, Allocation, AllocationExtra, Pointer, Immediate, ImmTy, PlaceTy, MPlaceTy, }; pub type PtrId = NonZeroU64; pub type CallId = u64; /// Tracking pointer provenance #[derive(Copy, Clone, Debug, Hash, PartialEq, Eq)] pub enum Tag { Tagged(PtrId), Untagged, } impl fmt::Display for Tag { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match self { Tag::Tagged(id) => write!(f, "{}", id), Tag::Untagged => write!(f, ""), } } } /// Indicates which permission is granted (by this item to some pointers) #[derive(Copy, Clone, Debug, Hash, PartialEq, Eq)] pub enum Permission { /// Grants unique mutable access. Unique, /// Grants shared mutable access. SharedReadWrite, /// Greants shared read-only access. SharedReadOnly, } /// An item in the per-location borrow stack. #[derive(Copy, Clone, Debug, Hash, PartialEq, Eq)] pub enum Item { /// Grants the given permission for pointers with this tag. Permission(Permission, Tag), /// A barrier, tracking the function it belongs to by its index on the call stack. FnBarrier(CallId), } impl fmt::Display for Item { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match self { Item::Permission(perm, tag) => write!(f, "[{:?} for {}]", perm, tag), Item::FnBarrier(call) => write!(f, "[barrier {}]", call), } } } /// Extra per-location state. #[derive(Clone, Debug, PartialEq, Eq)] pub struct Stack { /// Used *mostly* as a stack; never empty. /// We sometimes push into the middle but never remove from the middle. /// The same tag may occur multiple times, e.g. from a two-phase borrow. /// Invariants: /// * Above a `SharedReadOnly` there can only be barriers and more `SharedReadOnly`. borrows: Vec, } /// Extra per-allocation state. #[derive(Clone, Debug)] pub struct Stacks { // Even reading memory can have effects on the stack, so we need a `RefCell` here. stacks: RefCell>, // Pointer to global state global: MemoryState, } /// Extra global state, available to the memory access hooks. #[derive(Debug)] pub struct GlobalState { next_ptr_id: PtrId, next_call_id: CallId, active_calls: HashSet, } pub type MemoryState = Rc>; /// Indicates which kind of access is being performed. #[derive(Copy, Clone, Debug, Hash, PartialEq, Eq)] pub enum AccessKind { Read, Write, } impl fmt::Display for AccessKind { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match self { AccessKind::Read => write!(f, "read"), AccessKind::Write => write!(f, "write"), } } } /// Indicates which kind of reference is being created. /// Used by high-level `reborrow` to compute which permissions to grant to the /// new pointer. #[derive(Copy, Clone, Debug, Hash, PartialEq, Eq)] pub enum RefKind { /// `&mut` and `Box`. Unique, /// `&` with or without interior mutability. Shared, /// `*mut`/`*const` (raw pointers). Raw { mutable: bool }, } impl fmt::Display for RefKind { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match self { RefKind::Unique => write!(f, "unique"), RefKind::Shared => write!(f, "shared"), RefKind::Raw { mutable: true } => write!(f, "raw (mutable)"), RefKind::Raw { mutable: false } => write!(f, "raw (constant)"), } } } /// Utilities for initialization and ID generation impl Default for GlobalState { fn default() -> Self { GlobalState { next_ptr_id: NonZeroU64::new(1).unwrap(), next_call_id: 0, active_calls: HashSet::default(), } } } impl GlobalState { pub fn new_ptr(&mut self) -> PtrId { let id = self.next_ptr_id; self.next_ptr_id = NonZeroU64::new(id.get() + 1).unwrap(); id } pub fn new_call(&mut self) -> CallId { let id = self.next_call_id; trace!("new_call: Assigning ID {}", id); self.active_calls.insert(id); self.next_call_id = id+1; id } pub fn end_call(&mut self, id: CallId) { assert!(self.active_calls.remove(&id)); } fn is_active(&self, id: CallId) -> bool { self.active_calls.contains(&id) } } // # Stacked Borrows Core Begin /// We need to make at least the following things true: /// /// U1: After creating a `Uniq`, it is at the top. /// U2: If the top is `Uniq`, accesses must be through that `Uniq` or remove it it. /// U3: If an access happens with a `Uniq`, it requires the `Uniq` to be in the stack. /// /// F1: After creating a `&`, the parts outside `UnsafeCell` have our `SharedReadOnly` on top. /// F2: If a write access happens, it pops the `SharedReadOnly`. This has three pieces: /// F2a: If a write happens granted by an item below our `SharedReadOnly`, the `SharedReadOnly` /// gets popped. /// F2b: No `SharedReadWrite` or `Unique` will ever be added on top of our `SharedReadOnly`. /// F3: If an access happens with an `&` outside `UnsafeCell`, /// it requires the `SharedReadOnly` to still be in the stack. impl Default for Tag { #[inline(always)] fn default() -> Tag { Tag::Untagged } } /// Core relations on `Permission` define which accesses are allowed: /// On every access, we try to find a *granting* item, and then we remove all /// *incompatible* items above it. impl Permission { /// This defines for a given permission, whether it permits the given kind of access. fn grants(self, access: AccessKind) -> bool { match (self, access) { // Unique and SharedReadWrite allow any kind of access. (Permission::Unique, _) | (Permission::SharedReadWrite, _) => true, // SharedReadOnly only permits read access. (Permission::SharedReadOnly, AccessKind::Read) => true, (Permission::SharedReadOnly, AccessKind::Write) => false, } } /// This defines for a given permission, which other permissions it can tolerate "above" itself /// for which kinds of accesses. /// If true, then `other` is allowed to remain on top of `self` when `access` happens. fn compatible_with(self, access: AccessKind, other: Permission) -> bool { use self::Permission::*; match (self, access, other) { // Some cases are impossible. (SharedReadOnly, _, SharedReadWrite) | (SharedReadOnly, _, Unique) => bug!("There can never be a SharedReadWrite or a Unique on top of a SharedReadOnly"), // When `other` is `SharedReadOnly`, that is NEVER compatible with // write accesses. // This makes sure read-only pointers become invalid on write accesses (ensures F2a). (_, AccessKind::Write, SharedReadOnly) => false, // When `other` is `Unique`, that is compatible with nothing. // This makes sure unique pointers become invalid on incompatible accesses (ensures U2). (_, _, Unique) => false, // When we are unique and this is a write/dealloc, we tolerate nothing. // This makes sure we re-assert uniqueness ("being on top") on write accesses. // (This is particularily important such that when a new mutable ref gets created, it gets // pushed into the right item -- this behaves like a write and we assert uniqueness of the // pointer from which this comes, *if* it was a unique pointer.) (Unique, AccessKind::Write, _) => false, // `SharedReadWrite` items can tolerate any other akin items for any kind of access. (SharedReadWrite, _, SharedReadWrite) => true, // Any item can tolerate read accesses for shared items. // This includes unique items! Reads from unique pointers do not invalidate // other pointers. (_, AccessKind::Read, SharedReadWrite) | (_, AccessKind::Read, SharedReadOnly) => true, // That's it. } } } /// Core per-location operations: access, dealloc, reborrow. impl<'tcx> Stack { /// Find the item granting the given kind of access to the given tag, and where that item is in the stack. fn find_granting(&self, access: AccessKind, tag: Tag) -> Option<(usize, Permission)> { self.borrows.iter() .enumerate() // we also need to know *where* in the stack .rev() // search top-to-bottom // Return permission of first item that grants access. // We require a permission with the right tag, ensuring U3 and F3. .filter_map(|(idx, item)| match item { &Item::Permission(perm, item_tag) if perm.grants(access) && tag == item_tag => Some((idx, perm)), _ => None, }) .next() } /// Test if a memory `access` using pointer tagged `tag` is granted. /// If yes, return the index of the item that granted it. fn access( &mut self, access: AccessKind, tag: Tag, global: &GlobalState, ) -> EvalResult<'tcx, usize> { // Two main steps: Find granting item, remove all incompatible items above. // The second step is where barriers get implemented: they "protect" the items // below them, meaning that if we remove an item and then further up encounter a barrier, // we raise an error. // Step 1: Find granting item. let (granting_idx, granting_perm) = self.find_granting(access, tag) .ok_or_else(|| InterpError::MachineError(format!( "no item granting {} access to tag {} found in borrow stack", access, tag, )))?; // Step 2: Remove everything incompatible above them. // Items below an active barrier however may not be removed, so we check that as well. // We do *not* maintain a stack discipline here. We could, in principle, decide to only // keep the items immediately above `granting_idx` that are compatible, and then pop the rest. // However, that kills off entire "branches" of pointer derivation too easily: // in `let raw = &mut *x as *mut _; let _val = *x;`, the second statement would pop the `Unique` // from the reborrow of the first statement, and subequently also pop the `SharedReadWrite` for `raw`. { // Implemented with indices because there does not seem to be a nice iterator and range-based // API for this. let mut cur = granting_idx + 1; let mut removed_item = None; while let Some(item) = self.borrows.get(cur) { match *item { Item::Permission(perm, _) => { if granting_perm.compatible_with(access, perm) { // Keep this, check next. cur += 1; } else { // Aha! This is a bad one, remove it. let item = self.borrows.remove(cur); trace!("access: removing item {}", item); removed_item = Some(item); } } Item::FnBarrier(call) if !global.is_active(call) => { // An inactive barrier, just get rid of it. (Housekeeping.) self.borrows.remove(cur); } Item::FnBarrier(call) => { // We hit an active barrier! If we have already removed an item, // we got a problem! The barrier was supposed to protect this item. if let Some(removed_item) = removed_item { return err!(MachineError(format!( "not granting {} access to tag {} because barrier ({}) protects incompatible item {}", access, tag, call, removed_item ))); } // Keep this, check next. cur += 1; } } } } // Done. return Ok(granting_idx); } /// Deallocate a location: Like a write access, but also there must be no /// barriers at all. fn dealloc( &mut self, tag: Tag, global: &GlobalState, ) -> EvalResult<'tcx> { // Step 1: Find granting item. self.find_granting(AccessKind::Write, tag) .ok_or_else(|| InterpError::MachineError(format!( "no item granting write access for deallocation to tag {} found in borrow stack", tag, )))?; // We must make sure there are no active barriers remaining on the stack. // Also clear the stack, no more accesses are possible. while let Some(itm) = self.borrows.pop() { match itm { Item::FnBarrier(call) if global.is_active(call) => { return err!(MachineError(format!( "deallocating with active barrier ({})", call ))) } _ => {}, } } Ok(()) } /// `reborrow` helper function. /// Grant `permisson` to new pointer tagged `tag`, added at `position` in the stack. fn grant(&mut self, perm: Permission, tag: Tag, position: usize) { // Simply add it to the "stack" -- this might add in the middle. // As an optimization, do nothing if the new item is identical to one of its neighbors. let item = Item::Permission(perm, tag); if self.borrows[position-1] == item || self.borrows.get(position) == Some(&item) { // Optimization applies, done. trace!("reborrow: avoiding redundant item {}", item); return; } trace!("reborrow: adding item {}", item); self.borrows.insert(position, item); } /// `reborrow` helper function. /// Adds a barrier. fn barrier(&mut self, call: CallId) { let itm = Item::FnBarrier(call); if *self.borrows.last().unwrap() == itm { // This is just an optimization, no functional change: Avoid stacking // multiple identical barriers on top of each other. // This can happen when a function receives several shared references // that overlap. trace!("reborrow: avoiding redundant extra barrier"); } else { trace!("reborrow: adding barrier for call {}", call); self.borrows.push(itm); } } /// `reborrow` helper function: test that the stack invariants are still maintained. fn test_invariants(&self) { let mut saw_shared_read_only = false; for item in self.borrows.iter() { match item { Item::Permission(Permission::SharedReadOnly, _) => { saw_shared_read_only = true; } Item::Permission(perm, _) if saw_shared_read_only => { panic!("Found {:?} on top of a SharedReadOnly!", perm); } _ => {} } } } /// Derived a new pointer from one with the given tag. fn reborrow( &mut self, derived_from: Tag, barrier: Option, new_perm: Permission, new_tag: Tag, global: &GlobalState, ) -> EvalResult<'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 (derived_from_idx, _) = self.find_granting(access, derived_from) .ok_or_else(|| InterpError::MachineError(format!( "no item to reborrow for {:?} from tag {} found in borrow stack", new_perm, derived_from, )))?; // We behave very differently for the "unsafe" case of a shared-read-write pointer // ("unsafe" because this also applies to shared references with interior mutability). // This is because such pointers may be reborrowed to unique pointers that actually // remain valid when their "parents" get further reborrows! // However, either way, we ensure that we insert the new item in a way that between // `derived_from` and the new one, there are only items *compatible with* `derived_from`. if new_perm == Permission::SharedReadWrite { // A very liberal reborrow because the new pointer does not expect any kind of aliasing guarantee. // Just insert new permission as child of old permission, and maintain everything else. // This inserts "as far down as possible", which is good because it makes this pointer as // long-lived as possible *and* we want all the items that are incompatible with this // to actually get removed from the stack. If we pushed a `SharedReadWrite` on top of // a `SharedReadOnly`, we'd violate the invariant that `SaredReadOnly` are at the top // and we'd allow write access without invalidating frozen shared references! // This ensures F2b for `SharedReadWrite` by adding the new item below any // potentially existing `SharedReadOnly`. self.grant(new_perm, new_tag, derived_from_idx+1); // No barrier. They can rightfully alias with `&mut`. // FIXME: This means that the `dereferencable` attribute on non-frozen shared references // is incorrect! They are dereferencable when the function is called, but might become // non-dereferencable during the course of execution. // Also see [1], [2]. // // [1]: , // [2]: } else { // A "safe" reborrow for a pointer that actually expects some aliasing guarantees. // Here, creating a reference actually counts as an access, and pops incompatible // stuff off the stack. // This ensures F2b for `Unique`, by removing offending `SharedReadOnly`. let check_idx = self.access(access, derived_from, global)?; assert_eq!(check_idx, derived_from_idx, "somehow we saw different items??"); // 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 // get the strongest possible guarantees. // This ensures U1 and F1. self.grant(new_perm, new_tag, self.borrows.len()); // Now is a good time to add the barrier, protecting the item we just added. if let Some(call) = barrier { self.barrier(call); } } // Make sure that after all this, the stack's invariant is still maintained. if cfg!(debug_assertions) { self.test_invariants(); } Ok(()) } } // # Stacked Borrows Core End /// Map per-stack operations to higher-level per-location-range operations. impl<'tcx> Stacks { /// Creates new stack with initial tag. pub(crate) fn new( size: Size, tag: Tag, extra: MemoryState, ) -> Self { let item = Item::Permission(Permission::Unique, tag); let stack = Stack { borrows: vec![item], }; Stacks { stacks: RefCell::new(RangeMap::new(size, stack)), global: extra, } } /// Call `f` on every stack in the range. fn for_each( &self, ptr: Pointer, size: Size, f: impl Fn(&mut Stack, Tag, &GlobalState) -> EvalResult<'tcx>, ) -> EvalResult<'tcx> { let global = self.global.borrow(); let mut stacks = self.stacks.borrow_mut(); for stack in stacks.iter_mut(ptr.offset, size) { f(stack, ptr.tag, &*global)?; } Ok(()) } } /// Glue code to connect with Miri Machine Hooks impl Stacks { pub fn new_allocation( size: Size, extra: &MemoryState, kind: MemoryKind, ) -> (Self, Tag) { let tag = match kind { MemoryKind::Stack => { // New unique borrow. This `Uniq` is not accessible by the program, // so it will only ever be used when using the local directly (i.e., // not through a pointer). That is, whenever we directly use a local, this will pop // everything else off the stack, invalidating all previous pointers, // and in particular, *all* raw pointers. This subsumes the explicit // `reset` which the blog post [1] says to perform when accessing a local. // // [1]: Tag::Tagged(extra.borrow_mut().new_ptr()) } _ => { Tag::Untagged } }; let stack = Stacks::new(size, tag, Rc::clone(extra)); (stack, tag) } } impl AllocationExtra for Stacks { #[inline(always)] fn memory_read<'tcx>( alloc: &Allocation, ptr: Pointer, size: Size, ) -> EvalResult<'tcx> { trace!("read access with tag {}: {:?}, size {}", ptr.tag, ptr, size.bytes()); alloc.extra.for_each(ptr, size, |stack, tag, global| { stack.access(AccessKind::Read, tag, global)?; Ok(()) }) } #[inline(always)] fn memory_written<'tcx>( alloc: &mut Allocation, ptr: Pointer, size: Size, ) -> EvalResult<'tcx> { trace!("write access with tag {}: {:?}, size {}", ptr.tag, ptr, size.bytes()); alloc.extra.for_each(ptr, size, |stack, tag, global| { stack.access(AccessKind::Write, tag, global)?; Ok(()) }) } #[inline(always)] fn memory_deallocated<'tcx>( alloc: &mut Allocation, ptr: Pointer, size: Size, ) -> EvalResult<'tcx> { trace!("deallocation with tag {}: {:?}, size {}", ptr.tag, ptr, size.bytes()); alloc.extra.for_each(ptr, size, |stack, tag, global| { stack.dealloc(tag, global) }) } } impl<'a, 'mir, 'tcx> EvalContextPrivExt<'a, 'mir, 'tcx> for crate::MiriEvalContext<'a, 'mir, 'tcx> {} trait EvalContextPrivExt<'a, 'mir, 'tcx: 'a+'mir>: crate::MiriEvalContextExt<'a, 'mir, 'tcx> { /// High-level `reborrow` operation. This decides which reference gets which kind /// of permission! fn reborrow( &mut self, place: MPlaceTy<'tcx, Tag>, size: Size, kind: RefKind, new_tag: Tag, fn_barrier: bool, ) -> EvalResult<'tcx> { let this = self.eval_context_mut(); let barrier = if fn_barrier { Some(this.frame().extra) } else { None }; let ptr = place.ptr.to_ptr()?; trace!("reborrow: {:?} reference {} derived from {} (pointee {}): {:?}, size {}", kind, new_tag, ptr.tag, place.layout.ty, ptr, size.bytes()); // Get the allocation. It might not be mutable, so we cannot use `get_mut`. let alloc = this.memory().get(ptr.alloc_id)?; alloc.check_bounds(this, ptr, size)?; // Update the stacks. let perm = match kind { RefKind::Unique => Permission::Unique, RefKind::Raw { mutable: true } => Permission::SharedReadWrite, RefKind::Shared | RefKind::Raw { mutable: false } => { // Shared references and *const are a whole different kind of game, the // permission is not uniform across the entire range! // We need a frozen-sensitive reborrow. return this.visit_freeze_sensitive(place, size, |cur_ptr, size, frozen| { // We are only ever `SharedReadOnly` inside the frozen bits. let perm = if frozen { Permission::SharedReadOnly } else { Permission::SharedReadWrite }; alloc.extra.for_each(cur_ptr, size, |stack, tag, global| { stack.reborrow(tag, barrier, perm, new_tag, global) }) }); } }; debug_assert_ne!(perm, Permission::SharedReadOnly, "SharedReadOnly must be used frozen-sensitive"); alloc.extra.for_each(ptr, size, |stack, tag, global| { stack.reborrow(tag, barrier, perm, new_tag, global) }) } /// Retags an indidual pointer, returning the retagged version. /// `mutbl` can be `None` to make this a raw pointer. fn retag_reference( &mut self, val: ImmTy<'tcx, Tag>, kind: RefKind, fn_barrier: bool, two_phase: bool, ) -> EvalResult<'tcx, Immediate> { let this = self.eval_context_mut(); // We want a place for where the ptr *points to*, so we get one. let place = this.ref_to_mplace(val)?; let size = this.size_and_align_of_mplace(place)? .map(|(size, _)| size) .unwrap_or_else(|| place.layout.size); if size == Size::ZERO { // Nothing to do for ZSTs. return Ok(*val); } // Compute new borrow. let new_tag = match kind { RefKind::Raw { .. } => Tag::Untagged, _ => Tag::Tagged(this.memory().extra.borrow_mut().new_ptr()), }; // Reborrow. this.reborrow(place, size, kind, new_tag, fn_barrier)?; let new_place = place.replace_tag(new_tag); // Handle two-phase borrows. if two_phase { assert!(kind == RefKind::Unique, "two-phase shared borrows make no sense"); // Grant read access *to the parent pointer* with the old tag. This means the same pointer // has multiple items in the stack now! // FIXME: Think about this some more, in particular about the interaction with cast-to-raw. // Maybe find a better way to express 2-phase, now that we have a "more expressive language" // in the stack. let old_tag = place.ptr.to_ptr().unwrap().tag; this.reborrow(new_place, size, RefKind::Shared, old_tag, /* fn_barrier: */ false)?; } // Return new pointer. Ok(new_place.to_ref()) } } impl<'a, 'mir, 'tcx> EvalContextExt<'a, 'mir, 'tcx> for crate::MiriEvalContext<'a, 'mir, 'tcx> {} pub trait EvalContextExt<'a, 'mir, 'tcx: 'a+'mir>: crate::MiriEvalContextExt<'a, 'mir, 'tcx> { fn retag( &mut self, kind: RetagKind, place: PlaceTy<'tcx, Tag> ) -> EvalResult<'tcx> { let this = self.eval_context_mut(); // Determine mutability and whether to add a barrier. // Cannot use `builtin_deref` because that reports *immutable* for `Box`, // making it useless. fn qualify(ty: ty::Ty<'_>, kind: RetagKind) -> Option<(RefKind, bool)> { match ty.sty { // References are simple. ty::Ref(_, _, MutMutable) => Some((RefKind::Unique, kind == RetagKind::FnEntry)), ty::Ref(_, _, MutImmutable) => Some((RefKind::Shared, kind == RetagKind::FnEntry)), // Raw pointers need to be enabled. ty::RawPtr(tym) if kind == RetagKind::Raw => Some((RefKind::Raw { mutable: tym.mutbl == MutMutable }, false)), // Boxes do not get a barrier: barriers reflect that references outlive the call // they were passed in to; that's just not the case for boxes. ty::Adt(..) if ty.is_box() => Some((RefKind::Unique, false)), _ => None, } } // We need a visitor to visit all references. However, that requires // a `MemPlace`, so we have a fast path for reference types that // avoids allocating. if let Some((mutbl, barrier)) = qualify(place.layout.ty, kind) { // Fast path. let val = this.read_immediate(this.place_to_op(place)?)?; let val = this.retag_reference(val, mutbl, barrier, kind == RetagKind::TwoPhase)?; this.write_immediate(val, place)?; return Ok(()); } let place = this.force_allocation(place)?; let mut visitor = RetagVisitor { ecx: this, kind }; visitor.visit_value(place)?; // The actual visitor. struct RetagVisitor<'ecx, 'a, 'mir, 'tcx> { ecx: &'ecx mut MiriEvalContext<'a, 'mir, 'tcx>, kind: RetagKind, } impl<'ecx, 'a, 'mir, 'tcx> MutValueVisitor<'a, 'mir, 'tcx, Evaluator<'tcx>> for RetagVisitor<'ecx, 'a, 'mir, 'tcx> { type V = MPlaceTy<'tcx, Tag>; #[inline(always)] fn ecx(&mut self) -> &mut MiriEvalContext<'a, 'mir, 'tcx> { &mut self.ecx } // Primitives of reference type, that is the one thing we are interested in. fn visit_primitive(&mut self, place: MPlaceTy<'tcx, Tag>) -> EvalResult<'tcx> { // Cannot use `builtin_deref` because that reports *immutable* for `Box`, // making it useless. if let Some((mutbl, barrier)) = qualify(place.layout.ty, self.kind) { let val = self.ecx.read_immediate(place.into())?; let val = self.ecx.retag_reference( val, mutbl, barrier, self.kind == RetagKind::TwoPhase )?; self.ecx.write_immediate(val, place.into())?; } Ok(()) } } Ok(()) } }