validity in non-const mode relies on ref_to_mplace checking bounds; (de)reference hooks work on places
This commit is contained in:
parent
048900c5b6
commit
f5e8830278
4 changed files with 119 additions and 121 deletions
|
@ -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
|
||||||
|
|
|
@ -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]
|
||||||
|
|
|
@ -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()),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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)));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue