From 192da8819f2e936f7b944624a202a97405e689b3 Mon Sep 17 00:00:00 2001 From: Oliver Schneider Date: Wed, 12 Jul 2017 14:51:47 +0200 Subject: [PATCH] Ensure that it is not possible to explicitly free stack memory --- src/error.rs | 21 ++++-- src/eval_context.rs | 31 ++++---- src/memory.rs | 121 ++++++++++++++++-------------- src/terminator/mod.rs | 20 ++--- src/traits.rs | 6 +- tests/compile-fail/double_free.rs | 8 ++ tests/compile-fail/stack_free.rs | 5 ++ 7 files changed, 122 insertions(+), 90 deletions(-) create mode 100644 tests/compile-fail/double_free.rs create mode 100644 tests/compile-fail/stack_free.rs diff --git a/src/error.rs b/src/error.rs index 3e1155e0b874..403ca9539e5b 100644 --- a/src/error.rs +++ b/src/error.rs @@ -2,7 +2,7 @@ use std::error::Error; use std::fmt; use rustc::mir; use rustc::ty::{FnSig, Ty, layout}; -use memory::MemoryPointer; +use memory::{MemoryPointer, Kind}; use rustc_const_math::ConstMathErr; use syntax::codemap::Span; @@ -12,6 +12,7 @@ pub enum EvalError<'tcx> { NoMirFor(String), UnterminatedCString(MemoryPointer), DanglingPointerDeref, + DoubleFree, InvalidMemoryAccess, InvalidFunctionPointer, InvalidBool, @@ -56,8 +57,8 @@ pub enum EvalError<'tcx> { AssumptionNotHeld, InlineAsm, TypeNotPrimitive(Ty<'tcx>), - ReallocatedStaticMemory, - DeallocatedStaticMemory, + ReallocatedWrongMemoryKind(Kind, Kind), + DeallocatedWrongMemoryKind(Kind, Kind), ReallocateNonBasePtr, DeallocateNonBasePtr, IncorrectAllocationInformation, @@ -84,6 +85,8 @@ impl<'tcx> Error for EvalError<'tcx> { "tried to access memory through an invalid pointer", DanglingPointerDeref => "dangling pointer was dereferenced", + DoubleFree => + "tried to deallocate dangling pointer", InvalidFunctionPointer => "tried to use an integer pointer or a dangling pointer as a function pointer", InvalidBool => @@ -148,10 +151,10 @@ impl<'tcx> Error for EvalError<'tcx> { "miri does not support inline assembly", TypeNotPrimitive(_) => "expected primitive type, got nonprimitive", - ReallocatedStaticMemory => - "tried to reallocate static memory", - DeallocatedStaticMemory => - "tried to deallocate static memory", + ReallocatedWrongMemoryKind(_, _) => + "tried to reallocate memory from one kind to another", + DeallocatedWrongMemoryKind(_, _) => + "tried to deallocate memory of the wrong kind", ReallocateNonBasePtr => "tried to reallocate with a pointer not to the beginning of an existing object", DeallocateNonBasePtr => @@ -198,6 +201,10 @@ impl<'tcx> fmt::Display for EvalError<'tcx> { write!(f, "tried to call a function with sig {} through a function pointer of type {}", sig, got), ArrayIndexOutOfBounds(span, len, index) => write!(f, "index out of bounds: the len is {} but the index is {} at {:?}", len, index, span), + ReallocatedWrongMemoryKind(old, new) => + write!(f, "tried to reallocate memory from {:?} to {:?}", old, new), + DeallocatedWrongMemoryKind(old, new) => + write!(f, "tried to deallocate {:?} memory but gave {:?} as the kind", old, new), Math(span, ref err) => write!(f, "{:?} at {:?}", err, span), Intrinsic(ref err) => diff --git a/src/eval_context.rs b/src/eval_context.rs index 58fabf694d1e..ac39cc4339d1 100644 --- a/src/eval_context.rs +++ b/src/eval_context.rs @@ -154,7 +154,7 @@ impl<'a, 'tcx> EvalContext<'a, 'tcx> { ) -> EvalResult<'tcx, MemoryPointer> { let size = self.type_size_with_substs(ty, substs)?.expect("cannot alloc memory for unsized type"); let align = self.type_align_with_substs(ty, substs)?; - self.memory.allocate(size, align) + self.memory.allocate(size, align, ::memory::Kind::Stack) } pub fn memory(&self) -> &Memory<'a, 'tcx> { @@ -354,16 +354,16 @@ impl<'a, 'tcx> EvalContext<'a, 'tcx> { // FIXME: to_ptr()? might be too extreme here, static zsts might reach this under certain conditions Value::ByRef(ptr, _aligned) => // Alignment does not matter for this call - self.memory.mark_static_initalized(ptr.to_ptr()?.alloc_id, mutable)?, + self.memory.mark_static_initalized(ptr.to_ptr()?.alloc_id, !mutable)?, Value::ByVal(val) => if let PrimVal::Ptr(ptr) = val { - self.memory.mark_inner_allocation(ptr.alloc_id, mutable)?; + self.memory.mark_inner_allocation(ptr.alloc_id, !mutable)?; }, Value::ByValPair(val1, val2) => { if let PrimVal::Ptr(ptr) = val1 { - self.memory.mark_inner_allocation(ptr.alloc_id, mutable)?; + self.memory.mark_inner_allocation(ptr.alloc_id, !mutable)?; } if let PrimVal::Ptr(ptr) = val2 { - self.memory.mark_inner_allocation(ptr.alloc_id, mutable)?; + self.memory.mark_inner_allocation(ptr.alloc_id, !mutable)?; } }, } @@ -414,11 +414,10 @@ impl<'a, 'tcx> EvalContext<'a, 'tcx> { trace!("deallocating local"); let ptr = ptr.to_ptr()?; self.memory.dump_alloc(ptr.alloc_id); - match self.memory.deallocate(ptr, None) { - // We could alternatively check whether the alloc_id is static before calling - // deallocate, but this is much simpler and is probably the rare case. - Ok(()) | Err(EvalError::DeallocatedStaticMemory) => {}, - other => return other, + match self.memory.get(ptr.alloc_id)?.kind { + ::memory::Kind::Static => {}, + ::memory::Kind::Stack => self.memory.deallocate(ptr, None, ::memory::Kind::Stack)?, + other => bug!("local contained non-stack memory: {:?}", other), } }; Ok(()) @@ -693,11 +692,13 @@ impl<'a, 'tcx> EvalContext<'a, 'tcx> { return Err(EvalError::NeedsRfc("\"heap\" allocations".to_string())); } // FIXME: call the `exchange_malloc` lang item if available - if self.type_size(ty)?.expect("box only works with sized types") == 0 { + let size = self.type_size(ty)?.expect("box only works with sized types"); + if size == 0 { let align = self.type_align(ty)?; self.write_primval(dest, PrimVal::Bytes(align.into()), dest_ty)?; } else { - let ptr = self.alloc_ptr(ty)?; + let align = self.type_align(ty)?; + let ptr = self.memory.allocate(size, align, ::memory::Kind::Rust)?; self.write_primval(dest, PrimVal::Ptr(ptr), dest_ty)?; } } @@ -1032,7 +1033,7 @@ impl<'a, 'tcx> EvalContext<'a, 'tcx> { self.write_value_to_ptr(global_val.value, ptr.into(), global_val.ty)?; // see comment on `initialized` field if global_val.initialized { - self.memory.mark_static_initalized(ptr.alloc_id, global_val.mutable)?; + self.memory.mark_static_initalized(ptr.alloc_id, !global_val.mutable)?; } let lval = self.globals.get_mut(&cid).expect("already checked"); *lval = Global { @@ -1686,7 +1687,7 @@ pub fn eval_main<'a, 'tcx: 'a>( } // Return value - let ret_ptr = ecx.memory.allocate(ecx.tcx.data_layout.pointer_size.bytes(), ecx.tcx.data_layout.pointer_align.abi())?; + let ret_ptr = ecx.memory.allocate(ecx.tcx.data_layout.pointer_size.bytes(), ecx.tcx.data_layout.pointer_align.abi(), ::memory::Kind::Stack)?; cleanup_ptr = Some(ret_ptr); // Push our stack frame @@ -1728,7 +1729,7 @@ pub fn eval_main<'a, 'tcx: 'a>( while ecx.step()? {} if let Some(cleanup_ptr) = cleanup_ptr { - ecx.memory.deallocate(cleanup_ptr, None)?; + ecx.memory.deallocate(cleanup_ptr, None, ::memory::Kind::Stack)?; } return Ok(()); } diff --git a/src/memory.rs b/src/memory.rs index 6e70e3692c11..f84dd3124824 100644 --- a/src/memory.rs +++ b/src/memory.rs @@ -35,19 +35,25 @@ pub struct Allocation { /// The alignment of the allocation to detect unaligned reads. pub align: u64, /// Whether the allocation may be modified. + pub mutable: bool, /// Use the `mark_static_initalized` method of `Memory` to ensure that an error occurs, if the memory of this /// allocation is modified or deallocated in the future. - pub static_kind: StaticKind, + /// Helps guarantee that stack allocations aren't deallocated via `rust_deallocate` + pub kind: Kind, } #[derive(Debug, PartialEq, Copy, Clone)] -pub enum StaticKind { - /// may be deallocated without breaking miri's invariants - NotStatic, - /// may be modified, but never deallocated - Mutable, - /// may neither be modified nor deallocated - Immutable, +pub enum Kind { + /// Error if deallocated any other way than `rust_deallocate` + Rust, + /// Error if deallocated any other way than `free` + C, + /// Error if deallocated via `rust_deallocate` + Stack, + /// May never be deallocated + Static, + /// Part of env var emulation + Env, } #[derive(Copy, Clone, Debug, Eq, PartialEq)] @@ -181,14 +187,14 @@ impl<'a, 'tcx> Memory<'a, 'tcx> { return Ok(MemoryPointer::new(alloc_id, 0)); } - let ptr = self.allocate(bytes.len() as u64, 1)?; + let ptr = self.allocate(bytes.len() as u64, 1, Kind::Static)?; self.write_bytes(PrimVal::Ptr(ptr), bytes)?; - self.mark_static_initalized(ptr.alloc_id, false)?; + self.mark_static_initalized(ptr.alloc_id, true)?; self.literal_alloc_cache.insert(bytes.to_vec(), ptr.alloc_id); Ok(ptr) } - pub fn allocate(&mut self, size: u64, align: u64) -> EvalResult<'tcx, MemoryPointer> { + pub fn allocate(&mut self, size: u64, align: u64, kind: Kind) -> EvalResult<'tcx, MemoryPointer> { assert_ne!(align, 0); assert!(align.is_power_of_two()); @@ -206,7 +212,8 @@ impl<'a, 'tcx> Memory<'a, 'tcx> { relocations: BTreeMap::new(), undef_mask: UndefMask::new(size), align, - static_kind: StaticKind::NotStatic, + kind, + mutable: true, }; let id = self.next_id; self.next_id.0 += 1; @@ -214,49 +221,45 @@ impl<'a, 'tcx> Memory<'a, 'tcx> { Ok(MemoryPointer::new(id, 0)) } - // TODO(solson): Track which allocations were returned from __rust_allocate and report an error - // when reallocating/deallocating any others. - pub fn reallocate(&mut self, ptr: MemoryPointer, old_size: u64, old_align: u64, new_size: u64, new_align: u64) -> EvalResult<'tcx, MemoryPointer> { + pub fn reallocate(&mut self, ptr: MemoryPointer, old_size: u64, old_align: u64, new_size: u64, new_align: u64, kind: Kind) -> EvalResult<'tcx, MemoryPointer> { use std::cmp::min; - // TODO(solson): Report error about non-__rust_allocate'd pointer. if ptr.offset != 0 || self.get(ptr.alloc_id).is_err() { return Err(EvalError::ReallocateNonBasePtr); } - if self.get(ptr.alloc_id).ok().map_or(false, |alloc| alloc.static_kind != StaticKind::NotStatic) { - return Err(EvalError::ReallocatedStaticMemory); + if let Ok(alloc) = self.get(ptr.alloc_id) { + if alloc.kind != kind { + return Err(EvalError::ReallocatedWrongMemoryKind(alloc.kind, kind)); + } } // For simplicities' sake, we implement reallocate as "alloc, copy, dealloc" - let new_ptr = self.allocate(new_size, new_align)?; + let new_ptr = self.allocate(new_size, new_align, kind)?; self.copy(ptr.into(), new_ptr.into(), min(old_size, new_size), min(old_align, new_align), /*nonoverlapping*/true)?; - self.deallocate(ptr, Some((old_size, old_align)))?; + self.deallocate(ptr, Some((old_size, old_align)), kind)?; Ok(new_ptr) } - // TODO(solson): See comment on `reallocate`. - pub fn deallocate(&mut self, ptr: MemoryPointer, size_and_align: Option<(u64, u64)>) -> EvalResult<'tcx> { + pub fn deallocate(&mut self, ptr: MemoryPointer, size_and_align: Option<(u64, u64)>, kind: Kind) -> EvalResult<'tcx> { if ptr.offset != 0 || self.get(ptr.alloc_id).is_err() { - // TODO(solson): Report error about non-__rust_allocate'd pointer. return Err(EvalError::DeallocateNonBasePtr); } - { - // deallocate_local in eval_context.rs relies on nothing actually having changed when this error occurs. - // So we do this test in advance. - let alloc = self.get(ptr.alloc_id)?; - if alloc.static_kind != StaticKind::NotStatic { - return Err(EvalError::DeallocatedStaticMemory); - } - if let Some((size, align)) = size_and_align { - if size != alloc.bytes.len() as u64 || align != alloc.align { - return Err(EvalError::IncorrectAllocationInformation); - } + let alloc = match self.alloc_map.remove(&ptr.alloc_id) { + Some(alloc) => alloc, + None => return Err(EvalError::DoubleFree), + }; + + if alloc.kind != kind { + return Err(EvalError::DeallocatedWrongMemoryKind(alloc.kind, kind)); + } + if let Some((size, align)) = size_and_align { + if size != alloc.bytes.len() as u64 || align != alloc.align { + return Err(EvalError::IncorrectAllocationInformation); } } - let alloc = self.alloc_map.remove(&ptr.alloc_id).expect("already verified"); self.memory_usage -= alloc.bytes.len() as u64; debug!("deallocated : {}", ptr.alloc_id); @@ -401,10 +404,10 @@ impl<'a, 'tcx> Memory<'a, 'tcx> { pub fn get_mut(&mut self, id: AllocId) -> EvalResult<'tcx, &mut Allocation> { match self.alloc_map.get_mut(&id) { - Some(alloc) => match alloc.static_kind { - StaticKind::Mutable | - StaticKind::NotStatic => Ok(alloc), - StaticKind::Immutable => Err(EvalError::ModifiedConstantMemory), + Some(alloc) => if alloc.mutable { + Ok(alloc) + } else { + Err(EvalError::ModifiedConstantMemory) }, None => match self.functions.get(&id) { Some(_) => Err(EvalError::DerefFunctionPointer), @@ -473,10 +476,13 @@ impl<'a, 'tcx> Memory<'a, 'tcx> { } } - let immutable = match alloc.static_kind { - StaticKind::Mutable => " (static mut)", - StaticKind::Immutable => " (immutable)", - StaticKind::NotStatic => "", + let immutable = match (alloc.kind, alloc.mutable) { + (Kind::Static, true) => " (static mut)", + (Kind::Static, false) => " (immutable)", + (Kind::Env, _) => " (env var)", + (Kind::C, _) => " (malloc)", + (Kind::Rust, _) => " (heap)", + (Kind::Stack, _) => " (stack)", }; trace!("{}({} bytes, alignment {}){}", msg, alloc.bytes.len(), alloc.align, immutable); @@ -503,7 +509,7 @@ impl<'a, 'tcx> Memory<'a, 'tcx> { let leaks: Vec<_> = self.alloc_map .iter() .filter_map(|(&key, val)| { - if val.static_kind == StaticKind::NotStatic { + if val.kind != Kind::Static { Some(key) } else { None @@ -578,26 +584,31 @@ impl<'a, 'tcx> Memory<'a, 'tcx> { } /// mark an allocation pointed to by a static as static and initialized - pub fn mark_inner_allocation(&mut self, alloc: AllocId, mutable: bool) -> EvalResult<'tcx> { + pub fn mark_inner_allocation(&mut self, alloc: AllocId, make_immutable: bool) -> EvalResult<'tcx> { // relocations into other statics are not "inner allocations" if !self.static_alloc.contains(&alloc) { - self.mark_static_initalized(alloc, mutable)?; + self.mark_static_initalized(alloc, make_immutable)?; } Ok(()) } /// mark an allocation as static and initialized, either mutable or not - pub fn mark_static_initalized(&mut self, alloc_id: AllocId, mutable: bool) -> EvalResult<'tcx> { - trace!("mark_static_initialized {:?}, mutable: {:?}", alloc_id, mutable); + pub fn mark_static_initalized(&mut self, alloc_id: AllocId, make_immutable: bool) -> EvalResult<'tcx> { + trace!("mark_static_initalized {:?}, make_immutable: {:?}", alloc_id, make_immutable); // do not use `self.get_mut(alloc_id)` here, because we might have already marked a // sub-element or have circular pointers (e.g. `Rc`-cycles) let relocations = match self.alloc_map.get_mut(&alloc_id) { - Some(&mut Allocation { ref mut relocations, static_kind: ref mut kind @ StaticKind::NotStatic, .. }) => { - *kind = if mutable { - StaticKind::Mutable - } else { - StaticKind::Immutable - }; + Some(&mut Allocation { kind: Kind::Static, ref mut mutable, .. }) => { + if make_immutable { + *mutable = false; + } + return Ok(()); + }, + Some(&mut Allocation { ref mut relocations, ref mut kind, ref mut mutable, .. }) => { + *kind = Kind::Static; + if make_immutable { + *mutable = false; + } // take out the relocations vector to free the borrow on self, so we can call // mark recursively mem::replace(relocations, Default::default()) @@ -607,7 +618,7 @@ impl<'a, 'tcx> Memory<'a, 'tcx> { }; // recurse into inner allocations for &alloc in relocations.values() { - self.mark_inner_allocation(alloc, mutable)?; + self.mark_inner_allocation(alloc, make_immutable)?; } // put back the relocations self.alloc_map.get_mut(&alloc_id).expect("checked above").relocations = relocations; diff --git a/src/terminator/mod.rs b/src/terminator/mod.rs index 0c25dc3bed37..c4a8d2e73c29 100644 --- a/src/terminator/mod.rs +++ b/src/terminator/mod.rs @@ -9,7 +9,7 @@ use syntax::abi::Abi; use error::{EvalError, EvalResult}; use eval_context::{EvalContext, IntegerExt, StackPopCleanup, is_inhabited}; use lvalue::Lvalue; -use memory::{MemoryPointer, TlsKey}; +use memory::{MemoryPointer, TlsKey, Kind}; use value::{PrimVal, Value}; use rustc_data_structures::indexed_vec::Idx; @@ -558,7 +558,7 @@ impl<'a, 'tcx> EvalContext<'a, 'tcx> { if !align.is_power_of_two() { return Err(EvalError::HeapAllocNonPowerOfTwoAlignment(align)); } - let ptr = self.memory.allocate(size, align)?; + let ptr = self.memory.allocate(size, align, Kind::Rust)?; self.write_primval(dest, PrimVal::Ptr(ptr), dest_ty)?; } "alloc::heap::::__rust_alloc_zeroed" => { @@ -570,7 +570,7 @@ impl<'a, 'tcx> EvalContext<'a, 'tcx> { if !align.is_power_of_two() { return Err(EvalError::HeapAllocNonPowerOfTwoAlignment(align)); } - let ptr = self.memory.allocate(size, align)?; + let ptr = self.memory.allocate(size, align, Kind::Rust)?; self.memory.write_repeat(ptr.into(), 0, size)?; self.write_primval(dest, PrimVal::Ptr(ptr), dest_ty)?; } @@ -584,7 +584,7 @@ impl<'a, 'tcx> EvalContext<'a, 'tcx> { if !align.is_power_of_two() { return Err(EvalError::HeapAllocNonPowerOfTwoAlignment(align)); } - self.memory.deallocate(ptr, Some((old_size, align)))?; + self.memory.deallocate(ptr, Some((old_size, align)), Kind::Rust)?; } "alloc::heap::::__rust_realloc" => { let ptr = args[0].into_ptr(&mut self.memory)?.to_ptr()?; @@ -601,7 +601,7 @@ impl<'a, 'tcx> EvalContext<'a, 'tcx> { if !new_align.is_power_of_two() { return Err(EvalError::HeapAllocNonPowerOfTwoAlignment(new_align)); } - let new_ptr = self.memory.reallocate(ptr, old_size, old_align, new_size, new_align)?; + let new_ptr = self.memory.reallocate(ptr, old_size, old_align, new_size, new_align, Kind::Rust)?; self.write_primval(dest, PrimVal::Ptr(new_ptr), dest_ty)?; } @@ -657,7 +657,7 @@ impl<'a, 'tcx> EvalContext<'a, 'tcx> { self.write_null(dest, dest_ty)?; } else { let align = self.memory.pointer_size(); - let ptr = self.memory.allocate(size, align)?; + let ptr = self.memory.allocate(size, align, Kind::C)?; self.write_primval(dest, PrimVal::Ptr(ptr), dest_ty)?; } } @@ -665,7 +665,7 @@ impl<'a, 'tcx> EvalContext<'a, 'tcx> { "free" => { let ptr = args[0].into_ptr(&mut self.memory)?; if !ptr.is_null()? { - self.memory.deallocate(ptr.to_ptr()?, None)?; + self.memory.deallocate(ptr.to_ptr()?, None, Kind::C)?; } } @@ -789,7 +789,7 @@ impl<'a, 'tcx> EvalContext<'a, 'tcx> { } if let Some(old) = success { if let Some(var) = old { - self.memory.deallocate(var, None)?; + self.memory.deallocate(var, None, Kind::Env)?; } self.write_null(dest, dest_ty)?; } else { @@ -812,11 +812,11 @@ impl<'a, 'tcx> EvalContext<'a, 'tcx> { } if let Some((name, value)) = new { // +1 for the null terminator - let value_copy = self.memory.allocate((value.len() + 1) as u64, 1)?; + let value_copy = self.memory.allocate((value.len() + 1) as u64, 1, Kind::Env)?; self.memory.write_bytes(PrimVal::Ptr(value_copy), &value)?; self.memory.write_bytes(PrimVal::Ptr(value_copy.offset(value.len() as u64, self.memory.layout)?), &[0])?; if let Some(var) = self.env_vars.insert(name.to_owned(), value_copy) { - self.memory.deallocate(var, None)?; + self.memory.deallocate(var, None, Kind::Env)?; } self.write_null(dest, dest_ty)?; } else { diff --git a/src/traits.rs b/src/traits.rs index bfb923510bbb..6fbb973d7b14 100644 --- a/src/traits.rs +++ b/src/traits.rs @@ -1,7 +1,7 @@ use rustc::traits::{self, Reveal}; use eval_context::EvalContext; -use memory::MemoryPointer; +use memory::{MemoryPointer, Kind}; use value::{Value, PrimVal}; use rustc::hir::def_id::DefId; @@ -51,7 +51,7 @@ impl<'a, 'tcx> EvalContext<'a, 'tcx> { let ptr_size = self.memory.pointer_size(); let methods = ::rustc::traits::get_vtable_methods(self.tcx, trait_ref); - let vtable = self.memory.allocate(ptr_size * (3 + methods.count() as u64), ptr_size)?; + let vtable = self.memory.allocate(ptr_size * (3 + methods.count() as u64), ptr_size, Kind::Static)?; let drop = ::eval_context::resolve_drop_in_place(self.tcx, ty); let drop = self.memory.create_fn_alloc(drop); @@ -68,7 +68,7 @@ impl<'a, 'tcx> EvalContext<'a, 'tcx> { } } - self.memory.mark_static_initalized(vtable.alloc_id, false)?; + self.memory.mark_static_initalized(vtable.alloc_id, true)?; Ok(vtable) } diff --git a/tests/compile-fail/double_free.rs b/tests/compile-fail/double_free.rs new file mode 100644 index 000000000000..29ccf8c32131 --- /dev/null +++ b/tests/compile-fail/double_free.rs @@ -0,0 +1,8 @@ +fn main() { + let x = Box::new(42); + { + let bad_box: Box = unsafe { std::ptr::read(&x) }; + drop(bad_box); + } + drop(x); //~ ERROR dangling pointer was dereferenced +} diff --git a/tests/compile-fail/stack_free.rs b/tests/compile-fail/stack_free.rs new file mode 100644 index 000000000000..1828d809b208 --- /dev/null +++ b/tests/compile-fail/stack_free.rs @@ -0,0 +1,5 @@ +fn main() { + let x = 42; + let bad_box = unsafe { std::mem::transmute::<&i32, Box>(&x) }; + drop(bad_box); //~ ERROR tried to deallocate Stack memory but gave Rust as the kind +}