]> git.lizzy.rs Git - rust.git/commitdiff
move validation to its own file
authorRalf Jung <post@ralfj.de>
Fri, 17 Aug 2018 10:18:02 +0000 (12:18 +0200)
committerRalf Jung <post@ralfj.de>
Wed, 22 Aug 2018 11:08:38 +0000 (13:08 +0200)
src/librustc_mir/interpret/eval_context.rs
src/librustc_mir/interpret/mod.rs
src/librustc_mir/interpret/validity.rs [new file with mode: 0644]

index 539428b9e517e4679c58068eda1d553aed687c44..d42fc8746c4c058c3ece7ca13d3917deb89150ea 100644 (file)
@@ -7,7 +7,7 @@
 use rustc::hir::map::definitions::DefPathData;
 use rustc::mir;
 use rustc::ty::layout::{
-    self, Size, Align, HasDataLayout, LayoutOf, TyLayout, Primitive
+    self, Size, Align, HasDataLayout, LayoutOf, TyLayout
 };
 use rustc::ty::subst::{Subst, Substs};
 use rustc::ty::{self, Ty, TyCtxt, TypeFoldable};
@@ -15,7 +15,7 @@
 use rustc_data_structures::fx::{FxHashSet, FxHasher};
 use rustc_data_structures::indexed_vec::IndexVec;
 use rustc::mir::interpret::{
-    GlobalId, Scalar, FrameInfo, AllocType,
+    GlobalId, Scalar, FrameInfo,
     EvalResult, EvalErrorKind,
     ScalarMaybeUndef,
     truncate, sign_extend,
     Memory, Machine
 };
 
-macro_rules! validation_failure{
-    ($what:expr, $where:expr, $details:expr) => {{
-        let where_ = if $where.is_empty() {
-            String::new()
-        } else {
-            format!(" at {}", $where)
-        };
-        err!(ValidationFailure(format!(
-            "encountered {}{}, but expected {}",
-            $what, where_, $details,
-        )))
-    }};
-    ($what:expr, $where:expr) => {{
-        let where_ = if $where.is_empty() {
-            String::new()
-        } else {
-            format!(" at {}", $where)
-        };
-        err!(ValidationFailure(format!(
-            "encountered {}{}",
-            $what, where_,
-        )))
-    }};
-}
-
 pub struct EvalContext<'a, 'mir, 'tcx: 'a + 'mir, M: Machine<'mir, 'tcx>> {
     /// Stores the `Machine` instance.
     pub machine: M,
@@ -670,243 +645,6 @@ pub fn const_eval(&self, gid: GlobalId<'tcx>) -> EvalResult<'tcx, &'tcx ty::Cons
         self.tcx.const_eval(param_env.and(gid)).map_err(|err| EvalErrorKind::ReferencedConstant(err).into())
     }
 
-    fn validate_scalar(
-        &self,
-        value: ScalarMaybeUndef,
-        size: Size,
-        scalar: &layout::Scalar,
-        path: &str,
-        ty: Ty,
-    ) -> EvalResult<'tcx> {
-        trace!("validate scalar: {:#?}, {:#?}, {:#?}, {}", value, size, scalar, ty);
-        let (lo, hi) = scalar.valid_range.clone().into_inner();
-
-        let value = match value {
-            ScalarMaybeUndef::Scalar(scalar) => scalar,
-            ScalarMaybeUndef::Undef => return validation_failure!("undefined bytes", path),
-        };
-
-        let bits = match value {
-            Scalar::Bits { bits, size: value_size } => {
-                assert_eq!(value_size as u64, size.bytes());
-                bits
-            },
-            Scalar::Ptr(_) => {
-                let ptr_size = self.memory.pointer_size();
-                let ptr_max = u128::max_value() >> (128 - ptr_size.bits());
-                return if lo > hi {
-                    if lo - hi == 1 {
-                        // no gap, all values are ok
-                        Ok(())
-                    } else if hi < ptr_max || lo > 1 {
-                        let max = u128::max_value() >> (128 - size.bits());
-                        validation_failure!(
-                            "pointer",
-                            path,
-                            format!("something in the range {:?} or {:?}", 0..=lo, hi..=max)
-                        )
-                    } else {
-                        Ok(())
-                    }
-                } else if hi < ptr_max || lo > 1 {
-                    validation_failure!(
-                        "pointer",
-                        path,
-                        format!("something in the range {:?}", scalar.valid_range)
-                    )
-                } else {
-                    Ok(())
-                };
-            },
-        };
-
-        // char gets a special treatment, because its number space is not contiguous so `TyLayout`
-        // has no special checks for chars
-        match ty.sty {
-            ty::TyChar => {
-                debug_assert_eq!(size.bytes(), 4);
-                if ::std::char::from_u32(bits as u32).is_none() {
-                    return err!(InvalidChar(bits));
-                }
-            }
-            _ => {},
-        }
-
-        use std::ops::RangeInclusive;
-        let in_range = |bound: RangeInclusive<u128>| bound.contains(&bits);
-        if lo > hi {
-            if in_range(0..=hi) || in_range(lo..=u128::max_value()) {
-                Ok(())
-            } else {
-                validation_failure!(
-                    bits,
-                    path,
-                    format!("something in the range {:?} or {:?}", ..=hi, lo..)
-                )
-            }
-        } else {
-            if in_range(scalar.valid_range.clone()) {
-                Ok(())
-            } else {
-                validation_failure!(
-                    bits,
-                    path,
-                    format!("something in the range {:?}", scalar.valid_range)
-                )
-            }
-        }
-    }
-
-    /// This function checks the memory where `ptr` points to.
-    /// It will error if the bits at the destination do not match the ones described by the layout.
-    pub fn validate_mplace(
-        &self,
-        dest: MPlaceTy<'tcx>,
-        path: String,
-        seen: &mut FxHashSet<(MPlaceTy<'tcx>)>,
-        todo: &mut Vec<(MPlaceTy<'tcx>, String)>,
-    ) -> EvalResult<'tcx> {
-        self.memory.dump_alloc(dest.to_ptr()?.alloc_id);
-        trace!("validate_mplace: {:?}, {:#?}", *dest, dest.layout);
-
-        // Find the right variant
-        let (variant, dest) = match dest.layout.variants {
-            layout::Variants::NicheFilling { niche: ref tag, .. } |
-            layout::Variants::Tagged { ref tag, .. } => {
-                let size = tag.value.size(self);
-                // we first read the tag value as scalar, to be able to validate it
-                let tag_mplace = self.mplace_field(dest, 0)?;
-                let tag_value = self.read_scalar(tag_mplace.into())?;
-                let path = format!("{}.TAG", path);
-                self.validate_scalar(
-                    tag_value, size, tag, &path, tag_mplace.layout.ty
-                )?;
-                // then we read it again to get the index, to continue
-                let variant = self.read_discriminant_as_variant_index(dest.into())?;
-                let dest = self.mplace_downcast(dest, variant)?;
-                trace!("variant layout: {:#?}", dest.layout);
-                (variant, dest)
-            },
-            layout::Variants::Single { index } => {
-                (index, dest)
-            }
-        };
-
-        // Validate all fields
-        match dest.layout.fields {
-            // primitives are unions with zero fields
-            layout::FieldPlacement::Union(0) => {
-                match dest.layout.abi {
-                    // nothing to do, whatever the pointer points to, it is never going to be read
-                    layout::Abi::Uninhabited => validation_failure!("a value of an uninhabited type", path),
-                    // check that the scalar is a valid pointer or that its bit range matches the
-                    // expectation.
-                    layout::Abi::Scalar(ref scalar_layout) => {
-                        let size = scalar_layout.value.size(self);
-                        let value = self.read_value(dest.into())?;
-                        let scalar = value.to_scalar_or_undef();
-                        self.validate_scalar(scalar, size, scalar_layout, &path, dest.layout.ty)?;
-                        if scalar_layout.value == Primitive::Pointer {
-                            // ignore integer pointers, we can't reason about the final hardware
-                            if let Scalar::Ptr(ptr) = scalar.not_undef()? {
-                                let alloc_kind = self.tcx.alloc_map.lock().get(ptr.alloc_id);
-                                if let Some(AllocType::Static(did)) = alloc_kind {
-                                    // statics from other crates are already checked
-                                    // extern statics should not be validated as they have no body
-                                    if !did.is_local() || self.tcx.is_foreign_item(did) {
-                                        return Ok(());
-                                    }
-                                }
-                                if value.layout.ty.builtin_deref(false).is_some() {
-                                    trace!("Recursing below ptr {:#?}", value);
-                                    let ptr_place = self.ref_to_mplace(value)?;
-                                    // we have not encountered this pointer+layout combination before
-                                    if seen.insert(ptr_place) {
-                                        todo.push((ptr_place, format!("(*{})", path)))
-                                    }
-                                }
-                            }
-                        }
-                        Ok(())
-                    },
-                    _ => bug!("bad abi for FieldPlacement::Union(0): {:#?}", dest.layout.abi),
-                }
-            }
-            layout::FieldPlacement::Union(_) => {
-                // We can't check unions, their bits are allowed to be anything.
-                // The fields don't need to correspond to any bit pattern of the union's fields.
-                // See https://github.com/rust-lang/rust/issues/32836#issuecomment-406875389
-                Ok(())
-            },
-            layout::FieldPlacement::Array { count, .. } => {
-                for i in 0..count {
-                    let mut path = path.clone();
-                    self.dump_field_name(&mut path, dest.layout.ty, i as usize, variant).unwrap();
-                    let field = self.mplace_field(dest, i)?;
-                    self.validate_mplace(field, path, seen, todo)?;
-                }
-                Ok(())
-            },
-            layout::FieldPlacement::Arbitrary { ref offsets, .. } => {
-                // fat pointers need special treatment
-                match dest.layout.ty.builtin_deref(false).map(|tam| &tam.ty.sty) {
-                    | Some(ty::TyStr)
-                    | Some(ty::TySlice(_)) => {
-                        // check the length (for nicer error messages)
-                        let len_mplace = self.mplace_field(dest, 1)?;
-                        let len = self.read_scalar(len_mplace.into())?;
-                        let len = match len.to_bits(len_mplace.layout.size) {
-                            Err(_) => return validation_failure!("length is not a valid integer", path),
-                            Ok(len) => len as u64,
-                        };
-                        // get the fat ptr, and recursively check it
-                        let ptr = self.ref_to_mplace(self.read_value(dest.into())?)?;
-                        assert_eq!(ptr.extra, PlaceExtra::Length(len));
-                        let unpacked_ptr = self.unpack_unsized_mplace(ptr)?;
-                        if seen.insert(unpacked_ptr) {
-                            let mut path = path.clone();
-                            self.dump_field_name(&mut path, dest.layout.ty, 0, 0).unwrap();
-                            todo.push((unpacked_ptr, path))
-                        }
-                    },
-                    Some(ty::TyDynamic(..)) => {
-                        // check the vtable (for nicer error messages)
-                        let vtable = self.read_scalar(self.mplace_field(dest, 1)?.into())?;
-                        let vtable = match vtable.to_ptr() {
-                            Err(_) => return validation_failure!("vtable address is not a pointer", path),
-                            Ok(vtable) => vtable,
-                        };
-                        // get the fat ptr, and recursively check it
-                        let ptr = self.ref_to_mplace(self.read_value(dest.into())?)?;
-                        assert_eq!(ptr.extra, PlaceExtra::Vtable(vtable));
-                        let unpacked_ptr = self.unpack_unsized_mplace(ptr)?;
-                        if seen.insert(unpacked_ptr) {
-                            let mut path = path.clone();
-                            self.dump_field_name(&mut path, dest.layout.ty, 0, 0).unwrap();
-                            todo.push((unpacked_ptr, path))
-                        }
-                        // FIXME: More checks for the vtable... making sure it is exactly
-                        // the one one would expect for this type.
-                    },
-                    Some(ty) =>
-                        bug!("Unexpected fat pointer target type {:?}", ty),
-                    None => {
-                        // Not a pointer, perform regular aggregate handling below
-                        for i in 0..offsets.len() {
-                            let mut path = path.clone();
-                            self.dump_field_name(&mut path, dest.layout.ty, i, variant).unwrap();
-                            let field = self.mplace_field(dest, i as u64)?;
-                            self.validate_mplace(field, path, seen, todo)?;
-                        }
-                        // FIXME: For a TyStr, check that this is valid UTF-8.
-                    },
-                }
-
-                Ok(())
-            }
-        }
-    }
-
     #[inline(always)]
     pub fn frame(&self) -> &Frame<'mir, 'tcx> {
         self.stack.last().expect("no call frames exist")
@@ -1041,72 +779,5 @@ pub fn sign_extend(&self, value: u128, ty: TyLayout<'_>) -> u128 {
     pub fn truncate(&self, value: u128, ty: TyLayout<'_>) -> u128 {
         truncate(value, ty.size)
     }
-
-    fn dump_field_name(&self, s: &mut String, ty: Ty<'tcx>, i: usize, variant: usize) -> ::std::fmt::Result {
-        match ty.sty {
-            ty::TyBool |
-            ty::TyChar |
-            ty::TyInt(_) |
-            ty::TyUint(_) |
-            ty::TyFloat(_) |
-            ty::TyFnPtr(_) |
-            ty::TyNever |
-            ty::TyFnDef(..) |
-            ty::TyGeneratorWitness(..) |
-            ty::TyForeign(..) |
-            ty::TyDynamic(..) => {
-                bug!("field_name({:?}): not applicable", ty)
-            }
-
-            // Potentially-fat pointers.
-            ty::TyRef(_, pointee, _) |
-            ty::TyRawPtr(ty::TypeAndMut { ty: pointee, .. }) => {
-                assert!(i < 2);
-
-                // Reuse the fat *T type as its own thin pointer data field.
-                // This provides information about e.g. DST struct pointees
-                // (which may have no non-DST form), and will work as long
-                // as the `Abi` or `FieldPlacement` is checked by users.
-                if i == 0 {
-                    return write!(s, ".data_ptr");
-                }
-
-                match self.tcx.struct_tail(pointee).sty {
-                    ty::TySlice(_) |
-                    ty::TyStr => write!(s, ".len"),
-                    ty::TyDynamic(..) => write!(s, ".vtable_ptr"),
-                    _ => bug!("field_name({:?}): not applicable", ty)
-                }
-            }
-
-            // Arrays and slices.
-            ty::TyArray(_, _) |
-            ty::TySlice(_) |
-            ty::TyStr => write!(s, "[{}]", i),
-
-            // generators and closures.
-            ty::TyClosure(def_id, _) | ty::TyGenerator(def_id, _, _) => {
-                let node_id = self.tcx.hir.as_local_node_id(def_id).unwrap();
-                let freevar = self.tcx.with_freevars(node_id, |fv| fv[i]);
-                write!(s, ".upvar({})", self.tcx.hir.name(freevar.var_id()))
-            }
-
-            ty::TyTuple(_) => write!(s, ".{}", i),
-
-            // enums
-            ty::TyAdt(def, ..) if def.is_enum() => {
-                let variant = &def.variants[variant];
-                write!(s, ".{}::{}", variant.name, variant.fields[i].ident)
-            }
-
-            // other ADTs.
-            ty::TyAdt(def, _) => write!(s, ".{}", def.non_enum_variant().fields[i].ident),
-
-            ty::TyProjection(_) | ty::TyAnon(..) | ty::TyParam(_) |
-            ty::TyInfer(_) | ty::TyError => {
-                bug!("dump_field_name: unexpected type `{}`", ty)
-            }
-        }
-    }
 }
 
index 167fb5d184cfcc361214787ae91256e9fb4be46a..b769dae0a7abc2c8b73af5d22f6c502feab10544 100644 (file)
@@ -11,6 +11,7 @@
 mod terminator;
 mod traits;
 mod const_eval;
+mod validity;
 
 pub use self::eval_context::{
     EvalContext, Frame, StackPopCleanup, LocalValue,
diff --git a/src/librustc_mir/interpret/validity.rs b/src/librustc_mir/interpret/validity.rs
new file mode 100644 (file)
index 0000000..53d3006
--- /dev/null
@@ -0,0 +1,343 @@
+use std::fmt::Write;
+
+use rustc::ty::layout::{self, Size, Primitive};
+use rustc::ty::{self, Ty};
+use rustc_data_structures::fx::FxHashSet;
+use rustc::mir::interpret::{
+    Scalar, AllocType, EvalResult, ScalarMaybeUndef,
+};
+
+use super::{
+    MPlaceTy, PlaceExtra, Machine, EvalContext
+};
+
+macro_rules! validation_failure{
+    ($what:expr, $where:expr, $details:expr) => {{
+        let where_ = if $where.is_empty() {
+            String::new()
+        } else {
+            format!(" at {}", $where)
+        };
+        err!(ValidationFailure(format!(
+            "encountered {}{}, but expected {}",
+            $what, where_, $details,
+        )))
+    }};
+    ($what:expr, $where:expr) => {{
+        let where_ = if $where.is_empty() {
+            String::new()
+        } else {
+            format!(" at {}", $where)
+        };
+        err!(ValidationFailure(format!(
+            "encountered {}{}",
+            $what, where_,
+        )))
+    }};
+}
+
+impl<'a, 'mir, 'tcx, M: Machine<'mir, 'tcx>> EvalContext<'a, 'mir, 'tcx, M> {
+    fn validate_scalar(
+        &self,
+        value: ScalarMaybeUndef,
+        size: Size,
+        scalar: &layout::Scalar,
+        path: &str,
+        ty: Ty,
+    ) -> EvalResult<'tcx> {
+        trace!("validate scalar: {:#?}, {:#?}, {:#?}, {}", value, size, scalar, ty);
+        let (lo, hi) = scalar.valid_range.clone().into_inner();
+
+        let value = match value {
+            ScalarMaybeUndef::Scalar(scalar) => scalar,
+            ScalarMaybeUndef::Undef => return validation_failure!("undefined bytes", path),
+        };
+
+        let bits = match value {
+            Scalar::Bits { bits, size: value_size } => {
+                assert_eq!(value_size as u64, size.bytes());
+                bits
+            },
+            Scalar::Ptr(_) => {
+                let ptr_size = self.memory.pointer_size();
+                let ptr_max = u128::max_value() >> (128 - ptr_size.bits());
+                return if lo > hi {
+                    if lo - hi == 1 {
+                        // no gap, all values are ok
+                        Ok(())
+                    } else if hi < ptr_max || lo > 1 {
+                        let max = u128::max_value() >> (128 - size.bits());
+                        validation_failure!(
+                            "pointer",
+                            path,
+                            format!("something in the range {:?} or {:?}", 0..=lo, hi..=max)
+                        )
+                    } else {
+                        Ok(())
+                    }
+                } else if hi < ptr_max || lo > 1 {
+                    validation_failure!(
+                        "pointer",
+                        path,
+                        format!("something in the range {:?}", scalar.valid_range)
+                    )
+                } else {
+                    Ok(())
+                };
+            },
+        };
+
+        // char gets a special treatment, because its number space is not contiguous so `TyLayout`
+        // has no special checks for chars
+        match ty.sty {
+            ty::TyChar => {
+                debug_assert_eq!(size.bytes(), 4);
+                if ::std::char::from_u32(bits as u32).is_none() {
+                    return err!(InvalidChar(bits));
+                }
+            }
+            _ => {},
+        }
+
+        use std::ops::RangeInclusive;
+        let in_range = |bound: RangeInclusive<u128>| bound.contains(&bits);
+        if lo > hi {
+            if in_range(0..=hi) || in_range(lo..=u128::max_value()) {
+                Ok(())
+            } else {
+                validation_failure!(
+                    bits,
+                    path,
+                    format!("something in the range {:?} or {:?}", ..=hi, lo..)
+                )
+            }
+        } else {
+            if in_range(scalar.valid_range.clone()) {
+                Ok(())
+            } else {
+                validation_failure!(
+                    bits,
+                    path,
+                    format!("something in the range {:?}", scalar.valid_range)
+                )
+            }
+        }
+    }
+
+    /// This function checks the memory where `ptr` points to.
+    /// It will error if the bits at the destination do not match the ones described by the layout.
+    pub fn validate_mplace(
+        &self,
+        dest: MPlaceTy<'tcx>,
+        path: String,
+        seen: &mut FxHashSet<(MPlaceTy<'tcx>)>,
+        todo: &mut Vec<(MPlaceTy<'tcx>, String)>,
+    ) -> EvalResult<'tcx> {
+        self.memory.dump_alloc(dest.to_ptr()?.alloc_id);
+        trace!("validate_mplace: {:?}, {:#?}", *dest, dest.layout);
+
+        // Find the right variant
+        let (variant, dest) = match dest.layout.variants {
+            layout::Variants::NicheFilling { niche: ref tag, .. } |
+            layout::Variants::Tagged { ref tag, .. } => {
+                let size = tag.value.size(self);
+                // we first read the tag value as scalar, to be able to validate it
+                let tag_mplace = self.mplace_field(dest, 0)?;
+                let tag_value = self.read_scalar(tag_mplace.into())?;
+                let path = format!("{}.TAG", path);
+                self.validate_scalar(
+                    tag_value, size, tag, &path, tag_mplace.layout.ty
+                )?;
+                // then we read it again to get the index, to continue
+                let variant = self.read_discriminant_as_variant_index(dest.into())?;
+                let dest = self.mplace_downcast(dest, variant)?;
+                trace!("variant layout: {:#?}", dest.layout);
+                (variant, dest)
+            },
+            layout::Variants::Single { index } => {
+                (index, dest)
+            }
+        };
+
+        // Validate all fields
+        match dest.layout.fields {
+            // primitives are unions with zero fields
+            layout::FieldPlacement::Union(0) => {
+                match dest.layout.abi {
+                    // nothing to do, whatever the pointer points to, it is never going to be read
+                    layout::Abi::Uninhabited => validation_failure!("a value of an uninhabited type", path),
+                    // check that the scalar is a valid pointer or that its bit range matches the
+                    // expectation.
+                    layout::Abi::Scalar(ref scalar_layout) => {
+                        let size = scalar_layout.value.size(self);
+                        let value = self.read_value(dest.into())?;
+                        let scalar = value.to_scalar_or_undef();
+                        self.validate_scalar(scalar, size, scalar_layout, &path, dest.layout.ty)?;
+                        if scalar_layout.value == Primitive::Pointer {
+                            // ignore integer pointers, we can't reason about the final hardware
+                            if let Scalar::Ptr(ptr) = scalar.not_undef()? {
+                                let alloc_kind = self.tcx.alloc_map.lock().get(ptr.alloc_id);
+                                if let Some(AllocType::Static(did)) = alloc_kind {
+                                    // statics from other crates are already checked
+                                    // extern statics should not be validated as they have no body
+                                    if !did.is_local() || self.tcx.is_foreign_item(did) {
+                                        return Ok(());
+                                    }
+                                }
+                                if value.layout.ty.builtin_deref(false).is_some() {
+                                    trace!("Recursing below ptr {:#?}", value);
+                                    let ptr_place = self.ref_to_mplace(value)?;
+                                    // we have not encountered this pointer+layout combination before
+                                    if seen.insert(ptr_place) {
+                                        todo.push((ptr_place, format!("(*{})", path)))
+                                    }
+                                }
+                            }
+                        }
+                        Ok(())
+                    },
+                    _ => bug!("bad abi for FieldPlacement::Union(0): {:#?}", dest.layout.abi),
+                }
+            }
+            layout::FieldPlacement::Union(_) => {
+                // We can't check unions, their bits are allowed to be anything.
+                // The fields don't need to correspond to any bit pattern of the union's fields.
+                // See https://github.com/rust-lang/rust/issues/32836#issuecomment-406875389
+                Ok(())
+            },
+            layout::FieldPlacement::Array { count, .. } => {
+                for i in 0..count {
+                    let mut path = path.clone();
+                    self.dump_field_name(&mut path, dest.layout.ty, i as usize, variant).unwrap();
+                    let field = self.mplace_field(dest, i)?;
+                    self.validate_mplace(field, path, seen, todo)?;
+                }
+                Ok(())
+            },
+            layout::FieldPlacement::Arbitrary { ref offsets, .. } => {
+                // fat pointers need special treatment
+                match dest.layout.ty.builtin_deref(false).map(|tam| &tam.ty.sty) {
+                    | Some(ty::TyStr)
+                    | Some(ty::TySlice(_)) => {
+                        // check the length (for nicer error messages)
+                        let len_mplace = self.mplace_field(dest, 1)?;
+                        let len = self.read_scalar(len_mplace.into())?;
+                        let len = match len.to_bits(len_mplace.layout.size) {
+                            Err(_) => return validation_failure!("length is not a valid integer", path),
+                            Ok(len) => len as u64,
+                        };
+                        // get the fat ptr, and recursively check it
+                        let ptr = self.ref_to_mplace(self.read_value(dest.into())?)?;
+                        assert_eq!(ptr.extra, PlaceExtra::Length(len));
+                        let unpacked_ptr = self.unpack_unsized_mplace(ptr)?;
+                        if seen.insert(unpacked_ptr) {
+                            let mut path = path.clone();
+                            self.dump_field_name(&mut path, dest.layout.ty, 0, 0).unwrap();
+                            todo.push((unpacked_ptr, path))
+                        }
+                    },
+                    Some(ty::TyDynamic(..)) => {
+                        // check the vtable (for nicer error messages)
+                        let vtable = self.read_scalar(self.mplace_field(dest, 1)?.into())?;
+                        let vtable = match vtable.to_ptr() {
+                            Err(_) => return validation_failure!("vtable address is not a pointer", path),
+                            Ok(vtable) => vtable,
+                        };
+                        // get the fat ptr, and recursively check it
+                        let ptr = self.ref_to_mplace(self.read_value(dest.into())?)?;
+                        assert_eq!(ptr.extra, PlaceExtra::Vtable(vtable));
+                        let unpacked_ptr = self.unpack_unsized_mplace(ptr)?;
+                        if seen.insert(unpacked_ptr) {
+                            let mut path = path.clone();
+                            self.dump_field_name(&mut path, dest.layout.ty, 0, 0).unwrap();
+                            todo.push((unpacked_ptr, path))
+                        }
+                        // FIXME: More checks for the vtable... making sure it is exactly
+                        // the one one would expect for this type.
+                    },
+                    Some(ty) =>
+                        bug!("Unexpected fat pointer target type {:?}", ty),
+                    None => {
+                        // Not a pointer, perform regular aggregate handling below
+                        for i in 0..offsets.len() {
+                            let mut path = path.clone();
+                            self.dump_field_name(&mut path, dest.layout.ty, i, variant).unwrap();
+                            let field = self.mplace_field(dest, i as u64)?;
+                            self.validate_mplace(field, path, seen, todo)?;
+                        }
+                        // FIXME: For a TyStr, check that this is valid UTF-8.
+                    },
+                }
+
+                Ok(())
+            }
+        }
+    }
+
+    fn dump_field_name(&self, s: &mut String, ty: Ty<'tcx>, i: usize, variant: usize) -> ::std::fmt::Result {
+        match ty.sty {
+            ty::TyBool |
+            ty::TyChar |
+            ty::TyInt(_) |
+            ty::TyUint(_) |
+            ty::TyFloat(_) |
+            ty::TyFnPtr(_) |
+            ty::TyNever |
+            ty::TyFnDef(..) |
+            ty::TyGeneratorWitness(..) |
+            ty::TyForeign(..) |
+            ty::TyDynamic(..) => {
+                bug!("field_name({:?}): not applicable", ty)
+            }
+
+            // Potentially-fat pointers.
+            ty::TyRef(_, pointee, _) |
+            ty::TyRawPtr(ty::TypeAndMut { ty: pointee, .. }) => {
+                assert!(i < 2);
+
+                // Reuse the fat *T type as its own thin pointer data field.
+                // This provides information about e.g. DST struct pointees
+                // (which may have no non-DST form), and will work as long
+                // as the `Abi` or `FieldPlacement` is checked by users.
+                if i == 0 {
+                    return write!(s, ".data_ptr");
+                }
+
+                match self.tcx.struct_tail(pointee).sty {
+                    ty::TySlice(_) |
+                    ty::TyStr => write!(s, ".len"),
+                    ty::TyDynamic(..) => write!(s, ".vtable_ptr"),
+                    _ => bug!("field_name({:?}): not applicable", ty)
+                }
+            }
+
+            // Arrays and slices.
+            ty::TyArray(_, _) |
+            ty::TySlice(_) |
+            ty::TyStr => write!(s, "[{}]", i),
+
+            // generators and closures.
+            ty::TyClosure(def_id, _) | ty::TyGenerator(def_id, _, _) => {
+                let node_id = self.tcx.hir.as_local_node_id(def_id).unwrap();
+                let freevar = self.tcx.with_freevars(node_id, |fv| fv[i]);
+                write!(s, ".upvar({})", self.tcx.hir.name(freevar.var_id()))
+            }
+
+            ty::TyTuple(_) => write!(s, ".{}", i),
+
+            // enums
+            ty::TyAdt(def, ..) if def.is_enum() => {
+                let variant = &def.variants[variant];
+                write!(s, ".{}::{}", variant.name, variant.fields[i].ident)
+            }
+
+            // other ADTs.
+            ty::TyAdt(def, _) => write!(s, ".{}", def.non_enum_variant().fields[i].ident),
+
+            ty::TyProjection(_) | ty::TyAnon(..) | ty::TyParam(_) |
+            ty::TyInfer(_) | ty::TyError => {
+                bug!("dump_field_name: unexpected type `{}`", ty)
+            }
+        }
+    }
+}