From 145cbf844cc876b05e29a0e08aa1d7a461d00ae9 Mon Sep 17 00:00:00 2001 From: Oliver Schneider Date: Wed, 21 Sep 2016 18:00:04 +0200 Subject: [PATCH 1/5] enable A -> A downcasting --- src/interpreter/mod.rs | 16 ++++++++++++++-- tests/run-pass/traits.rs | 5 +++++ 2 files changed, 19 insertions(+), 2 deletions(-) diff --git a/src/interpreter/mod.rs b/src/interpreter/mod.rs index 9338044e534..60f5a75d46c 100644 --- a/src/interpreter/mod.rs +++ b/src/interpreter/mod.rs @@ -627,6 +627,9 @@ impl<'a, 'tcx> EvalContext<'a, 'tcx> { let src_pointee_ty = pointee_type(src_ty).unwrap(); let dest_pointee_ty = pointee_type(dest_ty).unwrap(); + // A -> A conversion + let (src_pointee_ty, dest_pointee_ty) = self.tcx.struct_lockstep_tails(src_pointee_ty, dest_pointee_ty); + match (&src_pointee_ty.sty, &dest_pointee_ty.sty) { (&ty::TyArray(_, length), &ty::TySlice(_)) => { self.memory.write_usize(extra, length as u64)?; @@ -881,7 +884,7 @@ impl<'a, 'tcx> EvalContext<'a, 'tcx> { use rustc::mir::repr::ProjectionElem::*; match proj.elem { - Field(field, _) => { + Field(field, field_ty) => { use rustc::ty::layout::Layout::*; let variant = match *base_layout { Univariant { ref variant, .. } => variant, @@ -901,7 +904,15 @@ impl<'a, 'tcx> EvalContext<'a, 'tcx> { }; let offset = variant.field_offset(field.index()).bytes(); - base.ptr.offset(offset as isize) + let ptr = base.ptr.offset(offset as isize); + match (&field_ty.sty, base.extra) { + (&ty::TyTrait(_), extra @ LvalueExtra::Vtable(_)) => return Ok(Lvalue { + ptr: ptr, + extra: extra, + }), + (&ty::TyTrait(_), _) => bug!("trait field without vtable"), + _ => ptr, + } }, Downcast(_, variant) => { @@ -922,6 +933,7 @@ impl<'a, 'tcx> EvalContext<'a, 'tcx> { Deref => { let pointee_ty = pointee_type(base_ty).expect("Deref of non-pointer"); + let pointee_ty = self.tcx.struct_tail(pointee_ty); let ptr = self.memory.read_ptr(base.ptr)?; let extra = match pointee_ty.sty { ty::TySlice(_) | ty::TyStr => { diff --git a/tests/run-pass/traits.rs b/tests/run-pass/traits.rs index acef02d2bc8..e3d93957fd9 100644 --- a/tests/run-pass/traits.rs +++ b/tests/run-pass/traits.rs @@ -10,9 +10,14 @@ impl Trait for Struct { } } +struct Foo(T); + fn main() { let y: &Trait = &Struct(42); y.method(); + let x: Foo = Foo(Struct(42)); + let y: &Foo = &x; + y.0.method(); /* let x: Box i32> = Box::new(|x| x * 2); assert_eq!(x(21), 42); From 0f578f0d2ef1943d8a3a1123520eba90f926e757 Mon Sep 17 00:00:00 2001 From: Oliver Schneider Date: Thu, 22 Sep 2016 13:00:43 +0200 Subject: [PATCH 2/5] fully implement `size_of_val` and add various tests that now succeed --- src/interpreter/mod.rs | 3 + src/interpreter/terminator/intrinsics.rs | 128 ++++++++++++++++--- src/memory.rs | 2 +- tests/run-pass/cast-rfc0401-vtable-kinds.rs | 54 ++++++++ tests/run-pass/dst-irrefutable-bind.rs | 24 ++++ tests/run-pass/dst-raw.rs | 113 ++++++++++++++++ tests/run-pass/dst-struct-sole.rs | 85 ++++++++++++ tests/run-pass/issue-23261.rs | 70 ++++++++++ tests/run-pass/issue-36278-prefix-nesting.rs | 28 ++++ tests/run-pass/mir_fat_ptr.rs | 61 +++++++++ 10 files changed, 551 insertions(+), 17 deletions(-) create mode 100644 tests/run-pass/cast-rfc0401-vtable-kinds.rs create mode 100644 tests/run-pass/dst-irrefutable-bind.rs create mode 100644 tests/run-pass/dst-raw.rs create mode 100644 tests/run-pass/dst-struct-sole.rs create mode 100644 tests/run-pass/issue-23261.rs create mode 100644 tests/run-pass/issue-36278-prefix-nesting.rs create mode 100644 tests/run-pass/mir_fat_ptr.rs diff --git a/src/interpreter/mod.rs b/src/interpreter/mod.rs index a7e4407ce59..b590905264e 100644 --- a/src/interpreter/mod.rs +++ b/src/interpreter/mod.rs @@ -621,6 +621,7 @@ impl<'a, 'tcx> EvalContext<'a, 'tcx> { let src = self.eval_operand_to_ptr(operand)?; let src_ty = self.operand_ty(operand); let dest_ty = self.monomorphize(dest_ty, self.substs()); + // FIXME: cases where dest_ty is not a fat pointer. e.g. Arc -> Arc assert!(self.type_is_fat_ptr(dest_ty)); let (ptr, extra) = self.get_fat_ptr(dest); self.move_(src, ptr, src_ty)?; @@ -883,6 +884,8 @@ impl<'a, 'tcx> EvalContext<'a, 'tcx> { let offset = variant.field_offset(field.index()).bytes(); let ptr = base.ptr.offset(offset as isize); match (&field_ty.sty, base.extra) { + (&ty::TyStr, extra @ LvalueExtra::Length(_)) | + (&ty::TySlice(_), extra @ LvalueExtra::Length(_)) | (&ty::TyTrait(_), extra @ LvalueExtra::Vtable(_)) => return Ok(Lvalue { ptr: ptr, extra: extra, diff --git a/src/interpreter/terminator/intrinsics.rs b/src/interpreter/terminator/intrinsics.rs index e2381d59e2c..f7fa1588b56 100644 --- a/src/interpreter/terminator/intrinsics.rs +++ b/src/interpreter/terminator/intrinsics.rs @@ -188,22 +188,8 @@ impl<'a, 'tcx> EvalContext<'a, 'tcx> { "size_of_val" => { let ty = substs.type_at(0); - if self.type_is_sized(ty) { - let size = self.type_size(ty) as u64; - self.memory.write_uint(dest, size, pointer_size)?; - } else { - match ty.sty { - ty::TySlice(_) | ty::TyStr => { - let elem_ty = ty.sequence_element_type(self.tcx); - let elem_size = self.type_size(elem_ty) as u64; - let ptr_size = self.memory.pointer_size() as isize; - let n = self.memory.read_usize(args_ptrs[0].offset(ptr_size))?; - self.memory.write_uint(dest, n * elem_size, pointer_size)?; - } - - _ => return Err(EvalError::Unimplemented(format!("unimplemented: size_of_val::<{:?}>", ty))), - } - } + let (size, _) = self.size_and_align_of_dst(ty, args_ptrs[0])?; + self.memory.write_uint(dest, size, pointer_size)?; } // FIXME: wait for eval_operand_to_ptr to be gone /* @@ -248,4 +234,114 @@ impl<'a, 'tcx> EvalContext<'a, 'tcx> { // current frame. Ok(()) } + + fn size_and_align_of_dst( + &self, + ty: ty::Ty<'tcx>, + value: Pointer, + ) -> EvalResult<'tcx, (u64, u64)> { + let pointer_size = self.memory.pointer_size(); + if self.type_is_sized(ty) { + Ok((self.type_size(ty) as u64, self.type_align(ty) as u64)) + } else { + match ty.sty { + ty::TyAdt(def, substs) => { + // First get the size of all statically known fields. + // Don't use type_of::sizing_type_of because that expects t to be sized, + // and it also rounds up to alignment, which we want to avoid, + // as the unsized field's alignment could be smaller. + assert!(!ty.is_simd()); + let layout = self.type_layout(ty); + debug!("DST {} layout: {:?}", ty, layout); + + // Returns size in bytes of all fields except the last one + // (we will be recursing on the last one). + fn local_prefix_bytes(variant: &ty::layout::Struct) -> u64 { + let fields = variant.offset_after_field.len(); + if fields > 1 { + variant.offset_after_field[fields - 2].bytes() + } else { + 0 + } + } + + let (sized_size, sized_align) = match *layout { + ty::layout::Layout::Univariant { ref variant, .. } => { + (local_prefix_bytes(variant), variant.align.abi()) + } + _ => { + bug!("size_and_align_of_dst: expcted Univariant for `{}`, found {:#?}", + ty, layout); + } + }; + debug!("DST {} statically sized prefix size: {} align: {}", + ty, sized_size, sized_align); + + // Recurse to get the size of the dynamically sized field (must be + // the last field). + let last_field = def.struct_variant().fields.last().unwrap(); + let field_ty = self.field_ty(substs, last_field); + let (unsized_size, unsized_align) = self.size_and_align_of_dst(field_ty, value)?; + + // FIXME (#26403, #27023): We should be adding padding + // to `sized_size` (to accommodate the `unsized_align` + // required of the unsized field that follows) before + // summing it with `sized_size`. (Note that since #26403 + // is unfixed, we do not yet add the necessary padding + // here. But this is where the add would go.) + + // Return the sum of sizes and max of aligns. + let size = sized_size + unsized_size; + + // Choose max of two known alignments (combined value must + // be aligned according to more restrictive of the two). + let align = ::std::cmp::max(sized_align, unsized_align); + + // Issue #27023: must add any necessary padding to `size` + // (to make it a multiple of `align`) before returning it. + // + // Namely, the returned size should be, in C notation: + // + // `size + ((size & (align-1)) ? align : 0)` + // + // emulated via the semi-standard fast bit trick: + // + // `(size + (align-1)) & -align` + + if size & (align - 1) != 0 { + Ok((size + align, align)) + } else { + Ok((size, align)) + } + } + ty::TyTrait(..) => { + let (_, vtable) = self.get_fat_ptr(value); + let vtable = self.memory.read_ptr(vtable)?; + // the second entry in the vtable is the dynamic size of the object. + let size = self.memory.read_usize(vtable.offset(pointer_size as isize))?; + let align = self.memory.read_usize(vtable.offset(pointer_size as isize * 2))?; + Ok((size, align)) + } + + ty::TySlice(_) | ty::TyStr => { + let elem_ty = ty.sequence_element_type(self.tcx); + let elem_size = self.type_size(elem_ty) as u64; + let (_, len_ptr) = self.get_fat_ptr(value); + let n = self.memory.read_usize(len_ptr)?; + let align = self.type_align(elem_ty); + Ok((n * elem_size, align as u64)) + } + + _ => bug!("size_of_val::<{:?}>", ty), + } + } + } + /// Returns the normalized type of a struct field + fn field_ty( + &self, + param_substs: &Substs<'tcx>, + f: ty::FieldDef<'tcx>, + )-> ty::Ty<'tcx> { + self.tcx.normalize_associated_type(&f.ty(self.tcx, param_substs)) + } } diff --git a/src/memory.rs b/src/memory.rs index 0108f7e5d9c..980f9ca728b 100644 --- a/src/memory.rs +++ b/src/memory.rs @@ -120,7 +120,7 @@ impl<'a, 'tcx> Memory<'a, 'tcx> { bytes: Vec::new(), relocations: BTreeMap::new(), undef_mask: UndefMask::new(0), - align: 1, + align: 8, // should be infinity? immutable: false, // must be mutable, because sometimes we "move out" of a ZST }; mem.alloc_map.insert(ZST_ALLOC_ID, alloc); diff --git a/tests/run-pass/cast-rfc0401-vtable-kinds.rs b/tests/run-pass/cast-rfc0401-vtable-kinds.rs new file mode 100644 index 00000000000..3a9f24ad4cc --- /dev/null +++ b/tests/run-pass/cast-rfc0401-vtable-kinds.rs @@ -0,0 +1,54 @@ +// Copyright 2015 The Rust Project Developers. See the COPYRIGHT +// file at the top-level directory of this distribution and at +// http://rust-lang.org/COPYRIGHT. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +// Check that you can cast between different pointers to trait objects +// whose vtable have the same kind (both lengths, or both trait pointers). + +trait Foo { + fn foo(&self, _: T) -> u32 { 42 } +} + +trait Bar { + fn bar(&self) { println!("Bar!"); } +} + +impl Foo for () {} +impl Foo for u32 { fn foo(&self, _: u32) -> u32 { self+43 } } +impl Bar for () {} + +unsafe fn round_trip_and_call<'a>(t: *const (Foo+'a)) -> u32 { + let foo_e : *const Foo = t as *const _; + let r_1 = foo_e as *mut Foo; + + (&*r_1).foo(0) +} + +#[repr(C)] +struct FooS(T); +#[repr(C)] +struct BarS(T); + +fn foo_to_bar(u: *const FooS) -> *const BarS { + u as *const BarS +} + +fn main() { + let x = 4u32; + let y : &Foo = &x; + let fl = unsafe { round_trip_and_call(y as *const Foo) }; + assert_eq!(fl, (43+4)); + + let s = FooS([0,1,2]); + let u: &FooS<[u32]> = &s; + let u: *const FooS<[u32]> = u; + let bar_ref : *const BarS<[u32]> = foo_to_bar(u); + let z : &BarS<[u32]> = unsafe{&*bar_ref}; + assert_eq!(&z.0, &[0,1,2]); +} diff --git a/tests/run-pass/dst-irrefutable-bind.rs b/tests/run-pass/dst-irrefutable-bind.rs new file mode 100644 index 00000000000..9f8067f372a --- /dev/null +++ b/tests/run-pass/dst-irrefutable-bind.rs @@ -0,0 +1,24 @@ +// Copyright 2015 The Rust Project Developers. See the COPYRIGHT +// file at the top-level directory of this distribution and at +// http://rust-lang.org/COPYRIGHT. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +struct Test(T); + +fn main() { + let x = Test([1,2,3]); + let x : &Test<[i32]> = &x; + + let & ref _y = x; + + // Make sure binding to a fat pointer behind a reference + // still works + let slice = &[1,2,3]; + let x = Test(&slice); + let Test(&_slice) = x; +} diff --git a/tests/run-pass/dst-raw.rs b/tests/run-pass/dst-raw.rs new file mode 100644 index 00000000000..3a74626b029 --- /dev/null +++ b/tests/run-pass/dst-raw.rs @@ -0,0 +1,113 @@ +// Copyright 2014 The Rust Project Developers. See the COPYRIGHT +// file at the top-level directory of this distribution and at +// http://rust-lang.org/COPYRIGHT. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +// Test DST raw pointers + + +trait Trait { + fn foo(&self) -> isize; +} + +struct A { + f: isize +} +impl Trait for A { + fn foo(&self) -> isize { + self.f + } +} + +struct Foo { + f: T +} + +pub fn main() { + // raw trait object + let x = A { f: 42 }; + let z: *const Trait = &x; + let r = unsafe { + (&*z).foo() + }; + assert_eq!(r, 42); + + // raw DST struct + let p = Foo {f: A { f: 42 }}; + let o: *const Foo = &p; + let r = unsafe { + (&*o).f.foo() + }; + assert_eq!(r, 42); + + // raw slice + let a: *const [_] = &[1, 2, 3]; + unsafe { + let b = (*a)[2]; + assert_eq!(b, 3); + let len = (*a).len(); + assert_eq!(len, 3); + } + + // raw slice with explicit cast + let a = &[1, 2, 3] as *const [i32]; + unsafe { + let b = (*a)[2]; + assert_eq!(b, 3); + let len = (*a).len(); + assert_eq!(len, 3); + } + + // raw DST struct with slice + let c: *const Foo<[_]> = &Foo {f: [1, 2, 3]}; + unsafe { + let b = (&*c).f[0]; + assert_eq!(b, 1); + let len = (&*c).f.len(); + assert_eq!(len, 3); + } + + // all of the above with *mut + let mut x = A { f: 42 }; + let z: *mut Trait = &mut x; + let r = unsafe { + (&*z).foo() + }; + assert_eq!(r, 42); + + let mut p = Foo {f: A { f: 42 }}; + let o: *mut Foo = &mut p; + let r = unsafe { + (&*o).f.foo() + }; + assert_eq!(r, 42); + + let a: *mut [_] = &mut [1, 2, 3]; + unsafe { + let b = (*a)[2]; + assert_eq!(b, 3); + let len = (*a).len(); + assert_eq!(len, 3); + } + + let a = &mut [1, 2, 3] as *mut [i32]; + unsafe { + let b = (*a)[2]; + assert_eq!(b, 3); + let len = (*a).len(); + assert_eq!(len, 3); + } + + let c: *mut Foo<[_]> = &mut Foo {f: [1, 2, 3]}; + unsafe { + let b = (&*c).f[0]; + assert_eq!(b, 1); + let len = (&*c).f.len(); + assert_eq!(len, 3); + } +} diff --git a/tests/run-pass/dst-struct-sole.rs b/tests/run-pass/dst-struct-sole.rs new file mode 100644 index 00000000000..58d7b35a527 --- /dev/null +++ b/tests/run-pass/dst-struct-sole.rs @@ -0,0 +1,85 @@ +// Copyright 2014 The Rust Project Developers. See the COPYRIGHT +// file at the top-level directory of this distribution and at +// http://rust-lang.org/COPYRIGHT. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +// As dst-struct.rs, but the unsized field is the only field in the struct. + + +struct Fat { + ptr: T +} + +// x is a fat pointer +fn foo(x: &Fat<[isize]>) { + let y = &x.ptr; + assert_eq!(x.ptr.len(), 3); + assert_eq!(y[0], 1); + assert_eq!(x.ptr[1], 2); +} + +fn foo2(x: &Fat<[T]>) { + let y = &x.ptr; + let bar = Bar; + assert_eq!(x.ptr.len(), 3); + assert_eq!(y[0].to_bar(), bar); + assert_eq!(x.ptr[1].to_bar(), bar); +} + +#[derive(Copy, Clone, PartialEq, Eq, Debug)] +struct Bar; + +trait ToBar { + fn to_bar(&self) -> Bar; +} + +impl ToBar for Bar { + fn to_bar(&self) -> Bar { + *self + } +} + +pub fn main() { + // With a vec of ints. + let f1 = Fat { ptr: [1, 2, 3] }; + foo(&f1); + let f2 = &f1; + foo(f2); + let f3: &Fat<[isize]> = f2; + foo(f3); + let f4: &Fat<[isize]> = &f1; + foo(f4); + let f5: &Fat<[isize]> = &Fat { ptr: [1, 2, 3] }; + foo(f5); + + // With a vec of Bars. + let bar = Bar; + let f1 = Fat { ptr: [bar, bar, bar] }; + foo2(&f1); + let f2 = &f1; + foo2(f2); + let f3: &Fat<[Bar]> = f2; + foo2(f3); + let f4: &Fat<[Bar]> = &f1; + foo2(f4); + let f5: &Fat<[Bar]> = &Fat { ptr: [bar, bar, bar] }; + foo2(f5); + + // Assignment. + let f5: &mut Fat<[isize]> = &mut Fat { ptr: [1, 2, 3] }; + f5.ptr[1] = 34; + assert_eq!(f5.ptr[0], 1); + assert_eq!(f5.ptr[1], 34); + assert_eq!(f5.ptr[2], 3); + + // Zero size vec. + let f5: &Fat<[isize]> = &Fat { ptr: [] }; + assert!(f5.ptr.is_empty()); + let f5: &Fat<[Bar]> = &Fat { ptr: [] }; + assert!(f5.ptr.is_empty()); +} diff --git a/tests/run-pass/issue-23261.rs b/tests/run-pass/issue-23261.rs new file mode 100644 index 00000000000..fc806f5429a --- /dev/null +++ b/tests/run-pass/issue-23261.rs @@ -0,0 +1,70 @@ +// Copyright 2015 The Rust Project Developers. See the COPYRIGHT +// file at the top-level directory of this distribution and at +// http://rust-lang.org/COPYRIGHT. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +// Matching on a DST struct should not trigger an LLVM assertion. + +struct Foo { + a: i32, + inner: T +} + +trait Get { + fn get(&self) -> i32; +} + +impl Get for i32 { + fn get(&self) -> i32 { + *self + } +} + +fn check_val(val: &Foo<[u8]>) { + match *val { + Foo { a, .. } => { + assert_eq!(a, 32); + } + } +} + +fn check_dst_val(val: &Foo<[u8]>) { + match *val { + Foo { ref inner, .. } => { + assert_eq!(inner, [1, 2, 3]); + } + } +} + +fn check_both(val: &Foo<[u8]>) { + match *val { + Foo { a, ref inner } => { + assert_eq!(a, 32); + assert_eq!(inner, [1, 2, 3]); + } + } +} + +fn check_trait_obj(val: &Foo) { + match *val { + Foo { a, ref inner } => { + assert_eq!(a, 32); + assert_eq!(inner.get(), 32); + } + } +} + +fn main() { + let foo: &Foo<[u8]> = &Foo { a: 32, inner: [1, 2, 3] }; + check_val(foo); + check_dst_val(foo); + check_both(foo); + + let foo: &Foo = &Foo { a: 32, inner: 32 }; + check_trait_obj(foo); +} diff --git a/tests/run-pass/issue-36278-prefix-nesting.rs b/tests/run-pass/issue-36278-prefix-nesting.rs new file mode 100644 index 00000000000..95269d0569d --- /dev/null +++ b/tests/run-pass/issue-36278-prefix-nesting.rs @@ -0,0 +1,28 @@ +// Copyright 2016 The Rust Project Developers. See the COPYRIGHT +// file at the top-level directory of this distribution and at +// http://rust-lang.org/COPYRIGHT. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +// Issue 36278: On an unsized struct with >1 level of nontrivial +// nesting, ensure we are computing dynamic size of prefix correctly. + +use std::mem; + +const SZ: usize = 100; +struct P([u8; SZ], T); + +type Ack = P>; + +fn main() { + let size_of_sized; let size_of_unsized; + let x: Box> = Box::new(P([0; SZ], P([0; SZ], [0; 0]))); + size_of_sized = mem::size_of_val::>(&x); + let y: Box> = x; + size_of_unsized = mem::size_of_val::>(&y); + assert_eq!(size_of_sized, size_of_unsized); +} diff --git a/tests/run-pass/mir_fat_ptr.rs b/tests/run-pass/mir_fat_ptr.rs new file mode 100644 index 00000000000..e5c9e3577d1 --- /dev/null +++ b/tests/run-pass/mir_fat_ptr.rs @@ -0,0 +1,61 @@ +// Copyright 2015 The Rust Project Developers. See the COPYRIGHT +// file at the top-level directory of this distribution and at +// http://rust-lang.org/COPYRIGHT. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +// test that ordinary fat pointer operations work. + +struct Wrapper(u32, T); + +struct FatPtrContainer<'a> { + ptr: &'a [u8] +} + +fn fat_ptr_project(a: &Wrapper<[u8]>) -> &[u8] { + &a.1 +} + +fn fat_ptr_simple(a: &[u8]) -> &[u8] { + a +} + +fn fat_ptr_via_local(a: &[u8]) -> &[u8] { + let x = a; + x +} + +fn fat_ptr_from_struct(s: FatPtrContainer) -> &[u8] { + s.ptr +} + +fn fat_ptr_to_struct(a: &[u8]) -> FatPtrContainer { + FatPtrContainer { ptr: a } +} + +fn fat_ptr_store_to<'a>(a: &'a [u8], b: &mut &'a [u8]) { + *b = a; +} + +fn fat_ptr_constant() -> &'static str { + "HELLO" +} + +fn main() { + let a = Wrapper(4, [7,6,5]); + + let p = fat_ptr_project(&a); + let p = fat_ptr_simple(p); + let p = fat_ptr_via_local(p); + let p = fat_ptr_from_struct(fat_ptr_to_struct(p)); + + let mut target : &[u8] = &[42]; + fat_ptr_store_to(p, &mut target); + assert_eq!(target, &a.1); + + assert_eq!(fat_ptr_constant(), "HELLO"); +} From 0690a26ddfb762c7fb7906326f41a7e470728654 Mon Sep 17 00:00:00 2001 From: Oliver Schneider Date: Thu, 22 Sep 2016 13:01:08 +0200 Subject: [PATCH 3/5] make Memory::dump use `trace!` instead of `println!` --- src/memory.rs | 24 +++++++++++++----------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/src/memory.rs b/src/memory.rs index 980f9ca728b..ae590b0eef1 100644 --- a/src/memory.rs +++ b/src/memory.rs @@ -345,25 +345,26 @@ impl<'a, 'tcx> Memory<'a, 'tcx> { /// Print an allocation and all allocations it points to, recursively. pub fn dump(&self, id: AllocId) { + use std::fmt::Write; let mut allocs_seen = HashSet::new(); let mut allocs_to_print = VecDeque::new(); allocs_to_print.push_back(id); while let Some(id) = allocs_to_print.pop_front() { allocs_seen.insert(id); - let prefix = format!("Alloc {:<5} ", format!("{}:", id)); - print!("{}", prefix); + let mut msg = format!("Alloc {:<5} ", format!("{}:", id)); + let prefix_len = msg.len(); let mut relocations = vec![]; let alloc = match (self.alloc_map.get(&id), self.functions.get(&id)) { (Some(a), None) => a, (None, Some(_)) => { // FIXME: print function name - println!("function pointer"); + trace!("{} function pointer", msg); continue; }, (None, None) => { - println!("(deallocated)"); + trace!("{} (deallocated)", msg); continue; }, (Some(_), Some(_)) => bug!("miri invariant broken: an allocation id exists that points to both a function and a memory location"), @@ -377,25 +378,26 @@ impl<'a, 'tcx> Memory<'a, 'tcx> { relocations.push((i, target_id)); } if alloc.undef_mask.is_range_defined(i, i + 1) { - print!("{:02x} ", alloc.bytes[i]); + write!(msg, "{:02x} ", alloc.bytes[i]).unwrap(); } else { - print!("__ "); + msg.push_str("__ "); } } let immutable = if alloc.immutable { " (immutable)" } else { "" }; - println!("({} bytes){}", alloc.bytes.len(), immutable); + trace!("{}({} bytes){}", msg, alloc.bytes.len(), immutable); if !relocations.is_empty() { - print!("{:1$}", "", prefix.len()); // Print spaces. + msg.clear(); + write!(msg, "{:1$}", "", prefix_len).unwrap(); // Print spaces. let mut pos = 0; let relocation_width = (self.pointer_size() - 1) * 3; for (i, target_id) in relocations { - print!("{:1$}", "", (i - pos) * 3); - print!("└{0:─^1$}┘ ", format!("({})", target_id), relocation_width); + write!(msg, "{:1$}", "", (i - pos) * 3).unwrap(); + write!(msg, "└{0:─^1$}┘ ", format!("({})", target_id), relocation_width).unwrap(); pos = i + self.pointer_size(); } - println!(""); + trace!("{}", msg); } } } From 875a4542f9eeace01f1ee5ec5fd25904601f58c5 Mon Sep 17 00:00:00 2001 From: Oliver Schneider Date: Thu, 22 Sep 2016 15:22:00 +0200 Subject: [PATCH 4/5] remove the ZST allocation and abort all zero byte writes/reads --- src/error.rs | 3 +++ src/memory.rs | 45 +++++++++++++++++++++++---------------- tests/compile-fail/zst.rs | 4 ++++ 3 files changed, 34 insertions(+), 18 deletions(-) create mode 100644 tests/compile-fail/zst.rs diff --git a/src/error.rs b/src/error.rs index 5624734e888..82b4eff6e92 100644 --- a/src/error.rs +++ b/src/error.rs @@ -10,6 +10,7 @@ use syntax::codemap::Span; pub enum EvalError<'tcx> { FunctionPointerTyMismatch(&'tcx BareFnTy<'tcx>, &'tcx BareFnTy<'tcx>), DanglingPointerDeref, + ZstAllocAccess, InvalidFunctionPointer, InvalidBool, InvalidDiscriminant, @@ -53,6 +54,8 @@ impl<'tcx> Error for EvalError<'tcx> { match *self { EvalError::FunctionPointerTyMismatch(..) => "tried to call a function through a function pointer of a different type", + EvalError::ZstAllocAccess => + "tried to access the ZST allocation", EvalError::DanglingPointerDeref => "dangling pointer was dereferenced", EvalError::InvalidFunctionPointer => diff --git a/src/memory.rs b/src/memory.rs index ae590b0eef1..53c50e45e85 100644 --- a/src/memory.rs +++ b/src/memory.rs @@ -105,7 +105,7 @@ const ZST_ALLOC_ID: AllocId = AllocId(0); impl<'a, 'tcx> Memory<'a, 'tcx> { pub fn new(layout: &'a TargetDataLayout, max_memory: usize) -> Self { - let mut mem = Memory { + Memory { alloc_map: HashMap::new(), functions: HashMap::new(), function_alloc_cache: HashMap::new(), @@ -113,21 +113,7 @@ impl<'a, 'tcx> Memory<'a, 'tcx> { layout: layout, memory_size: max_memory, memory_usage: 0, - }; - // alloc id 0 is reserved for ZSTs, this is an optimization to prevent ZST - // (e.g. function items, (), [], ...) from requiring memory - let alloc = Allocation { - bytes: Vec::new(), - relocations: BTreeMap::new(), - undef_mask: UndefMask::new(0), - align: 8, // should be infinity? - immutable: false, // must be mutable, because sometimes we "move out" of a ZST - }; - mem.alloc_map.insert(ZST_ALLOC_ID, alloc); - // check that additional zst allocs work - debug_assert!(mem.allocate(0, 1).unwrap().points_to_zst()); - debug_assert!(mem.get(ZST_ALLOC_ID).is_ok()); - mem + } } pub fn allocations(&self) -> ::std::collections::hash_map::Iter { @@ -293,6 +279,7 @@ impl<'a, 'tcx> Memory<'a, 'tcx> { Some(alloc) => Ok(alloc), None => match self.functions.get(&id) { Some(_) => Err(EvalError::DerefFunctionPointer), + None if id == ZST_ALLOC_ID => Err(EvalError::ZstAllocAccess), None => Err(EvalError::DanglingPointerDeref), } } @@ -304,6 +291,7 @@ impl<'a, 'tcx> Memory<'a, 'tcx> { Some(alloc) => Ok(alloc), None => match self.functions.get(&id) { Some(_) => Err(EvalError::DerefFunctionPointer), + None if id == ZST_ALLOC_ID => Err(EvalError::ZstAllocAccess), None => Err(EvalError::DanglingPointerDeref), } } @@ -353,6 +341,10 @@ impl<'a, 'tcx> Memory<'a, 'tcx> { while let Some(id) = allocs_to_print.pop_front() { allocs_seen.insert(id); let mut msg = format!("Alloc {:<5} ", format!("{}:", id)); + if id == ZST_ALLOC_ID { + trace!("{} zst allocation", msg); + continue; + } let prefix_len = msg.len(); let mut relocations = vec![]; @@ -406,6 +398,9 @@ impl<'a, 'tcx> Memory<'a, 'tcx> { /// Byte accessors impl<'a, 'tcx> Memory<'a, 'tcx> { fn get_bytes_unchecked(&self, ptr: Pointer, size: usize) -> EvalResult<'tcx, &[u8]> { + if size == 0 { + return Ok(&[]); + } let alloc = self.get(ptr.alloc_id)?; if ptr.offset + size > alloc.bytes.len() { return Err(EvalError::PointerOutOfBounds { @@ -418,6 +413,9 @@ impl<'a, 'tcx> Memory<'a, 'tcx> { } fn get_bytes_unchecked_mut(&mut self, ptr: Pointer, size: usize) -> EvalResult<'tcx, &mut [u8]> { + if size == 0 { + return Ok(&mut []); + } let alloc = self.get_mut(ptr.alloc_id)?; if ptr.offset + size > alloc.bytes.len() { return Err(EvalError::PointerOutOfBounds { @@ -430,6 +428,9 @@ impl<'a, 'tcx> Memory<'a, 'tcx> { } fn get_bytes(&self, ptr: Pointer, size: usize, align: usize) -> EvalResult<'tcx, &[u8]> { + if size == 0 { + return Ok(&[]); + } self.check_align(ptr, align)?; if self.relocations(ptr, size)?.count() != 0 { return Err(EvalError::ReadPointerAsBytes); @@ -439,6 +440,9 @@ impl<'a, 'tcx> Memory<'a, 'tcx> { } fn get_bytes_mut(&mut self, ptr: Pointer, size: usize, align: usize) -> EvalResult<'tcx, &mut [u8]> { + if size == 0 { + return Ok(&mut []); + } self.check_align(ptr, align)?; self.clear_relocations(ptr, size)?; self.mark_definedness(ptr, size, true)?; @@ -449,8 +453,7 @@ impl<'a, 'tcx> Memory<'a, 'tcx> { /// Reading and writing impl<'a, 'tcx> Memory<'a, 'tcx> { pub fn freeze(&mut self, alloc_id: AllocId) -> EvalResult<'tcx, ()> { - // Never freeze the zero-sized allocation. If you do that, then getting a mutable handle to - // _any_ ZST becomes an error, since they all share the same allocation. + // It's not possible to freeze the zero-sized allocation, because it doesn't exist. if alloc_id != ZST_ALLOC_ID { self.get_mut(alloc_id)?.immutable = true; } @@ -458,6 +461,9 @@ impl<'a, 'tcx> Memory<'a, 'tcx> { } pub fn copy(&mut self, src: Pointer, dest: Pointer, size: usize, align: usize) -> EvalResult<'tcx, ()> { + if size == 0 { + return Ok(()); + } self.check_relocation_edges(src, size)?; let src_bytes = self.get_bytes_unchecked(src, size)?.as_ptr(); @@ -714,6 +720,9 @@ impl<'a, 'tcx> Memory<'a, 'tcx> { pub fn mark_definedness(&mut self, ptr: Pointer, size: usize, new_state: bool) -> EvalResult<'tcx, ()> { + if size == 0 { + return Ok(()) + } let mut alloc = self.get_mut(ptr.alloc_id)?; alloc.undef_mask.set_range(ptr.offset, ptr.offset + size, new_state); Ok(()) diff --git a/tests/compile-fail/zst.rs b/tests/compile-fail/zst.rs new file mode 100644 index 00000000000..a244befed01 --- /dev/null +++ b/tests/compile-fail/zst.rs @@ -0,0 +1,4 @@ +fn main() { + let x = &() as *const () as *const i32; + let _ = unsafe { *x }; //~ ERROR: tried to access the ZST allocation +} From 38748fa6150a99aaa16902ecd081a38bcfb92630 Mon Sep 17 00:00:00 2001 From: Oliver Schneider Date: Thu, 22 Sep 2016 15:47:16 +0200 Subject: [PATCH 5/5] refactor away IntegerPtr --- src/error.rs | 9 +++------ src/interpreter/cast.rs | 5 ++--- src/interpreter/mod.rs | 8 +------- src/interpreter/terminator/intrinsics.rs | 15 +++------------ src/memory.rs | 14 +++++++++----- src/primval.rs | 9 +-------- tests/compile-fail/null_pointer_deref.rs | 2 +- tests/compile-fail/wild_pointer_deref.rs | 2 +- tests/compile-fail/zst.rs | 2 +- tests/run-pass/zst.rs | 2 ++ 10 files changed, 24 insertions(+), 44 deletions(-) diff --git a/src/error.rs b/src/error.rs index 82b4eff6e92..fde3db15968 100644 --- a/src/error.rs +++ b/src/error.rs @@ -10,7 +10,7 @@ use syntax::codemap::Span; pub enum EvalError<'tcx> { FunctionPointerTyMismatch(&'tcx BareFnTy<'tcx>, &'tcx BareFnTy<'tcx>), DanglingPointerDeref, - ZstAllocAccess, + InvalidMemoryAccess, InvalidFunctionPointer, InvalidBool, InvalidDiscriminant, @@ -20,7 +20,6 @@ pub enum EvalError<'tcx> { allocation_size: usize, }, ReadPointerAsBytes, - ReadBytesAsPointer, InvalidPointerMath, ReadUndefBytes, InvalidBoolOp(mir::BinOp), @@ -54,8 +53,8 @@ impl<'tcx> Error for EvalError<'tcx> { match *self { EvalError::FunctionPointerTyMismatch(..) => "tried to call a function through a function pointer of a different type", - EvalError::ZstAllocAccess => - "tried to access the ZST allocation", + EvalError::InvalidMemoryAccess => + "tried to access memory through an invalid pointer", EvalError::DanglingPointerDeref => "dangling pointer was dereferenced", EvalError::InvalidFunctionPointer => @@ -68,8 +67,6 @@ impl<'tcx> Error for EvalError<'tcx> { "pointer offset outside bounds of allocation", EvalError::ReadPointerAsBytes => "a raw memory access tried to access part of a pointer value as raw bytes", - EvalError::ReadBytesAsPointer => - "attempted to interpret some raw bytes as a pointer address", EvalError::InvalidPointerMath => "attempted to do math or a comparison on pointers into different allocations", EvalError::ReadUndefBytes => diff --git a/src/interpreter/cast.rs b/src/interpreter/cast.rs index 06cbf2495da..6227999569c 100644 --- a/src/interpreter/cast.rs +++ b/src/interpreter/cast.rs @@ -25,8 +25,7 @@ impl<'a, 'tcx> EvalContext<'a, 'tcx> { U16(u) => self.cast_const_int(u as u64, ty, false), U32(u) => self.cast_const_int(u as u64, ty, false), Char(c) => self.cast_const_int(c as u64, ty, false), - U64(u) | - IntegerPtr(u) => self.cast_const_int(u, ty, false), + U64(u) => self.cast_const_int(u, ty, false), FnPtr(ptr) | Ptr(ptr) => self.cast_ptr(ptr, ty), } @@ -74,7 +73,7 @@ impl<'a, 'tcx> EvalContext<'a, 'tcx> { ty::TyFloat(ast::FloatTy::F64) => Ok(F64(v as f64)), ty::TyFloat(ast::FloatTy::F32) if negative => Ok(F32(v as i64 as f32)), ty::TyFloat(ast::FloatTy::F32) => Ok(F32(v as f32)), - ty::TyRawPtr(_) => Ok(IntegerPtr(v)), + ty::TyRawPtr(_) => Ok(Ptr(Pointer::from_int(v as usize))), ty::TyChar if v as u8 as u64 == v => Ok(Char(v as u8 as char)), ty::TyChar => Err(EvalError::InvalidChar(v)), _ => Err(EvalError::Unimplemented(format!("int to {:?} cast", ty))), diff --git a/src/interpreter/mod.rs b/src/interpreter/mod.rs index b590905264e..9de72dc6225 100644 --- a/src/interpreter/mod.rs +++ b/src/interpreter/mod.rs @@ -1064,13 +1064,7 @@ impl<'a, 'tcx> EvalContext<'a, 'tcx> { &ty::TyRef(_, ty::TypeAndMut { ty, .. }) | &ty::TyRawPtr(ty::TypeAndMut { ty, .. }) => { if self.type_is_sized(ty) { - match self.memory.read_ptr(ptr) { - Ok(p) => PrimVal::Ptr(p), - Err(EvalError::ReadBytesAsPointer) => { - PrimVal::IntegerPtr(self.memory.read_usize(ptr)?) - } - Err(e) => return Err(e), - } + PrimVal::Ptr(self.memory.read_ptr(ptr)?) } else { bug!("primitive read of fat pointer type: {:?}", ty); } diff --git a/src/interpreter/terminator/intrinsics.rs b/src/interpreter/terminator/intrinsics.rs index f7fa1588b56..38b62254130 100644 --- a/src/interpreter/terminator/intrinsics.rs +++ b/src/interpreter/terminator/intrinsics.rs @@ -132,18 +132,9 @@ impl<'a, 'tcx> EvalContext<'a, 'tcx> { let ptr_arg = args_ptrs[0]; let offset = self.memory.read_isize(args_ptrs[1])?; - match self.memory.read_ptr(ptr_arg) { - Ok(ptr) => { - let result_ptr = ptr.offset(offset as isize * pointee_size); - self.memory.write_ptr(dest, result_ptr)?; - } - Err(EvalError::ReadBytesAsPointer) => { - let addr = self.memory.read_isize(ptr_arg)?; - let result_addr = addr + offset * pointee_size as i64; - self.memory.write_isize(dest, result_addr)?; - } - Err(e) => return Err(e), - } + let ptr = self.memory.read_ptr(ptr_arg)?; + let result_ptr = ptr.offset(offset as isize * pointee_size); + self.memory.write_ptr(dest, result_ptr)?; } "overflowing_sub" => { diff --git a/src/memory.rs b/src/memory.rs index 53c50e45e85..27e6ffff00e 100644 --- a/src/memory.rs +++ b/src/memory.rs @@ -55,6 +55,12 @@ impl Pointer { pub fn points_to_zst(&self) -> bool { self.alloc_id == ZST_ALLOC_ID } + pub fn from_int(i: usize) -> Self { + Pointer { + alloc_id: ZST_ALLOC_ID, + offset: i, + } + } fn zst_ptr() -> Self { Pointer { alloc_id: ZST_ALLOC_ID, @@ -279,7 +285,7 @@ impl<'a, 'tcx> Memory<'a, 'tcx> { Some(alloc) => Ok(alloc), None => match self.functions.get(&id) { Some(_) => Err(EvalError::DerefFunctionPointer), - None if id == ZST_ALLOC_ID => Err(EvalError::ZstAllocAccess), + None if id == ZST_ALLOC_ID => Err(EvalError::InvalidMemoryAccess), None => Err(EvalError::DanglingPointerDeref), } } @@ -291,7 +297,7 @@ impl<'a, 'tcx> Memory<'a, 'tcx> { Some(alloc) => Ok(alloc), None => match self.functions.get(&id) { Some(_) => Err(EvalError::DerefFunctionPointer), - None if id == ZST_ALLOC_ID => Err(EvalError::ZstAllocAccess), + None if id == ZST_ALLOC_ID => Err(EvalError::InvalidMemoryAccess), None => Err(EvalError::DanglingPointerDeref), } } @@ -511,7 +517,7 @@ impl<'a, 'tcx> Memory<'a, 'tcx> { let alloc = self.get(ptr.alloc_id)?; match alloc.relocations.get(&ptr.offset) { Some(&alloc_id) => Ok(Pointer { alloc_id: alloc_id, offset: offset }), - None => Err(EvalError::ReadBytesAsPointer), + None => Ok(Pointer::from_int(offset)), } } @@ -522,7 +528,6 @@ impl<'a, 'tcx> Memory<'a, 'tcx> { } pub fn write_primval(&mut self, ptr: Pointer, val: PrimVal) -> EvalResult<'tcx, ()> { - let pointer_size = self.pointer_size(); match val { PrimVal::Bool(b) => self.write_bool(ptr, b), PrimVal::I8(n) => self.write_int(ptr, n as i64, 1), @@ -534,7 +539,6 @@ impl<'a, 'tcx> Memory<'a, 'tcx> { PrimVal::U32(n) => self.write_uint(ptr, n as u64, 4), PrimVal::U64(n) => self.write_uint(ptr, n as u64, 8), PrimVal::Char(c) => self.write_uint(ptr, c as u64, 4), - PrimVal::IntegerPtr(n) => self.write_uint(ptr, n as u64, pointer_size), PrimVal::F32(f) => self.write_f32(ptr, f), PrimVal::F64(f) => self.write_f64(ptr, f), PrimVal::FnPtr(p) | diff --git a/src/primval.rs b/src/primval.rs index 50ef05a245d..717ad99dbcd 100644 --- a/src/primval.rs +++ b/src/primval.rs @@ -14,7 +14,6 @@ pub enum PrimVal { Ptr(Pointer), FnPtr(Pointer), - IntegerPtr(u64), Char(char), F32(f32), F64(f64), @@ -209,14 +208,8 @@ pub fn binary_op<'tcx>(bin_op: mir::BinOp, left: PrimVal, right: PrimVal) -> Eva }) } - (IntegerPtr(l), IntegerPtr(r)) => int_binops!(IntegerPtr, l, r), - - (Ptr(_), IntegerPtr(_)) | - (IntegerPtr(_), Ptr(_)) | (FnPtr(_), Ptr(_)) | - (Ptr(_), FnPtr(_)) | - (FnPtr(_), IntegerPtr(_)) | - (IntegerPtr(_), FnPtr(_)) => + (Ptr(_), FnPtr(_)) => unrelated_ptr_ops(bin_op)?, (FnPtr(l_ptr), FnPtr(r_ptr)) => match bin_op { diff --git a/tests/compile-fail/null_pointer_deref.rs b/tests/compile-fail/null_pointer_deref.rs index 3d1afe92156..fcf34ed44c9 100644 --- a/tests/compile-fail/null_pointer_deref.rs +++ b/tests/compile-fail/null_pointer_deref.rs @@ -1,4 +1,4 @@ fn main() { - let x: i32 = unsafe { *std::ptr::null() }; //~ ERROR: attempted to interpret some raw bytes as a pointer address + let x: i32 = unsafe { *std::ptr::null() }; //~ ERROR: tried to access memory through an invalid pointer panic!("this should never print: {}", x); } diff --git a/tests/compile-fail/wild_pointer_deref.rs b/tests/compile-fail/wild_pointer_deref.rs index 1f472489b4f..937546fdc35 100644 --- a/tests/compile-fail/wild_pointer_deref.rs +++ b/tests/compile-fail/wild_pointer_deref.rs @@ -1,5 +1,5 @@ fn main() { let p = 42 as *const i32; - let x = unsafe { *p }; //~ ERROR: attempted to interpret some raw bytes as a pointer address + let x = unsafe { *p }; //~ ERROR: tried to access memory through an invalid pointer panic!("this should never print: {}", x); } diff --git a/tests/compile-fail/zst.rs b/tests/compile-fail/zst.rs index a244befed01..970cc9abc9d 100644 --- a/tests/compile-fail/zst.rs +++ b/tests/compile-fail/zst.rs @@ -1,4 +1,4 @@ fn main() { let x = &() as *const () as *const i32; - let _ = unsafe { *x }; //~ ERROR: tried to access the ZST allocation + let _ = unsafe { *x }; //~ ERROR: tried to access memory through an invalid pointer } diff --git a/tests/run-pass/zst.rs b/tests/run-pass/zst.rs index 4ebb2001e72..78d3025587f 100644 --- a/tests/run-pass/zst.rs +++ b/tests/run-pass/zst.rs @@ -21,4 +21,6 @@ fn main() { assert_eq!(use_zst(), A); assert_eq!(&A as *const A as *const (), &() as *const _); assert_eq!(&A as *const A, &A as *const A); + let x = 42 as *mut (); + unsafe { *x = (); } }