1
Fork 0

validity in non-const mode relies on ref_to_mplace checking bounds; (de)reference hooks work on places

This commit is contained in:
Ralf Jung 2018-10-19 17:11:23 +02:00
parent 048900c5b6
commit f5e8830278
4 changed files with 119 additions and 121 deletions

View file

@ -20,8 +20,8 @@ use rustc::hir::{self, def_id::DefId};
use rustc::hir::def::Def; use rustc::hir::def::Def;
use rustc::mir::interpret::{ConstEvalErr, ErrorHandled}; use rustc::mir::interpret::{ConstEvalErr, ErrorHandled};
use rustc::mir; use rustc::mir;
use rustc::ty::{self, Ty, TyCtxt, Instance, query::TyCtxtAt}; use rustc::ty::{self, TyCtxt, Instance, query::TyCtxtAt};
use rustc::ty::layout::{self, Size, LayoutOf, TyLayout}; use rustc::ty::layout::{self, LayoutOf, TyLayout};
use rustc::ty::subst::Subst; use rustc::ty::subst::Subst;
use rustc::traits::Reveal; use rustc::traits::Reveal;
use rustc_data_structures::indexed_vec::IndexVec; use rustc_data_structures::indexed_vec::IndexVec;
@ -32,7 +32,7 @@ use syntax::ast::Mutability;
use syntax::source_map::{Span, DUMMY_SP}; use syntax::source_map::{Span, DUMMY_SP};
use interpret::{self, use interpret::{self,
PlaceTy, MemPlace, OpTy, Operand, Value, Pointer, Scalar, ConstValue, PlaceTy, MemPlace, OpTy, Operand, Value, Scalar, ConstValue,
EvalResult, EvalError, EvalErrorKind, GlobalId, EvalContext, StackPopCleanup, EvalResult, EvalError, EvalErrorKind, GlobalId, EvalContext, StackPopCleanup,
Allocation, AllocId, MemoryKind, Allocation, AllocId, MemoryKind,
snapshot, RefTracking, snapshot, RefTracking,
@ -465,28 +465,6 @@ impl<'a, 'mir, 'tcx> interpret::Machine<'a, 'mir, 'tcx>
&ecx.stack[..], &ecx.stack[..],
) )
} }
#[inline(always)]
fn tag_reference(
_ecx: &mut EvalContext<'a, 'mir, 'tcx, Self>,
_ptr: Pointer<Self::PointerTag>,
_pointee_ty: Ty<'tcx>,
_pointee_size: Size,
_borrow_kind: Option<hir::Mutability>,
) -> EvalResult<'tcx, Self::PointerTag> {
Ok(())
}
#[inline(always)]
fn tag_dereference(
_ecx: &EvalContext<'a, 'mir, 'tcx, Self>,
_ptr: Pointer<Self::PointerTag>,
_pointee_ty: Ty<'tcx>,
_pointee_size: Size,
_borrow_kind: Option<hir::Mutability>,
) -> EvalResult<'tcx, Self::PointerTag> {
Ok(())
}
} }
/// Project to a field of a (variant of a) const /// Project to a field of a (variant of a) const

View file

