1
Fork 0

miri validity: make recursive ref checking optional

This commit is contained in:
Ralf Jung 2018-10-02 16:06:50 +02:00
parent 25a75a4d86
commit bf5e6ebdd3
3 changed files with 121 additions and 87 deletions

View file

@ -1558,15 +1558,12 @@ fn validate_const<'a, 'tcx>(
let ecx = ::rustc_mir::const_eval::mk_eval_cx(tcx, gid.instance, param_env).unwrap();
let result = (|| {
let op = ecx.const_to_op(constant)?;
let mut todo = vec![(op, Vec::new())];
let mut seen = FxHashSet();
seen.insert(op);
while let Some((op, mut path)) = todo.pop() {
let mut ref_tracking = ::rustc_mir::interpret::RefTracking::new(op);
while let Some((op, mut path)) = ref_tracking.todo.pop() {
ecx.validate_operand(
op,
&mut path,
&mut seen,
&mut todo,
Some(&mut ref_tracking),
)?;
}
Ok(())

View file

@ -35,3 +35,5 @@ pub use self::memory::{Memory, MemoryKind};
pub use self::machine::Machine;
pub use self::operand::{ScalarMaybeUndef, Value, ValTy, Operand, OpTy};
pub use self::validity::RefTracking;

View file

@ -11,7 +11,7 @@
use std::fmt::Write;
use syntax_pos::symbol::Symbol;
use rustc::ty::layout::{self, Size, Primitive};
use rustc::ty::layout::{self, Size};
use rustc::ty::{self, Ty};
use rustc_data_structures::fx::FxHashSet;
use rustc::mir::interpret::{
@ -19,7 +19,7 @@ use rustc::mir::interpret::{
};
use super::{
OpTy, Machine, EvalContext, ScalarMaybeUndef
OpTy, MPlaceTy, Machine, EvalContext, ScalarMaybeUndef
};
macro_rules! validation_failure{
@ -63,6 +63,23 @@ pub enum PathElem {
Tag,
}
/// State for tracking recursive validation of references
pub struct RefTracking<'tcx> {
pub seen: FxHashSet<(OpTy<'tcx>)>,
pub todo: Vec<(OpTy<'tcx>, Vec<PathElem>)>,
}
impl<'tcx> RefTracking<'tcx> {
pub fn new(op: OpTy<'tcx>) -> Self {
let mut ref_tracking = RefTracking {
seen: FxHashSet(),
todo: vec![(op, Vec::new())],
};
ref_tracking.seen.insert(op);
ref_tracking
}
}
// Adding a Deref and making a copy of the path to be put into the queue
// always go together. This one does it with only new allocation.
fn path_clone_and_deref(path: &Vec<PathElem>) -> Vec<PathElem> {
@ -205,6 +222,41 @@ impl<'a, 'mir, 'tcx, M: Machine<'a, 'mir, 'tcx>> EvalContext<'a, 'mir, 'tcx, M>
}
}
/// Validate a reference, potentially recursively. `place` is assumed to already be
/// dereferenced, i.e. it describes the target.
fn validate_ref(
&self,
place: MPlaceTy<'tcx>,
path: &mut Vec<PathElem>,
ref_tracking: Option<&mut RefTracking<'tcx>>,
) -> EvalResult<'tcx> {
// Skip recursion for some external statics
if let Scalar::Ptr(ptr) = place.ptr {
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.
// they might be checked at a different type, but for now we want
// to avoid recursing too deeply.
// extern statics cannot be validated as they have no body.
if !did.is_local() || self.tcx.is_foreign_item(did) {
return Ok(());
}
}
}
// Check if we have encountered this pointer+layout combination
// before. Proceed recursively even for integer pointers, no
// reason to skip them! They are valid for some ZST, but not for others
// (e.g. `!` is a ZST).
let op = place.into();
if let Some(ref_tracking) = ref_tracking {
if ref_tracking.seen.insert(op) {
trace!("Recursing below ptr {:#?}", *op);
ref_tracking.todo.push((op, path_clone_and_deref(path)));
}
}
Ok(())
}
/// This function checks the data at `op`.
/// It will error if the bits at the destination do not match the ones described by the layout.
/// The `path` may be pushed to, but the part that is present when the function
@ -213,8 +265,7 @@ impl<'a, 'mir, 'tcx, M: Machine<'a, 'mir, 'tcx>> EvalContext<'a, 'mir, 'tcx, M>
&self,
dest: OpTy<'tcx>,
path: &mut Vec<PathElem>,
seen: &mut FxHashSet<(OpTy<'tcx>)>,
todo: &mut Vec<(OpTy<'tcx>, Vec<PathElem>)>,
mut ref_tracking: Option<&mut RefTracking<'tcx>>,
) -> EvalResult<'tcx> {
trace!("validate_operand: {:?}, {:#?}", *dest, dest.layout);
@ -273,7 +324,7 @@ impl<'a, 'mir, 'tcx, M: Machine<'a, 'mir, 'tcx>> EvalContext<'a, 'mir, 'tcx, M>
// Validate all fields
match dest.layout.fields {
// primitives are unions with zero fields
// Primitives appear as Union with 0 fields -- except for fat pointers.
// We still check `layout.fields`, not `layout.abi`, because `layout.abi`
// is `Scalar` for newtypes around scalars, but we want to descend through the
// fields to get a proper `path`.
@ -302,32 +353,61 @@ impl<'a, 'mir, 'tcx, M: Machine<'a, 'mir, 'tcx>> EvalContext<'a, 'mir, 'tcx, M>
};
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 cannot 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() {
let ptr_op = self.ref_to_mplace(value)?.into();
// we have not encountered this pointer+layout combination
// before.
if seen.insert(ptr_op) {
trace!("Recursing below ptr {:#?}", *value);
todo.push((ptr_op, path_clone_and_deref(path)));
}
}
}
// Recursively check *safe* references
if dest.layout.ty.builtin_deref(true).is_some() &&
!dest.layout.ty.is_unsafe_ptr()
{
self.validate_ref(self.ref_to_mplace(value)?, path, ref_tracking)?;
}
},
_ => bug!("bad abi for FieldPlacement::Union(0): {:#?}", dest.layout.abi),
}
}
layout::FieldPlacement::Arbitrary { .. }
if dest.layout.ty.builtin_deref(true).is_some() =>
{
// This is a fat pointer.
let ptr = match self.read_value(dest.into())
.and_then(|val| self.ref_to_mplace(val))
{
Ok(ptr) => ptr,
Err(_) =>
return validation_failure!(
"undefined location or metadata in fat pointer", path
),
};
// check metadata early, for better diagnostics
match self.tcx.struct_tail(ptr.layout.ty).sty {
ty::Dynamic(..) => {
match ptr.extra.unwrap().to_ptr() {
Ok(_) => {},
Err(_) =>
return validation_failure!(
"non-pointer vtable in fat pointer", path
),
}
// FIXME: More checks for the vtable.
}
ty::Slice(..) | ty::Str => {
match ptr.extra.unwrap().to_usize(self) {
Ok(_) => {},
Err(_) =>
return validation_failure!(
"non-integer slice length in fat pointer", path
),
}
}
_ =>
bug!("Unexpected unsized type tail: {:?}",
self.tcx.struct_tail(ptr.layout.ty)
),
}
// for safe ptrs, recursively check it
if !dest.layout.ty.is_unsafe_ptr() {
self.validate_ref(ptr, path, ref_tracking)?;
}
}
// Compound data structures
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.
@ -411,7 +491,11 @@ impl<'a, 'mir, 'tcx, M: Machine<'a, 'mir, 'tcx>> EvalContext<'a, 'mir, 'tcx, M>
for (i, field) in self.mplace_array_fields(dest)?.enumerate() {
let field = field?;
path.push(PathElem::ArrayElem(i));
self.validate_operand(field.into(), path, seen, todo)?;
self.validate_operand(
field.into(),
path,
ref_tracking.as_mut().map(|r| &mut **r)
)?;
path.truncate(path_len);
}
}
@ -421,63 +505,14 @@ impl<'a, 'mir, 'tcx, M: Machine<'a, 'mir, 'tcx>> EvalContext<'a, 'mir, 'tcx, M>
// An empty array. Nothing to do.
}
layout::FieldPlacement::Arbitrary { ref offsets, .. } => {
// Fat pointers are treated like pointers, not aggregates.
if dest.layout.ty.builtin_deref(true).is_some() {
// This is a fat pointer.
let ptr = match self.read_value(dest.into())
.and_then(|val| self.ref_to_mplace(val))
{
Ok(ptr) => ptr,
Err(_) =>
return validation_failure!(
"undefined location or metadata in fat pointer", path
),
};
// check metadata early, for better diagnostics
match self.tcx.struct_tail(ptr.layout.ty).sty {
ty::Dynamic(..) => {
match ptr.extra.unwrap().to_ptr() {
Ok(_) => {},
Err(_) =>
return validation_failure!(
"non-pointer vtable in fat pointer", path
),
}
// FIXME: More checks for the vtable.
}
ty::Slice(..) | ty::Str => {
match ptr.extra.unwrap().to_usize(self) {
Ok(_) => {},
Err(_) =>
return validation_failure!(
"non-integer slice length in fat pointer", path
),
}
}
_ =>
bug!("Unexpected unsized type tail: {:?}",
self.tcx.struct_tail(ptr.layout.ty)
),
}
// for safe ptrs, recursively check it
if !dest.layout.ty.is_unsafe_ptr() {
let ptr = ptr.into();
if seen.insert(ptr) {
trace!("Recursing below fat ptr {:?}", ptr);
todo.push((ptr, path_clone_and_deref(path)));
}
}
} else {
// Not a pointer, perform regular aggregate handling below
for i in 0..offsets.len() {
let field = self.operand_field(dest, i as u64)?;
path.push(self.aggregate_field_path_elem(dest.layout.ty, variant, i));
self.validate_operand(field, path, seen, todo)?;
self.validate_operand(field, path, ref_tracking.as_mut().map(|r| &mut **r))?;
path.truncate(path_len);
}
}
}
}
Ok(())
}