From 09919c2b596b19ad36850b77d8f6ea00b3b60612 Mon Sep 17 00:00:00 2001 From: Ralf Jung Date: Wed, 7 Nov 2018 14:56:25 +0100 Subject: [PATCH] Retag is the only operation that generates new tags --- .gitignore | 1 - src/fn_call.rs | 2 +- src/intrinsic.rs | 22 +-- src/lib.rs | 58 ++++---- src/stacked_borrows.rs | 127 +++++++++++------- .../stacked_borrows/buggy_as_mut_slice.rs | 8 +- .../stacked_borrows/buggy_split_at_mut.rs | 2 +- .../static_memory_modification.rs | 3 + .../stacked_borrows/transmute-is-no-escape.rs | 12 ++ .../stacked_borrows/unescaped_local.rs | 8 +- tests/run-pass/stacked-borrows.rs | 20 ++- 11 files changed, 161 insertions(+), 102 deletions(-) create mode 100644 tests/compile-fail/stacked_borrows/transmute-is-no-escape.rs diff --git a/.gitignore b/.gitignore index dcca7ec10a3..ca23de42088 100644 --- a/.gitignore +++ b/.gitignore @@ -2,6 +2,5 @@ target /doc tex/*/out *.dot -*.mir *.rs.bk Cargo.lock diff --git a/src/fn_call.rs b/src/fn_call.rs index d9bf049df70..150cf7402a6 100644 --- a/src/fn_call.rs +++ b/src/fn_call.rs @@ -555,7 +555,7 @@ impl<'a, 'mir, 'tcx: 'mir + 'a> EvalContextExt<'tcx, 'mir> for super::MiriEvalCo } "pthread_attr_getstack" => { // second argument is where we are supposed to write the stack size - let ptr = self.ref_to_mplace(self.read_immediate(args[1])?)?; + let ptr = self.deref_operand(args[1])?; let stackaddr = Scalar::from_int(0x80000, args[1].layout.size); // just any address self.write_scalar(stackaddr, ptr.into())?; // return 0 diff --git a/src/intrinsic.rs b/src/intrinsic.rs index 20402f4a232..e23cadfcaf0 100644 --- a/src/intrinsic.rs +++ b/src/intrinsic.rs @@ -59,7 +59,7 @@ impl<'a, 'mir, 'tcx> EvalContextExt<'tcx> for super::MiriEvalContext<'a, 'mir, ' "atomic_load_relaxed" | "atomic_load_acq" | "volatile_load" => { - let ptr = self.ref_to_mplace(self.read_immediate(args[0])?)?; + let ptr = self.deref_operand(args[0])?; let val = self.read_scalar(ptr.into())?; // make sure it fits into a scalar; otherwise it cannot be atomic self.write_scalar(val, dest)?; } @@ -68,7 +68,7 @@ impl<'a, 'mir, 'tcx> EvalContextExt<'tcx> for super::MiriEvalContext<'a, 'mir, ' "atomic_store_relaxed" | "atomic_store_rel" | "volatile_store" => { - let ptr = self.ref_to_mplace(self.read_immediate(args[0])?)?; + let ptr = self.deref_operand(args[0])?; let val = self.read_scalar(args[1])?; // make sure it fits into a scalar; otherwise it cannot be atomic self.write_scalar(val, ptr.into())?; } @@ -78,7 +78,7 @@ impl<'a, 'mir, 'tcx> EvalContextExt<'tcx> for super::MiriEvalContext<'a, 'mir, ' } _ if intrinsic_name.starts_with("atomic_xchg") => { - let ptr = self.ref_to_mplace(self.read_immediate(args[0])?)?; + let ptr = self.deref_operand(args[0])?; let new = self.read_scalar(args[1])?; let old = self.read_scalar(ptr.into())?; self.write_scalar(old, dest)?; // old value is returned @@ -86,10 +86,10 @@ impl<'a, 'mir, 'tcx> EvalContextExt<'tcx> for super::MiriEvalContext<'a, 'mir, ' } _ if intrinsic_name.starts_with("atomic_cxchg") => { - let ptr = self.ref_to_mplace(self.read_immediate(args[0])?)?; - let expect_old = self.read_immediate(args[1])?; // read as value for the sake of `binary_op_imm()` + let ptr = self.deref_operand(args[0])?; + let expect_old = self.read_immediate(args[1])?; // read as immediate for the sake of `binary_op_imm()` let new = self.read_scalar(args[2])?; - let old = self.read_immediate(ptr.into())?; // read as value for the sake of `binary_op_imm()` + let old = self.read_immediate(ptr.into())?; // read as immediate for the sake of `binary_op_imm()` // binary_op_imm will bail if either of them is not a scalar let (eq, _) = self.binary_op_imm(mir::BinOp::Eq, old, expect_old)?; let res = Immediate::ScalarPair(old.to_scalar_or_undef(), eq.into()); @@ -125,7 +125,7 @@ impl<'a, 'mir, 'tcx> EvalContextExt<'tcx> for super::MiriEvalContext<'a, 'mir, ' "atomic_xsub_rel" | "atomic_xsub_acqrel" | "atomic_xsub_relaxed" => { - let ptr = self.ref_to_mplace(self.read_immediate(args[0])?)?; + let ptr = self.deref_operand(args[0])?; if !ptr.layout.ty.is_integral() { return err!(Unimplemented(format!("Atomic arithmetic operations only work on integer types"))); } @@ -167,7 +167,7 @@ impl<'a, 'mir, 'tcx> EvalContextExt<'tcx> for super::MiriEvalContext<'a, 'mir, ' } "discriminant_value" => { - let place = self.ref_to_mplace(self.read_immediate(args[0])?)?; + let place = self.deref_operand(args[0])?; let discr_val = self.read_discriminant(place.into())?.0; self.write_scalar(Scalar::from_uint(discr_val, dest.layout.size), dest)?; } @@ -279,7 +279,7 @@ impl<'a, 'mir, 'tcx> EvalContextExt<'tcx> for super::MiriEvalContext<'a, 'mir, ' } "move_val_init" => { - let ptr = self.ref_to_mplace(self.read_immediate(args[0])?)?; + let ptr = self.deref_operand(args[0])?; self.copy_op(args[1], ptr.into())?; } @@ -347,7 +347,7 @@ impl<'a, 'mir, 'tcx> EvalContextExt<'tcx> for super::MiriEvalContext<'a, 'mir, ' } "size_of_val" => { - let mplace = self.ref_to_mplace(self.read_immediate(args[0])?)?; + let mplace = self.deref_operand(args[0])?; let (size, _) = self.size_and_align_of_mplace(mplace)? .expect("size_of_val called on extern type"); let ptr_size = self.pointer_size(); @@ -359,7 +359,7 @@ impl<'a, 'mir, 'tcx> EvalContextExt<'tcx> for super::MiriEvalContext<'a, 'mir, ' "min_align_of_val" | "align_of_val" => { - let mplace = self.ref_to_mplace(self.read_immediate(args[0])?)?; + let mplace = self.deref_operand(args[0])?; let (_, align) = self.size_and_align_of_mplace(mplace)? .expect("size_of_val called on extern type"); let ptr_size = self.pointer_size(); diff --git a/src/lib.rs b/src/lib.rs index b7deb8ee116..e8691409b1a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -296,7 +296,6 @@ impl<'a, 'mir, 'tcx> Machine<'a, 'mir, 'tcx> for Evaluator<'tcx> { type AllocExtra = stacked_borrows::Stacks; type PointerTag = Borrow; - const ENABLE_PTR_TRACKING_HOOKS: bool = true; type MemoryMap = MonoHashMap, Allocation)>; @@ -446,26 +445,6 @@ impl<'a, 'mir, 'tcx> Machine<'a, 'mir, 'tcx> for Evaluator<'tcx> { Cow::Owned(alloc) } - #[inline(always)] - fn tag_reference( - ecx: &mut EvalContext<'a, 'mir, 'tcx, Self>, - place: MPlaceTy<'tcx, Borrow>, - mutability: Option, - ) -> EvalResult<'tcx, Scalar> { - let (size, _) = ecx.size_and_align_of_mplace(place)? - // for extern types, just cover what we can - .unwrap_or_else(|| place.layout.size_and_align()); - if !ecx.machine.validate || size == Size::ZERO { - // No tracking - Ok(place.ptr) - } else { - let ptr = place.ptr.to_ptr()?; - let tag = ecx.tag_reference(place, size, mutability.into())?; - Ok(Scalar::Ptr(Pointer::new_with_tag(ptr.alloc_id, ptr.offset, tag))) - } - } - - #[inline(always)] fn tag_dereference( ecx: &EvalContext<'a, 'mir, 'tcx, Self>, place: MPlaceTy<'tcx, Borrow>, @@ -478,7 +457,7 @@ impl<'a, 'mir, 'tcx> Machine<'a, 'mir, 'tcx> for Evaluator<'tcx> { // No tracking Ok(place.ptr) } else { - let ptr = place.ptr.to_ptr()?; + let ptr = place.ptr.to_ptr()?; // assert this is not a scalar let tag = ecx.tag_dereference(place, size, mutability.into())?; Ok(Scalar::Ptr(Pointer::new_with_tag(ptr.alloc_id, ptr.offset, tag))) } @@ -499,6 +478,31 @@ impl<'a, 'mir, 'tcx> Machine<'a, 'mir, 'tcx> for Evaluator<'tcx> { } } + #[inline] + fn escape_to_raw( + ecx: &mut EvalContext<'a, 'mir, 'tcx, Self>, + ptr: OpTy<'tcx, Self::PointerTag>, + ) -> EvalResult<'tcx> { + // It is tempting to check the type here, but drop glue does EscapeToRaw + // on a raw pointer. + // This is deliberately NOT `deref_operand` as we do not want `tag_dereference` + // to be called! That would kill the original tag if we got a raw ptr. + let place = ecx.ref_to_mplace(ecx.read_immediate(ptr)?)?; + let (size, _) = ecx.size_and_align_of_mplace(place)? + // for extern types, just cover what we can + .unwrap_or_else(|| place.layout.size_and_align()); + if !ecx.tcx.sess.opts.debugging_opts.mir_emit_retag || + !ecx.machine.validate || size == Size::ZERO + { + // No tracking, or no retagging. The latter is possible because a dependency of ours + // might be called with different flags than we are, so there are `Retag` + // statements but we do not want to execute them. + Ok(()) + } else { + ecx.escape_to_raw(place, size) + } + } + #[inline(always)] fn retag( ecx: &mut EvalContext<'a, 'mir, 'tcx, Self>, @@ -506,12 +510,14 @@ impl<'a, 'mir, 'tcx> Machine<'a, 'mir, 'tcx> for Evaluator<'tcx> { place: PlaceTy<'tcx, Borrow>, ) -> EvalResult<'tcx> { if !ecx.tcx.sess.opts.debugging_opts.mir_emit_retag || !Self::enforce_validity(ecx) { - // No tracking, or no retagging. This is possible because a dependency of ours might be - // called with different flags than we are, + // No tracking, or no retagging. The latter is possible because a dependency of ours + // might be called with different flags than we are, so there are `Retag` + // statements but we do not want to execute them. // Also, honor the whitelist in `enforce_validity` because otherwise we might retag // uninitialized data. - return Ok(()) + Ok(()) + } else { + ecx.retag(fn_entry, place) } - ecx.retag(fn_entry, place) } } diff --git a/src/stacked_borrows.rs b/src/stacked_borrows.rs index c6cd7f5005d..e7c595f1c6c 100644 --- a/src/stacked_borrows.rs +++ b/src/stacked_borrows.rs @@ -6,7 +6,7 @@ use rustc::hir; use crate::{ EvalResult, MiriEvalContext, HelpersEvalContextExt, MemoryKind, MiriMemoryKind, RangeMap, AllocId, Allocation, AllocationExtra, - Pointer, PlaceTy, MPlaceTy, + Pointer, MemPlace, Scalar, Immediate, ImmTy, PlaceTy, MPlaceTy, }; pub type Timestamp = u64; @@ -395,13 +395,6 @@ impl<'tcx> Stacks { pub trait EvalContextExt<'tcx> { - fn tag_reference( - &mut self, - place: MPlaceTy<'tcx, Borrow>, - size: Size, - usage: UsageKind, - ) -> EvalResult<'tcx, Borrow>; - fn tag_dereference( &self, place: MPlaceTy<'tcx, Borrow>, @@ -415,47 +408,27 @@ pub trait EvalContextExt<'tcx> { kind: MemoryKind, ) -> Borrow; + /// Retag an indidual pointer, returning the retagged version. + fn retag_ptr( + &mut self, + ptr: ImmTy<'tcx, Borrow>, + mutbl: hir::Mutability, + ) -> EvalResult<'tcx, Immediate>; + fn retag( &mut self, fn_entry: bool, place: PlaceTy<'tcx, Borrow> ) -> EvalResult<'tcx>; -} -impl<'a, 'mir, 'tcx> EvalContextExt<'tcx> for MiriEvalContext<'a, 'mir, 'tcx> { - /// Called for place-to-value conversion. - fn tag_reference( + fn escape_to_raw( &mut self, place: MPlaceTy<'tcx, Borrow>, size: Size, - usage: UsageKind, - ) -> EvalResult<'tcx, Borrow> { - let ptr = place.ptr.to_ptr()?; - let time = self.machine.stacked_borrows.increment_clock(); - let new_bor = match usage { - UsageKind::Write => Borrow::Uniq(time), - UsageKind::Read => Borrow::Shr(Some(time)), - UsageKind::Raw => Borrow::Shr(None), - }; - trace!("tag_reference: Creating new reference ({:?}) for {:?} (pointee {}): {:?}", - usage, ptr, place.layout.ty, new_bor); - - // Update the stacks. First create the new ref as usual, then maybe freeze stuff. - self.memory().check_bounds(ptr, size, false)?; - let alloc = self.memory().get(ptr.alloc_id).expect("We checked that the ptr is fine!"); - alloc.extra.use_and_maybe_re_borrow(ptr, size, usage, Some(new_bor))?; - // Maybe freeze stuff - if let Borrow::Shr(Some(bor_t)) = new_bor { - self.visit_frozen(place, size, |frz_ptr, size| { - debug_assert_eq!(frz_ptr.alloc_id, ptr.alloc_id); - // Be frozen! - alloc.extra.freeze(frz_ptr, size, bor_t) - })?; - } - - Ok(new_bor) - } + ) -> EvalResult<'tcx>; +} +impl<'a, 'mir, 'tcx> EvalContextExt<'tcx> for MiriEvalContext<'a, 'mir, 'tcx> { /// Called for value-to-place conversion. /// /// Note that this does NOT mean that all this memory will actually get accessed/referenced! @@ -466,9 +439,9 @@ impl<'a, 'mir, 'tcx> EvalContextExt<'tcx> for MiriEvalContext<'a, 'mir, 'tcx> { size: Size, usage: UsageKind, ) -> EvalResult<'tcx, Borrow> { - let ptr = place.ptr.to_ptr()?; trace!("tag_dereference: Accessing reference ({:?}) for {:?} (pointee {})", - usage, ptr, place.layout.ty); + usage, place.ptr, place.layout.ty); + let ptr = place.ptr.to_ptr()?; // In principle we should not have to do anything here. However, with transmutes involved, // it can happen that the tag of `ptr` does not actually match `usage`, and we // should adjust for that. @@ -551,6 +524,50 @@ impl<'a, 'mir, 'tcx> EvalContextExt<'tcx> for MiriEvalContext<'a, 'mir, 'tcx> { Borrow::Uniq(time) } + fn retag_ptr( + &mut self, + val: ImmTy<'tcx, Borrow>, + mutbl: hir::Mutability, + ) -> EvalResult<'tcx, Immediate> { + // We want a place for where the ptr *points to*, so we get one. + let place = self.ref_to_mplace(val)?; + let size = self.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); + } + + // Prepare to re-borrow this place. + let ptr = place.ptr.to_ptr()?; + let time = self.machine.stacked_borrows.increment_clock(); + let new_bor = match mutbl { + hir::MutMutable => Borrow::Uniq(time), + hir::MutImmutable => Borrow::Shr(Some(time)), + }; + trace!("retag: Creating new reference ({:?}) for {:?} (pointee {}): {:?}", + mutbl, ptr, place.layout.ty, new_bor); + + // Update the stacks. First create a new borrow, then maybe freeze stuff. + self.memory().check_bounds(ptr, size, false)?; // `ptr_dereference` wouldn't do any checks if this is a raw ptr + let alloc = self.memory().get(ptr.alloc_id).expect("We checked that the ptr is fine!"); + alloc.extra.use_and_maybe_re_borrow(ptr, size, Some(mutbl).into(), Some(new_bor))?; + // Maybe freeze stuff + if let Borrow::Shr(Some(bor_t)) = new_bor { + self.visit_frozen(place, size, |frz_ptr, size| { + debug_assert_eq!(frz_ptr.alloc_id, ptr.alloc_id); + // Be frozen! + alloc.extra.freeze(frz_ptr, size, bor_t) + })?; + } + + // Compute the new value and return that + let new_ptr = Scalar::Ptr(Pointer::new_with_tag(ptr.alloc_id, ptr.offset, new_bor)); + let new_place = MemPlace { ptr: new_ptr, ..*place }; + Ok(new_place.to_ref()) + } + fn retag( &mut self, _fn_entry: bool, @@ -558,20 +575,30 @@ impl<'a, 'mir, 'tcx> EvalContextExt<'tcx> for MiriEvalContext<'a, 'mir, 'tcx> { ) -> EvalResult<'tcx> { // For now, we only retag if the toplevel type is a reference. // TODO: Recurse into structs and enums, sharing code with validation. + // TODO: Honor `fn_entry`. let mutbl = match place.layout.ty.sty { ty::Ref(_, _, mutbl) => mutbl, // go ahead - _ => return Ok(()), // don't do a thing + _ => return Ok(()), // do nothing, for now }; - // We want to reborrow the reference stored there. This will call the hooks - // above. First deref, which will call `tag_dereference`. - // (This is somewhat redundant because validation already did the same thing, - // but what can you do.) + // Retag the pointer and write it back. let val = self.read_immediate(self.place_to_op(place)?)?; - let dest = self.ref_to_mplace(val)?; - // Now put a new ref into the old place, which will call `tag_reference`. - // FIXME: Honor `fn_entry`! - let val = self.create_ref(dest, Some(mutbl))?; + let val = self.retag_ptr(val, mutbl)?; self.write_immediate(val, place)?; Ok(()) } + + fn escape_to_raw( + &mut self, + place: MPlaceTy<'tcx, Borrow>, + size: Size, + ) -> EvalResult<'tcx> { + trace!("self: {:?} is now accessible by raw pointers", *place); + // Re-borrow to raw. This is a NOP for shared borrows, but we do not know the borrow + // type here and that's also okay. + let ptr = place.ptr.to_ptr()?; + self.memory().check_bounds(ptr, size, false)?; // `ptr_dereference` wouldn't do any checks if this is a raw ptr + let alloc = self.memory().get(ptr.alloc_id).expect("We checked that the ptr is fine!"); + alloc.extra.use_and_maybe_re_borrow(ptr, size, UsageKind::Raw, Some(Borrow::default()))?; + Ok(()) + } } diff --git a/tests/compile-fail/stacked_borrows/buggy_as_mut_slice.rs b/tests/compile-fail/stacked_borrows/buggy_as_mut_slice.rs index 4857ada7fb2..2f3d0793f63 100644 --- a/tests/compile-fail/stacked_borrows/buggy_as_mut_slice.rs +++ b/tests/compile-fail/stacked_borrows/buggy_as_mut_slice.rs @@ -1,4 +1,4 @@ -#![allow(unused_variables)] +// error-pattern: mutable reference with frozen tag mod safe { use std::slice::from_raw_parts_mut; @@ -12,8 +12,10 @@ mod safe { fn main() { let v = vec![0,1,2]; - let v1 = safe::as_mut_slice(&v); + let _v1 = safe::as_mut_slice(&v); +/* let v2 = safe::as_mut_slice(&v); - v1[1] = 5; //~ ERROR does not exist on the stack + v1[1] = 5; v1[1] = 6; +*/ } diff --git a/tests/compile-fail/stacked_borrows/buggy_split_at_mut.rs b/tests/compile-fail/stacked_borrows/buggy_split_at_mut.rs index a6daa5d93d7..711544f8014 100644 --- a/tests/compile-fail/stacked_borrows/buggy_split_at_mut.rs +++ b/tests/compile-fail/stacked_borrows/buggy_split_at_mut.rs @@ -11,7 +11,6 @@ mod safe { assert!(mid <= len); (from_raw_parts_mut(ptr, len - mid), // BUG: should be "mid" instead of "len - mid" - //~^ ERROR does not exist on the stack from_raw_parts_mut(ptr.offset(mid as isize), len - mid)) } } @@ -20,6 +19,7 @@ mod safe { fn main() { let mut array = [1,2,3,4]; let (a, b) = safe::split_at_mut(&mut array, 0); + //~^ ERROR does not exist on the stack a[1] = 5; b[1] = 6; } diff --git a/tests/compile-fail/stacked_borrows/static_memory_modification.rs b/tests/compile-fail/stacked_borrows/static_memory_modification.rs index c092cbfe509..5c605eff678 100644 --- a/tests/compile-fail/stacked_borrows/static_memory_modification.rs +++ b/tests/compile-fail/stacked_borrows/static_memory_modification.rs @@ -1,3 +1,6 @@ +// FIXME still considering whether we are okay with this not being an error +// ignore-test + static X: usize = 5; #[allow(mutable_transmutes)] diff --git a/tests/compile-fail/stacked_borrows/transmute-is-no-escape.rs b/tests/compile-fail/stacked_borrows/transmute-is-no-escape.rs new file mode 100644 index 00000000000..1ab005e3fa1 --- /dev/null +++ b/tests/compile-fail/stacked_borrows/transmute-is-no-escape.rs @@ -0,0 +1,12 @@ +// Make sure we cannot use raw ptrs that got transmuted from mutable references +// (i.e, no EscapeToRaw happened). +// We could, in principle, to EscapeToRaw lazily to allow this code, but that +// would no alleviate the need for EscapeToRaw (see `ref_raw_int_raw` in +// `run-pass/stacked-borrows.rs`), and thus increase overall complexity. +use std::mem; + +fn main() { + let mut x: i32 = 42; + let raw: *mut i32 = unsafe { mem::transmute(&mut x) }; + unsafe { *raw = 13; } //~ ERROR does not exist on the stack +} diff --git a/tests/compile-fail/stacked_borrows/unescaped_local.rs b/tests/compile-fail/stacked_borrows/unescaped_local.rs index 8627cc44c2e..054697b04a0 100644 --- a/tests/compile-fail/stacked_borrows/unescaped_local.rs +++ b/tests/compile-fail/stacked_borrows/unescaped_local.rs @@ -1,10 +1,8 @@ -use std::mem; - // Make sure we cannot use raw ptrs to access a local that -// has never been escaped to the raw world. +// we took the direct address of. fn main() { let mut x = 42; - let ptr = &mut x; - let raw: *mut i32 = unsafe { mem::transmute(ptr) }; + let raw = &mut x as *mut i32 as usize as *mut i32; + let _ptr = &mut x; unsafe { *raw = 13; } //~ ERROR does not exist on the stack } diff --git a/tests/run-pass/stacked-borrows.rs b/tests/run-pass/stacked-borrows.rs index 8faa4d65911..adab2b5a568 100644 --- a/tests/run-pass/stacked-borrows.rs +++ b/tests/run-pass/stacked-borrows.rs @@ -3,15 +3,17 @@ fn main() { deref_partially_dangling_raw(); read_does_not_invalidate1(); read_does_not_invalidate2(); + ref_raw_int_raw(); } // Deref a raw ptr to access a field of a large struct, where the field // is allocated but not the entire struct is. // For now, we want to allow this. fn deref_partially_dangling_raw() { - let x = (1, 1); + let x = (1, 13); let xptr = &x as *const _ as *const (i32, i32, i32); - let _val = unsafe { (*xptr).1 }; + let val = unsafe { (*xptr).1 }; + assert_eq!(val, 13); } // Make sure that reading from an `&mut` does, like reborrowing to `&`, @@ -23,7 +25,7 @@ fn read_does_not_invalidate1() { let _val = x.1; // we just read, this does NOT invalidate the reborrows. ret } - foo(&mut (1, 2)); + assert_eq!(*foo(&mut (1, 2)), 2); } // Same as above, but this time we first create a raw, then read from `&mut` // and then freeze from the raw. @@ -34,5 +36,15 @@ fn read_does_not_invalidate2() { let ret = unsafe { &(*xraw).1 }; ret } - foo(&mut (1, 2)); + assert_eq!(*foo(&mut (1, 2)), 2); +} + +// Just to make sure that casting a ref to raw, to int and back to raw +// and only then using it works. This rules out ideas like "do escape-to-raw lazily": +// After casting to int and back, we lost the tag that could have let us do that. +fn ref_raw_int_raw() { + let mut x = 3; + let xref = &mut x; + let xraw = xref as *mut i32 as usize as *mut i32; + assert_eq!(unsafe { *xraw }, 3); }