@ -21,7 +21,7 @@ use rustc::ty::{self, Ty, layout::{Size, TyLayout}, query::TyCtxtAt};
use super::{ use super::{
Allocation, AllocId, EvalResult, Scalar, Allocation, AllocId, EvalResult, Scalar,
EvalContext, PlaceTy, OpTy, Pointer, MemoryKind, EvalContext, PlaceTy, OpTy, Pointer, MemPlace, MemoryKind,
}; };
/// Classifying memory accesses /// Classifying memory accesses
@ -205,26 +205,32 @@ pub trait Machine<'a, 'mir, 'tcx>: Sized {
} }
/// Executed when evaluating the `&` operator: Creating a new reference. /// Executed when evaluating the `&` operator: Creating a new reference.
/// This has the chance to adjust the tag. /// This has the chance to adjust the tag. It should not change anything else!
/// `mutability` can be `None` in case a raw ptr is being created. /// `mutability` can be `None` in case a raw ptr is being created.
#[inline]
fn tag_reference( fn tag_reference(
ecx: &mut EvalContext<'a, 'mir, 'tcx, Self>, _ecx: &mut EvalContext<'a, 'mir, 'tcx, Self>,
ptr: Pointer<Self::PointerTag>, place: MemPlace<Self::PointerTag>,
pointee_ty: Ty<'tcx>, _ty: Ty<'tcx>,
pointee_size: Size, _size: Size,
mutability: Option<hir::Mutability>, _mutability: Option<hir::Mutability>,
) -> EvalResult<'tcx, Self::PointerTag>; ) -> EvalResult<'tcx, MemPlace<Self::PointerTag>> {
Ok(place)
}
/// Executed when evaluating the `*` operator: Following a reference. /// Executed when evaluating the `*` operator: Following a reference.
/// This has the change to adjust the tag. /// This has the change to adjust the tag. It should not change anything else!
/// `mutability` can be `None` in case a raw ptr is being dereferenced. /// `mutability` can be `None` in case a raw ptr is being dereferenced.
#[inline]
fn tag_dereference( fn tag_dereference(
ecx: &EvalContext<'a, 'mir, 'tcx, Self>, _ecx: &EvalContext<'a, 'mir, 'tcx, Self>,
ptr: Pointer<Self::PointerTag>, place: MemPlace<Self::PointerTag>,
pointee_ty: Ty<'tcx>, _ty: Ty<'tcx>,
pointee_size: Size, _size: Size,
mutability: Option<hir::Mutability>, _mutability: Option<hir::Mutability>,
) -> EvalResult<'tcx, Self::PointerTag>; ) -> EvalResult<'tcx, MemPlace<Self::PointerTag>> {
Ok(place)
}
/// Execute a validation operation /// Execute a validation operation
#[inline] #[inline]

View file

@ -276,26 +276,25 @@ where
let align = layout.align; let align = layout.align;
let meta = val.to_meta()?; let meta = val.to_meta()?;
let ptr = val.to_scalar_ptr()?;
let ptr = match val.to_scalar_ptr()? { let mplace = MemPlace { ptr, align, meta };
Scalar::Ptr(ptr) if M::ENABLE_PTR_TRACKING_HOOKS => { // Pointer tag tracking might want to adjust the tag.
// Machine might want to track the `*` operator let mplace = if M::ENABLE_PTR_TRACKING_HOOKS {
let (size, _) = self.size_and_align_of(meta, layout)? let (size, _) = self.size_and_align_of(meta, layout)?
.expect("ref_to_mplace cannot determine size"); // for extern types, just cover what we can
let mutbl = match val.layout.ty.sty { .unwrap_or_else(|| layout.size_and_align());
// `builtin_deref` considers boxes immutable, that's useless for our purposes let mutbl = match val.layout.ty.sty {
ty::Ref(_, _, mutbl) => Some(mutbl), // `builtin_deref` considers boxes immutable, that's useless for our purposes
ty::Adt(def, _) if def.is_box() => Some(hir::MutMutable), ty::Ref(_, _, mutbl) => Some(mutbl),
ty::RawPtr(_) => None, ty::Adt(def, _) if def.is_box() => Some(hir::MutMutable),
_ => bug!("Unexpected pointer type {}", val.layout.ty.sty), ty::RawPtr(_) => None,
}; _ => bug!("Unexpected pointer type {}", val.layout.ty.sty),
let tag = M::tag_dereference(self, ptr, pointee_type, size, mutbl)?; };
Scalar::Ptr(Pointer::new_with_tag(ptr.alloc_id, ptr.offset, tag)) M::tag_dereference(self, mplace, pointee_type, size, mutbl)?
} } else {
other => other, mplace
}; };
Ok(MPlaceTy { mplace, layout })
Ok(MPlaceTy { mplace: MemPlace { ptr, align, meta }, layout })
} }
/// Turn a mplace into a (thin or fat) pointer, as a reference, pointing to the same space. /// Turn a mplace into a (thin or fat) pointer, as a reference, pointing to the same space.
@ -305,24 +304,25 @@ where
place: MPlaceTy<'tcx, M::PointerTag>, place: MPlaceTy<'tcx, M::PointerTag>,
borrow_kind: Option<mir::BorrowKind>, borrow_kind: Option<mir::BorrowKind>,
) -> EvalResult<'tcx, Value<M::PointerTag>> { ) -> EvalResult<'tcx, Value<M::PointerTag>> {
let ptr = match place.ptr { // Pointer tag tracking might want to adjust the tag
Scalar::Ptr(ptr) if M::ENABLE_PTR_TRACKING_HOOKS => { let place = if M::ENABLE_PTR_TRACKING_HOOKS {
// Machine might want to track the `&` operator let (size, _) = self.size_and_align_of_mplace(place)?
let (size, _) = self.size_and_align_of_mplace(place)? // for extern types, just cover what we can
.expect("create_ref cannot determine size"); .unwrap_or_else(|| place.layout.size_and_align());
let mutbl = match borrow_kind { let mutbl = match borrow_kind {
Some(mir::BorrowKind::Mut { .. }) => Some(hir::MutMutable), Some(mir::BorrowKind::Mut { .. }) |
Some(_) => Some(hir::MutImmutable), Some(mir::BorrowKind::Unique) =>
None => None, Some(hir::MutMutable),
}; Some(_) => Some(hir::MutImmutable),
let tag = M::tag_reference(self, ptr, place.layout.ty, size, mutbl)?; None => None,
Scalar::Ptr(Pointer::new_with_tag(ptr.alloc_id, ptr.offset, tag)) };
}, M::tag_reference(self, *place, place.layout.ty, size, mutbl)?
other => other, } else {
*place
}; };
Ok(match place.meta { Ok(match place.meta {
None => Value::Scalar(ptr.into()), None => Value::Scalar(place.ptr.into()),
Some(meta) => Value::ScalarPair(ptr.into(), meta.into()), Some(meta) => Value::ScalarPair(place.ptr.into(), meta.into()),
}) })
} }

View file

@ -12,7 +12,7 @@ use std::fmt::Write;
use std::hash::Hash; use std::hash::Hash;
use syntax_pos::symbol::Symbol; use syntax_pos::symbol::Symbol;
use rustc::ty::layout::{self, Size, Align, TyLayout}; use rustc::ty::layout::{self, Size, Align, TyLayout, LayoutOf};
use rustc::ty; use rustc::ty;
use rustc_data_structures::fx::FxHashSet; use rustc_data_structures::fx::FxHashSet;
use rustc::mir::interpret::{ use rustc::mir::interpret::{
@ -176,19 +176,27 @@ impl<'a, 'mir, 'tcx, M: Machine<'a, 'mir, 'tcx>> EvalContext<'a, 'mir, 'tcx, M>
// undef. We should fix that, but let's start low. // undef. We should fix that, but let's start low.
} }
} }
_ if ty.is_box() || ty.is_region_ptr() || ty.is_unsafe_ptr() => { ty::RawPtr(..) => {
// Handle fat pointers. We also check fat raw pointers, // No undef allowed here. Eventually this should be consistent with
// their metadata must be valid! // the integer types.
// This also checks that the ptr itself is initialized, which let _ptr = try_validation!(value.to_scalar_ptr(),
// seems reasonable even for raw pointers. "undefined address in pointer", path);
let place = try_validation!(self.ref_to_mplace(value), let _meta = try_validation!(value.to_meta(),
"undefined data in pointer", path); "uninitialized data in fat pointer metadata", path);
}
_ if ty.is_box() || ty.is_region_ptr() => {
// Handle fat pointers.
// Check metadata early, for better diagnostics // Check metadata early, for better diagnostics
if place.layout.is_unsized() { let ptr = try_validation!(value.to_scalar_ptr(),
let tail = self.tcx.struct_tail(place.layout.ty); "undefined address in pointer", path);
let meta = try_validation!(value.to_meta(),
"uninitialized data in fat pointer metadata", path);
let layout = self.layout_of(value.layout.ty.builtin_deref(true).unwrap().ty)?;
if layout.is_unsized() {
let tail = self.tcx.struct_tail(layout.ty);
match tail.sty { match tail.sty {
ty::Dynamic(..) => { ty::Dynamic(..) => {
let vtable = try_validation!(place.meta.unwrap().to_ptr(), let vtable = try_validation!(meta.unwrap().to_ptr(),
"non-pointer vtable in fat pointer", path); "non-pointer vtable in fat pointer", path);
try_validation!(self.read_drop_type_from_vtable(vtable), try_validation!(self.read_drop_type_from_vtable(vtable),
"invalid drop fn in vtable", path); "invalid drop fn in vtable", path);
@ -197,7 +205,7 @@ impl<'a, 'mir, 'tcx, M: Machine<'a, 'mir, 'tcx>> EvalContext<'a, 'mir, 'tcx, M>
// FIXME: More checks for the vtable. // FIXME: More checks for the vtable.
} }
ty::Slice(..) | ty::Str => { ty::Slice(..) | ty::Str => {
try_validation!(place.meta.unwrap().to_usize(self), try_validation!(meta.unwrap().to_usize(self),
"non-integer slice length in fat pointer", path); "non-integer slice length in fat pointer", path);
} }
ty::Foreign(..) => { ty::Foreign(..) => {
@ -207,17 +215,17 @@ impl<'a, 'mir, 'tcx, M: Machine<'a, 'mir, 'tcx>> EvalContext<'a, 'mir, 'tcx, M>
bug!("Unexpected unsized type tail: {:?}", tail), bug!("Unexpected unsized type tail: {:?}", tail),
} }
} }
// for safe ptrs, also check the ptr values itself // Make sure this is non-NULL and aligned
if !ty.is_unsafe_ptr() { let (size, align) = self.size_and_align_of(meta, layout)?
// Make sure this is non-NULL and aligned // for the purpose of validity, consider foreign types to have
let (size, align) = self.size_and_align_of(place.meta, place.layout)? // alignment and size determined by the layout (size will be 0,
// for the purpose of validity, consider foreign types to have // alignment should take attributes into account).
// alignment and size determined by the layout (size will be 0, .unwrap_or_else(|| layout.size_and_align());
// alignment should take attributes into account). match self.memory.check_align(ptr, align) {
.unwrap_or_else(|| place.layout.size_and_align()); Ok(_) => {},
match self.memory.check_align(place.ptr, align) { Err(err) => {
Ok(_) => {}, error!("{:?} is not aligned to {:?}", ptr, align);
Err(err) => match err.kind { match err.kind {
EvalErrorKind::InvalidNullPointerUsage => EvalErrorKind::InvalidNullPointerUsage =>
return validation_failure!("NULL reference", path), return validation_failure!("NULL reference", path),
EvalErrorKind::AlignmentCheckFailed { .. } => EvalErrorKind::AlignmentCheckFailed { .. } =>
@ -225,41 +233,47 @@ impl<'a, 'mir, 'tcx, M: Machine<'a, 'mir, 'tcx>> EvalContext<'a, 'mir, 'tcx, M>
_ => _ =>
return validation_failure!( return validation_failure!(
"dangling (out-of-bounds) reference (might be NULL at \ "dangling (out-of-bounds) reference (might be NULL at \
run-time)", run-time)",
path path
), ),
} }
} }
// non-ZST also have to be dereferenceable }
// Turn ptr into place.
// `ref_to_mplace` also calls the machine hook for (re)activating the tag,
// which in turn will (in full miri) check if the pointer is dereferencable.
let place = self.ref_to_mplace(value)?;
// Recursive checking
if let Some(ref_tracking) = ref_tracking {
assert!(const_mode, "We should only do recursie checking in const mode");
if size != Size::ZERO { if size != Size::ZERO {
// Non-ZST also have to be dereferencable
let ptr = try_validation!(place.ptr.to_ptr(), let ptr = try_validation!(place.ptr.to_ptr(),
"integer pointer in non-ZST reference", path); "integer pointer in non-ZST reference", path);
if const_mode { // Skip validation entirely for some external statics
// Skip validation entirely for some external statics let alloc_kind = self.tcx.alloc_map.lock().get(ptr.alloc_id);
let alloc_kind = self.tcx.alloc_map.lock().get(ptr.alloc_id); if let Some(AllocType::Static(did)) = alloc_kind {
if let Some(AllocType::Static(did)) = alloc_kind { // `extern static` cannot be validated as they have no body.
// `extern static` cannot be validated as they have no body. // FIXME: Statics from other crates are also skipped.
// FIXME: Statics from other crates are also skipped. // They might be checked at a different type, but for now we
// They might be checked at a different type, but for now we // want to avoid recursing too deeply. This is not sound!
// want to avoid recursing too deeply. This is not sound! if !did.is_local() || self.tcx.is_foreign_item(did) {
if !did.is_local() || self.tcx.is_foreign_item(did) { return Ok(());
return Ok(());
}
} }
} }
// Maintain the invariant that the place we are checking is
// already verified to be in-bounds.
try_validation!(self.memory.check_bounds(ptr, size, false), try_validation!(self.memory.check_bounds(ptr, size, false),
"dangling (not entirely in bounds) reference", path); "dangling (not entirely in bounds) reference", path);
} }
if let Some(ref_tracking) = ref_tracking { // Check if we have encountered this pointer+layout combination
// Check if we have encountered this pointer+layout combination // before. Proceed recursively even for integer pointers, no
// before. Proceed recursively even for integer pointers, no // reason to skip them! They are (recursively) valid for some ZST,
// reason to skip them! They are (recursively) valid for some ZST, // but not for others (e.g. `!` is a ZST).
// but not for others (e.g. `!` is a ZST). let op = place.into();
let op = place.into(); if ref_tracking.seen.insert(op) {
if ref_tracking.seen.insert(op) { trace!("Recursing below ptr {:#?}", *op);
trace!("Recursing below ptr {:#?}", *op); ref_tracking.todo.push((op, path_clone_and_deref(path)));
ref_tracking.todo.push((op, path_clone_and_deref(path)));
}
} }
} }
} }