generalize the traversal part of validation to a ValueVisitor
This commit is contained in:
parent
0117b42f66
commit
5b5e076b47
@ -535,14 +535,14 @@ fn validate_const<'a, 'tcx>(
|
|||||||
key: ty::ParamEnvAnd<'tcx, GlobalId<'tcx>>,
|
key: ty::ParamEnvAnd<'tcx, GlobalId<'tcx>>,
|
||||||
) -> ::rustc::mir::interpret::ConstEvalResult<'tcx> {
|
) -> ::rustc::mir::interpret::ConstEvalResult<'tcx> {
|
||||||
let cid = key.value;
|
let cid = key.value;
|
||||||
let ecx = mk_eval_cx(tcx, cid.instance, key.param_env).unwrap();
|
let mut ecx = mk_eval_cx(tcx, cid.instance, key.param_env).unwrap();
|
||||||
let val = (|| {
|
let val = (|| {
|
||||||
let op = ecx.const_to_op(constant)?;
|
let op = ecx.const_to_op(constant)?;
|
||||||
let mut ref_tracking = RefTracking::new(op);
|
let mut ref_tracking = RefTracking::new(op);
|
||||||
while let Some((op, mut path)) = ref_tracking.todo.pop() {
|
while let Some((op, path)) = ref_tracking.todo.pop() {
|
||||||
ecx.validate_operand(
|
ecx.validate_operand(
|
||||||
op,
|
op,
|
||||||
&mut path,
|
path,
|
||||||
Some(&mut ref_tracking),
|
Some(&mut ref_tracking),
|
||||||
/* const_mode */ true,
|
/* const_mode */ true,
|
||||||
)?;
|
)?;
|
||||||
|
@ -521,7 +521,7 @@ impl<'a, 'mir, 'tcx: 'mir, M: Machine<'a, 'mir, 'tcx>> EvalContext<'a, 'mir, 'tc
|
|||||||
// return place is always a local and then this cannot happen.
|
// return place is always a local and then this cannot happen.
|
||||||
self.validate_operand(
|
self.validate_operand(
|
||||||
self.place_to_op(return_place)?,
|
self.place_to_op(return_place)?,
|
||||||
&mut vec![],
|
vec![],
|
||||||
None,
|
None,
|
||||||
/*const_mode*/false,
|
/*const_mode*/false,
|
||||||
)?;
|
)?;
|
||||||
|
@ -23,6 +23,7 @@ mod terminator;
|
|||||||
mod traits;
|
mod traits;
|
||||||
mod validity;
|
mod validity;
|
||||||
mod intrinsics;
|
mod intrinsics;
|
||||||
|
mod visitor;
|
||||||
|
|
||||||
pub use rustc::mir::interpret::*; // have all the `interpret` symbols in one place: here
|
pub use rustc::mir::interpret::*; // have all the `interpret` symbols in one place: here
|
||||||
|
|
||||||
@ -38,4 +39,6 @@ pub use self::machine::{Machine, AllocMap, MayLeak};
|
|||||||
|
|
||||||
pub use self::operand::{ScalarMaybeUndef, Immediate, ImmTy, Operand, OpTy};
|
pub use self::operand::{ScalarMaybeUndef, Immediate, ImmTy, Operand, OpTy};
|
||||||
|
|
||||||
|
pub use self::visitor::ValueVisitor;
|
||||||
|
|
||||||
pub use self::validity::RefTracking;
|
pub use self::validity::RefTracking;
|
||||||
|
@ -489,6 +489,8 @@ where
|
|||||||
|
|
||||||
/// Get the place of a field inside the place, and also the field's type.
|
/// Get the place of a field inside the place, and also the field's type.
|
||||||
/// Just a convenience function, but used quite a bit.
|
/// Just a convenience function, but used quite a bit.
|
||||||
|
/// This is the only projection that might have a side-effect: We cannot project
|
||||||
|
/// into the field of a local `ScalarPair`, we have to first allocate it.
|
||||||
pub fn place_field(
|
pub fn place_field(
|
||||||
&mut self,
|
&mut self,
|
||||||
base: PlaceTy<'tcx, M::PointerTag>,
|
base: PlaceTy<'tcx, M::PointerTag>,
|
||||||
@ -501,7 +503,7 @@ where
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn place_downcast(
|
pub fn place_downcast(
|
||||||
&mut self,
|
&self,
|
||||||
base: PlaceTy<'tcx, M::PointerTag>,
|
base: PlaceTy<'tcx, M::PointerTag>,
|
||||||
variant: usize,
|
variant: usize,
|
||||||
) -> EvalResult<'tcx, PlaceTy<'tcx, M::PointerTag>> {
|
) -> EvalResult<'tcx, PlaceTy<'tcx, M::PointerTag>> {
|
||||||
@ -643,7 +645,7 @@ where
|
|||||||
|
|
||||||
if M::enforce_validity(self) {
|
if M::enforce_validity(self) {
|
||||||
// Data got changed, better make sure it matches the type!
|
// Data got changed, better make sure it matches the type!
|
||||||
self.validate_operand(self.place_to_op(dest)?, &mut vec![], None, /*const_mode*/false)?;
|
self.validate_operand(self.place_to_op(dest)?, vec![], None, /*const_mode*/false)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
@ -765,7 +767,7 @@ where
|
|||||||
|
|
||||||
if M::enforce_validity(self) {
|
if M::enforce_validity(self) {
|
||||||
// Data got changed, better make sure it matches the type!
|
// Data got changed, better make sure it matches the type!
|
||||||
self.validate_operand(self.place_to_op(dest)?, &mut vec![], None, /*const_mode*/false)?;
|
self.validate_operand(self.place_to_op(dest)?, vec![], None, /*const_mode*/false)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
@ -843,7 +845,7 @@ where
|
|||||||
|
|
||||||
if M::enforce_validity(self) {
|
if M::enforce_validity(self) {
|
||||||
// Data got changed, better make sure it matches the type!
|
// Data got changed, better make sure it matches the type!
|
||||||
self.validate_operand(dest.into(), &mut vec![], None, /*const_mode*/false)?;
|
self.validate_operand(dest.into(), vec![], None, /*const_mode*/false)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
@ -8,24 +8,24 @@
|
|||||||
// option. This file may not be copied, modified, or distributed
|
// option. This file may not be copied, modified, or distributed
|
||||||
// except according to those terms.
|
// except according to those terms.
|
||||||
|
|
||||||
use std::fmt::Write;
|
use std::fmt::{self, 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, LayoutOf};
|
use rustc::ty::layout::{self, Size, Align, TyLayout, LayoutOf};
|
||||||
use rustc::ty;
|
use rustc::ty::{self, TyCtxt};
|
||||||
use rustc_data_structures::fx::FxHashSet;
|
use rustc_data_structures::fx::FxHashSet;
|
||||||
use rustc::mir::interpret::{
|
use rustc::mir::interpret::{
|
||||||
Scalar, AllocType, EvalResult, EvalErrorKind
|
Scalar, AllocType, EvalResult, EvalErrorKind
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::{
|
use super::{
|
||||||
ImmTy, OpTy, MPlaceTy, Machine, EvalContext, ScalarMaybeUndef
|
OpTy, MPlaceTy, Machine, EvalContext, ScalarMaybeUndef, ValueVisitor
|
||||||
};
|
};
|
||||||
|
|
||||||
macro_rules! validation_failure {
|
macro_rules! validation_failure {
|
||||||
($what:expr, $where:expr, $details:expr) => {{
|
($what:expr, $where:expr, $details:expr) => {{
|
||||||
let where_ = path_format($where);
|
let where_ = path_format(&$where);
|
||||||
let where_ = if where_.is_empty() {
|
let where_ = if where_.is_empty() {
|
||||||
String::new()
|
String::new()
|
||||||
} else {
|
} else {
|
||||||
@ -37,7 +37,7 @@ macro_rules! validation_failure {
|
|||||||
)))
|
)))
|
||||||
}};
|
}};
|
||||||
($what:expr, $where:expr) => {{
|
($what:expr, $where:expr) => {{
|
||||||
let where_ = path_format($where);
|
let where_ = path_format(&$where);
|
||||||
let where_ = if where_.is_empty() {
|
let where_ = if where_.is_empty() {
|
||||||
String::new()
|
String::new()
|
||||||
} else {
|
} else {
|
||||||
@ -129,6 +129,43 @@ fn path_format(path: &Vec<PathElem>) -> String {
|
|||||||
out
|
out
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn aggregate_field_path_elem<'a, 'tcx>(
|
||||||
|
layout: TyLayout<'tcx>,
|
||||||
|
field: usize,
|
||||||
|
tcx: TyCtxt<'a, 'tcx, 'tcx>,
|
||||||
|
) -> PathElem {
|
||||||
|
match layout.ty.sty {
|
||||||
|
// generators and closures.
|
||||||
|
ty::Closure(def_id, _) | ty::Generator(def_id, _, _) => {
|
||||||
|
if let Some(upvar) = tcx.optimized_mir(def_id).upvar_decls.get(field) {
|
||||||
|
PathElem::ClosureVar(upvar.debug_name)
|
||||||
|
} else {
|
||||||
|
// Sometimes the index is beyond the number of freevars (seen
|
||||||
|
// for a generator).
|
||||||
|
PathElem::ClosureVar(Symbol::intern(&field.to_string()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// tuples
|
||||||
|
ty::Tuple(_) => PathElem::TupleElem(field),
|
||||||
|
|
||||||
|
// enums
|
||||||
|
ty::Adt(def, ..) if def.is_enum() => {
|
||||||
|
let variant = match layout.variants {
|
||||||
|
layout::Variants::Single { index } => &def.variants[index],
|
||||||
|
_ => bug!("aggregate_field_path_elem: got enum but not in a specific variant"),
|
||||||
|
};
|
||||||
|
PathElem::Field(variant.fields[field].ident.name)
|
||||||
|
}
|
||||||
|
|
||||||
|
// other ADTs
|
||||||
|
ty::Adt(def, _) => PathElem::Field(def.non_enum_variant().fields[field].ident.name),
|
||||||
|
|
||||||
|
// nothing else has an aggregate layout
|
||||||
|
_ => bug!("aggregate_field_path_elem: got non-aggregate type {:?}", layout.ty),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn scalar_format<Tag>(value: ScalarMaybeUndef<Tag>) -> String {
|
fn scalar_format<Tag>(value: ScalarMaybeUndef<Tag>) -> String {
|
||||||
match value {
|
match value {
|
||||||
ScalarMaybeUndef::Undef =>
|
ScalarMaybeUndef::Undef =>
|
||||||
@ -140,37 +177,92 @@ fn scalar_format<Tag>(value: ScalarMaybeUndef<Tag>) -> String {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a, 'mir, 'tcx, M: Machine<'a, 'mir, 'tcx>> EvalContext<'a, 'mir, 'tcx, M> {
|
struct ValidityVisitor<'rt, 'tcx, Tag> {
|
||||||
/// Make sure that `value` is valid for `ty`, *assuming* `ty` is a primitive type.
|
op: OpTy<'tcx, Tag>,
|
||||||
fn validate_primitive_type(
|
/// The `path` may be pushed to, but the part that is present when a function
|
||||||
&self,
|
/// starts must not be changed! `visit_fields` and `visit_array` rely on
|
||||||
value: ImmTy<'tcx, M::PointerTag>,
|
/// this stack discipline.
|
||||||
path: &Vec<PathElem>,
|
path: Vec<PathElem>,
|
||||||
ref_tracking: Option<&mut RefTracking<'tcx, M::PointerTag>>,
|
ref_tracking: Option<&'rt mut RefTracking<'tcx, Tag>>,
|
||||||
const_mode: bool,
|
const_mode: bool,
|
||||||
) -> EvalResult<'tcx> {
|
}
|
||||||
|
|
||||||
|
impl<Tag: fmt::Debug> fmt::Debug for ValidityVisitor<'_, '_, Tag> {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
write!(f, "{:?} ({:?})", *self.op, self.op.layout.ty)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'rt, 'a, 'mir, 'tcx, M: Machine<'a, 'mir, 'tcx>>
|
||||||
|
ValueVisitor<'a, 'mir, 'tcx, M> for ValidityVisitor<'rt, 'tcx, M::PointerTag>
|
||||||
|
{
|
||||||
|
#[inline(always)]
|
||||||
|
fn layout(&self) -> TyLayout<'tcx> {
|
||||||
|
self.op.layout
|
||||||
|
}
|
||||||
|
|
||||||
|
fn downcast_enum(&mut self, ectx: &EvalContext<'a, 'mir, 'tcx, M>)
|
||||||
|
-> EvalResult<'tcx>
|
||||||
|
{
|
||||||
|
let variant = match ectx.read_discriminant(self.op) {
|
||||||
|
Ok(res) => res.1,
|
||||||
|
Err(err) => return match err.kind {
|
||||||
|
EvalErrorKind::InvalidDiscriminant(val) =>
|
||||||
|
validation_failure!(
|
||||||
|
format!("invalid enum discriminant {}", val), self.path
|
||||||
|
),
|
||||||
|
_ =>
|
||||||
|
validation_failure!(
|
||||||
|
format!("non-integer enum discriminant"), self.path
|
||||||
|
),
|
||||||
|
}
|
||||||
|
};
|
||||||
|
// Put the variant projection onto the path, as a field
|
||||||
|
self.path.push(PathElem::Field(self.op.layout.ty
|
||||||
|
.ty_adt_def()
|
||||||
|
.unwrap()
|
||||||
|
.variants[variant].name));
|
||||||
|
// Proceed with this variant
|
||||||
|
self.op = ectx.operand_downcast(self.op, variant)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn downcast_dyn_trait(&mut self, ectx: &EvalContext<'a, 'mir, 'tcx, M>)
|
||||||
|
-> EvalResult<'tcx>
|
||||||
|
{
|
||||||
|
// FIXME: Should we reflect this in `self.path`?
|
||||||
|
let dest = self.op.to_mem_place(); // immediate trait objects are not a thing
|
||||||
|
self.op = ectx.unpack_dyn_trait(dest)?.1.into();
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn visit_primitive(&mut self, ectx: &mut EvalContext<'a, 'mir, 'tcx, M>)
|
||||||
|
-> EvalResult<'tcx>
|
||||||
|
{
|
||||||
|
let value = try_validation!(ectx.read_immediate(self.op),
|
||||||
|
"uninitialized or unrepresentable data", self.path);
|
||||||
// Go over all the primitive types
|
// Go over all the primitive types
|
||||||
let ty = value.layout.ty;
|
let ty = value.layout.ty;
|
||||||
match ty.sty {
|
match ty.sty {
|
||||||
ty::Bool => {
|
ty::Bool => {
|
||||||
let value = value.to_scalar_or_undef();
|
let value = value.to_scalar_or_undef();
|
||||||
try_validation!(value.to_bool(),
|
try_validation!(value.to_bool(),
|
||||||
scalar_format(value), path, "a boolean");
|
scalar_format(value), self.path, "a boolean");
|
||||||
},
|
},
|
||||||
ty::Char => {
|
ty::Char => {
|
||||||
let value = value.to_scalar_or_undef();
|
let value = value.to_scalar_or_undef();
|
||||||
try_validation!(value.to_char(),
|
try_validation!(value.to_char(),
|
||||||
scalar_format(value), path, "a valid unicode codepoint");
|
scalar_format(value), self.path, "a valid unicode codepoint");
|
||||||
},
|
},
|
||||||
ty::Float(_) | ty::Int(_) | ty::Uint(_) => {
|
ty::Float(_) | ty::Int(_) | ty::Uint(_) => {
|
||||||
// NOTE: Keep this in sync with the array optimization for int/float
|
// NOTE: Keep this in sync with the array optimization for int/float
|
||||||
// types below!
|
// types below!
|
||||||
let size = value.layout.size;
|
let size = value.layout.size;
|
||||||
let value = value.to_scalar_or_undef();
|
let value = value.to_scalar_or_undef();
|
||||||
if const_mode {
|
if self.const_mode {
|
||||||
// Integers/floats in CTFE: Must be scalar bits, pointers are dangerous
|
// Integers/floats in CTFE: Must be scalar bits, pointers are dangerous
|
||||||
try_validation!(value.to_bits(size),
|
try_validation!(value.to_bits(size),
|
||||||
scalar_format(value), path, "initialized plain bits");
|
scalar_format(value), self.path, "initialized plain bits");
|
||||||
} else {
|
} else {
|
||||||
// At run-time, for now, we accept *anything* for these types, including
|
// At run-time, for now, we accept *anything* for these types, including
|
||||||
// undef. We should fix that, but let's start low.
|
// undef. We should fix that, but let's start low.
|
||||||
@ -180,33 +272,33 @@ impl<'a, 'mir, 'tcx, M: Machine<'a, 'mir, 'tcx>> EvalContext<'a, 'mir, 'tcx, M>
|
|||||||
// No undef allowed here. Eventually this should be consistent with
|
// No undef allowed here. Eventually this should be consistent with
|
||||||
// the integer types.
|
// the integer types.
|
||||||
let _ptr = try_validation!(value.to_scalar_ptr(),
|
let _ptr = try_validation!(value.to_scalar_ptr(),
|
||||||
"undefined address in pointer", path);
|
"undefined address in pointer", self.path);
|
||||||
let _meta = try_validation!(value.to_meta(),
|
let _meta = try_validation!(value.to_meta(),
|
||||||
"uninitialized data in fat pointer metadata", path);
|
"uninitialized data in fat pointer metadata", self.path);
|
||||||
}
|
}
|
||||||
_ if ty.is_box() || ty.is_region_ptr() => {
|
_ if ty.is_box() || ty.is_region_ptr() => {
|
||||||
// Handle fat pointers.
|
// Handle fat pointers.
|
||||||
// Check metadata early, for better diagnostics
|
// Check metadata early, for better diagnostics
|
||||||
let ptr = try_validation!(value.to_scalar_ptr(),
|
let ptr = try_validation!(value.to_scalar_ptr(),
|
||||||
"undefined address in pointer", path);
|
"undefined address in pointer", self.path);
|
||||||
let meta = try_validation!(value.to_meta(),
|
let meta = try_validation!(value.to_meta(),
|
||||||
"uninitialized data in fat pointer metadata", path);
|
"uninitialized data in fat pointer metadata", self.path);
|
||||||
let layout = self.layout_of(value.layout.ty.builtin_deref(true).unwrap().ty)?;
|
let layout = ectx.layout_of(value.layout.ty.builtin_deref(true).unwrap().ty)?;
|
||||||
if layout.is_unsized() {
|
if layout.is_unsized() {
|
||||||
let tail = self.tcx.struct_tail(layout.ty);
|
let tail = ectx.tcx.struct_tail(layout.ty);
|
||||||
match tail.sty {
|
match tail.sty {
|
||||||
ty::Dynamic(..) => {
|
ty::Dynamic(..) => {
|
||||||
let vtable = try_validation!(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", self.path);
|
||||||
try_validation!(self.read_drop_type_from_vtable(vtable),
|
try_validation!(ectx.read_drop_type_from_vtable(vtable),
|
||||||
"invalid drop fn in vtable", path);
|
"invalid drop fn in vtable", self.path);
|
||||||
try_validation!(self.read_size_and_align_from_vtable(vtable),
|
try_validation!(ectx.read_size_and_align_from_vtable(vtable),
|
||||||
"invalid size or align in vtable", path);
|
"invalid size or align in vtable", self.path);
|
||||||
// FIXME: More checks for the vtable.
|
// FIXME: More checks for the vtable.
|
||||||
}
|
}
|
||||||
ty::Slice(..) | ty::Str => {
|
ty::Slice(..) | ty::Str => {
|
||||||
try_validation!(meta.unwrap().to_usize(self),
|
try_validation!(meta.unwrap().to_usize(ectx),
|
||||||
"non-integer slice length in fat pointer", path);
|
"non-integer slice length in fat pointer", self.path);
|
||||||
}
|
}
|
||||||
ty::Foreign(..) => {
|
ty::Foreign(..) => {
|
||||||
// Unsized, but not fat.
|
// Unsized, but not fat.
|
||||||
@ -216,25 +308,25 @@ impl<'a, 'mir, 'tcx, M: Machine<'a, 'mir, 'tcx>> EvalContext<'a, 'mir, 'tcx, M>
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Make sure this is non-NULL and aligned
|
// Make sure this is non-NULL and aligned
|
||||||
let (size, align) = self.size_and_align_of(meta, layout)?
|
let (size, align) = ectx.size_and_align_of(meta, layout)?
|
||||||
// for the purpose of validity, consider foreign types to have
|
// for the purpose of validity, consider foreign types to have
|
||||||
// alignment and size determined by the layout (size will be 0,
|
// alignment and size determined by the layout (size will be 0,
|
||||||
// alignment should take attributes into account).
|
// alignment should take attributes into account).
|
||||||
.unwrap_or_else(|| layout.size_and_align());
|
.unwrap_or_else(|| layout.size_and_align());
|
||||||
match self.memory.check_align(ptr, align) {
|
match ectx.memory.check_align(ptr, align) {
|
||||||
Ok(_) => {},
|
Ok(_) => {},
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
error!("{:?} is not aligned to {:?}", ptr, align);
|
error!("{:?} is not aligned to {:?}", ptr, align);
|
||||||
match err.kind {
|
match err.kind {
|
||||||
EvalErrorKind::InvalidNullPointerUsage =>
|
EvalErrorKind::InvalidNullPointerUsage =>
|
||||||
return validation_failure!("NULL reference", path),
|
return validation_failure!("NULL reference", self.path),
|
||||||
EvalErrorKind::AlignmentCheckFailed { .. } =>
|
EvalErrorKind::AlignmentCheckFailed { .. } =>
|
||||||
return validation_failure!("unaligned reference", path),
|
return validation_failure!("unaligned reference", self.path),
|
||||||
_ =>
|
_ =>
|
||||||
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
|
self.path
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -242,29 +334,29 @@ impl<'a, 'mir, 'tcx, M: Machine<'a, 'mir, 'tcx>> EvalContext<'a, 'mir, 'tcx, M>
|
|||||||
// Turn ptr into place.
|
// Turn ptr into place.
|
||||||
// `ref_to_mplace` also calls the machine hook for (re)activating the tag,
|
// `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.
|
// which in turn will (in full miri) check if the pointer is dereferencable.
|
||||||
let place = self.ref_to_mplace(value)?;
|
let place = ectx.ref_to_mplace(value)?;
|
||||||
// Recursive checking
|
// Recursive checking
|
||||||
if let Some(ref_tracking) = ref_tracking {
|
if let Some(ref mut ref_tracking) = self.ref_tracking {
|
||||||
assert!(const_mode, "We should only do recursie checking in const mode");
|
assert!(self.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
|
// 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", self.path);
|
||||||
// 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 = ectx.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() || ectx.tcx.is_foreign_item(did) {
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Maintain the invariant that the place we are checking is
|
// Maintain the invariant that the place we are checking is
|
||||||
// already verified to be in-bounds.
|
// already verified to be in-bounds.
|
||||||
try_validation!(self.memory.check_bounds(ptr, size, false),
|
try_validation!(ectx.memory.check_bounds(ptr, size, false),
|
||||||
"dangling (not entirely in bounds) reference", path);
|
"dangling (not entirely in bounds) reference", self.path);
|
||||||
}
|
}
|
||||||
// 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
|
||||||
@ -273,16 +365,16 @@ impl<'a, 'mir, 'tcx, M: Machine<'a, 'mir, 'tcx>> EvalContext<'a, 'mir, 'tcx, M>
|
|||||||
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(&self.path)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ty::FnPtr(_sig) => {
|
ty::FnPtr(_sig) => {
|
||||||
let value = value.to_scalar_or_undef();
|
let value = value.to_scalar_or_undef();
|
||||||
let ptr = try_validation!(value.to_ptr(),
|
let ptr = try_validation!(value.to_ptr(),
|
||||||
scalar_format(value), path, "a pointer");
|
scalar_format(value), self.path, "a pointer");
|
||||||
let _fn = try_validation!(self.memory.get_fn(ptr),
|
let _fn = try_validation!(ectx.memory.get_fn(ptr),
|
||||||
scalar_format(value), path, "a function pointer");
|
scalar_format(value), self.path, "a function pointer");
|
||||||
// FIXME: Check if the signature matches
|
// FIXME: Check if the signature matches
|
||||||
}
|
}
|
||||||
// This should be all the primitive types
|
// This should be all the primitive types
|
||||||
@ -292,16 +384,15 @@ impl<'a, 'mir, 'tcx, M: Machine<'a, 'mir, 'tcx>> EvalContext<'a, 'mir, 'tcx, M>
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Make sure that `value` matches the
|
fn visit_scalar(&mut self, ectx: &mut EvalContext<'a, 'mir, 'tcx, M>, layout: &layout::Scalar)
|
||||||
fn validate_scalar_layout(
|
-> EvalResult<'tcx>
|
||||||
&self,
|
{
|
||||||
value: ScalarMaybeUndef<M::PointerTag>,
|
let value = try_validation!(ectx.read_scalar(self.op),
|
||||||
size: Size,
|
"uninitialized or unrepresentable data", self.path);
|
||||||
path: &Vec<PathElem>,
|
// Determine the allowed range
|
||||||
layout: &layout::Scalar,
|
|
||||||
) -> EvalResult<'tcx> {
|
|
||||||
let (lo, hi) = layout.valid_range.clone().into_inner();
|
let (lo, hi) = layout.valid_range.clone().into_inner();
|
||||||
let max_hi = u128::max_value() >> (128 - size.bits()); // as big as the size fits
|
// `max_hi` is as big as the size fits
|
||||||
|
let max_hi = u128::max_value() >> (128 - self.op.layout.size.bits());
|
||||||
assert!(hi <= max_hi);
|
assert!(hi <= max_hi);
|
||||||
// We could also write `(hi + 1) % (max_hi + 1) == lo` but `max_hi + 1` overflows for `u128`
|
// We could also write `(hi + 1) % (max_hi + 1) == lo` but `max_hi + 1` overflows for `u128`
|
||||||
if (lo == 0 && hi == max_hi) || (hi + 1 == lo) {
|
if (lo == 0 && hi == max_hi) || (hi + 1 == lo) {
|
||||||
@ -310,7 +401,8 @@ impl<'a, 'mir, 'tcx, M: Machine<'a, 'mir, 'tcx>> EvalContext<'a, 'mir, 'tcx, M>
|
|||||||
}
|
}
|
||||||
// At least one value is excluded. Get the bits.
|
// At least one value is excluded. Get the bits.
|
||||||
let value = try_validation!(value.not_undef(),
|
let value = try_validation!(value.not_undef(),
|
||||||
scalar_format(value), path, format!("something in the range {:?}", layout.valid_range));
|
scalar_format(value), self.path,
|
||||||
|
format!("something in the range {:?}", layout.valid_range));
|
||||||
let bits = match value {
|
let bits = match value {
|
||||||
Scalar::Ptr(ptr) => {
|
Scalar::Ptr(ptr) => {
|
||||||
if lo == 1 && hi == max_hi {
|
if lo == 1 && hi == max_hi {
|
||||||
@ -318,13 +410,13 @@ impl<'a, 'mir, 'tcx, M: Machine<'a, 'mir, 'tcx>> EvalContext<'a, 'mir, 'tcx, M>
|
|||||||
// We can call `check_align` to check non-NULL-ness, but have to also look
|
// We can call `check_align` to check non-NULL-ness, but have to also look
|
||||||
// for function pointers.
|
// for function pointers.
|
||||||
let non_null =
|
let non_null =
|
||||||
self.memory.check_align(
|
ectx.memory.check_align(
|
||||||
Scalar::Ptr(ptr), Align::from_bytes(1, 1).unwrap()
|
Scalar::Ptr(ptr), Align::from_bytes(1, 1).unwrap()
|
||||||
).is_ok() ||
|
).is_ok() ||
|
||||||
self.memory.get_fn(ptr).is_ok();
|
ectx.memory.get_fn(ptr).is_ok();
|
||||||
if !non_null {
|
if !non_null {
|
||||||
// could be NULL
|
// could be NULL
|
||||||
return validation_failure!("a potentially NULL pointer", path);
|
return validation_failure!("a potentially NULL pointer", self.path);
|
||||||
}
|
}
|
||||||
return Ok(());
|
return Ok(());
|
||||||
} else {
|
} else {
|
||||||
@ -332,7 +424,7 @@ impl<'a, 'mir, 'tcx, M: Machine<'a, 'mir, 'tcx>> EvalContext<'a, 'mir, 'tcx, M>
|
|||||||
// value.
|
// value.
|
||||||
return validation_failure!(
|
return validation_failure!(
|
||||||
"a pointer",
|
"a pointer",
|
||||||
path,
|
self.path,
|
||||||
format!(
|
format!(
|
||||||
"something that cannot possibly be outside the (wrapping) range {:?}",
|
"something that cannot possibly be outside the (wrapping) range {:?}",
|
||||||
layout.valid_range
|
layout.valid_range
|
||||||
@ -340,8 +432,8 @@ impl<'a, 'mir, 'tcx, M: Machine<'a, 'mir, 'tcx>> EvalContext<'a, 'mir, 'tcx, M>
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Scalar::Bits { bits, size: value_size } => {
|
Scalar::Bits { bits, size } => {
|
||||||
assert_eq!(value_size as u64, size.bytes());
|
assert_eq!(size as u64, self.op.layout.size.bytes());
|
||||||
bits
|
bits
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -355,7 +447,7 @@ impl<'a, 'mir, 'tcx, M: Machine<'a, 'mir, 'tcx>> EvalContext<'a, 'mir, 'tcx, M>
|
|||||||
} else {
|
} else {
|
||||||
validation_failure!(
|
validation_failure!(
|
||||||
bits,
|
bits,
|
||||||
path,
|
self.path,
|
||||||
format!("something in the range {:?} or {:?}", 0..=hi, lo..=max_hi)
|
format!("something in the range {:?} or {:?}", 0..=hi, lo..=max_hi)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -365,7 +457,7 @@ impl<'a, 'mir, 'tcx, M: Machine<'a, 'mir, 'tcx>> EvalContext<'a, 'mir, 'tcx, M>
|
|||||||
} else {
|
} else {
|
||||||
validation_failure!(
|
validation_failure!(
|
||||||
bits,
|
bits,
|
||||||
path,
|
self.path,
|
||||||
if hi == max_hi {
|
if hi == max_hi {
|
||||||
format!("something greater or equal to {}", lo)
|
format!("something greater or equal to {}", lo)
|
||||||
} else {
|
} else {
|
||||||
@ -376,250 +468,147 @@ impl<'a, 'mir, 'tcx, M: Machine<'a, 'mir, 'tcx>> EvalContext<'a, 'mir, 'tcx, M>
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn visit_fields(&mut self, ectx: &mut EvalContext<'a, 'mir, 'tcx, M>, num_fields: usize)
|
||||||
|
-> EvalResult<'tcx>
|
||||||
|
{
|
||||||
|
// Remember some stuff that will change for the recursive calls
|
||||||
|
let op = self.op;
|
||||||
|
let path_len = self.path.len();
|
||||||
|
// Go look at all the fields
|
||||||
|
for i in 0..num_fields {
|
||||||
|
// Adapt our state
|
||||||
|
self.op = ectx.operand_field(op, i as u64)?;
|
||||||
|
self.path.push(aggregate_field_path_elem(op.layout, i, *ectx.tcx));
|
||||||
|
// Recursive visit
|
||||||
|
ectx.visit_value(self)?;
|
||||||
|
// Restore original state
|
||||||
|
self.op = op;
|
||||||
|
self.path.truncate(path_len);
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn visit_str(&mut self, ectx: &mut EvalContext<'a, 'mir, 'tcx, M>)
|
||||||
|
-> EvalResult<'tcx>
|
||||||
|
{
|
||||||
|
let mplace = self.op.to_mem_place(); // strings are never immediate
|
||||||
|
try_validation!(ectx.read_str(mplace),
|
||||||
|
"uninitialized or non-UTF-8 data in str", self.path);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn visit_array(&mut self, ectx: &mut EvalContext<'a, 'mir, 'tcx, M>) -> EvalResult<'tcx>
|
||||||
|
{
|
||||||
|
let mplace = if self.op.layout.is_zst() {
|
||||||
|
// it's a ZST, the memory content cannot matter
|
||||||
|
MPlaceTy::dangling(self.op.layout, ectx)
|
||||||
|
} else {
|
||||||
|
// non-ZST array/slice/str cannot be immediate
|
||||||
|
self.op.to_mem_place()
|
||||||
|
};
|
||||||
|
match self.op.layout.ty.sty {
|
||||||
|
ty::Str => bug!("Strings should be handled separately"),
|
||||||
|
// Special handling for arrays/slices of builtin integer types
|
||||||
|
ty::Array(tys, ..) | ty::Slice(tys) if {
|
||||||
|
// This optimization applies only for integer and floating point types
|
||||||
|
// (i.e., types that can hold arbitrary bytes).
|
||||||
|
match tys.sty {
|
||||||
|
ty::Int(..) | ty::Uint(..) | ty::Float(..) => true,
|
||||||
|
_ => false,
|
||||||
|
}
|
||||||
|
} => {
|
||||||
|
// This is the length of the array/slice.
|
||||||
|
let len = mplace.len(ectx)?;
|
||||||
|
// This is the element type size.
|
||||||
|
let ty_size = ectx.layout_of(tys)?.size;
|
||||||
|
// This is the size in bytes of the whole array.
|
||||||
|
let size = ty_size * len;
|
||||||
|
|
||||||
|
// NOTE: Keep this in sync with the handling of integer and float
|
||||||
|
// types above, in `visit_primitive`.
|
||||||
|
// In run-time mode, we accept pointers in here. This is actually more
|
||||||
|
// permissive than a per-element check would be, e.g. we accept
|
||||||
|
// an &[u8] that contains a pointer even though bytewise checking would
|
||||||
|
// reject it. However, that's good: We don't inherently want
|
||||||
|
// to reject those pointers, we just do not have the machinery to
|
||||||
|
// talk about parts of a pointer.
|
||||||
|
// We also accept undef, for consistency with the type-based checks.
|
||||||
|
match ectx.memory.check_bytes(
|
||||||
|
mplace.ptr,
|
||||||
|
size,
|
||||||
|
/*allow_ptr_and_undef*/!self.const_mode,
|
||||||
|
) {
|
||||||
|
// In the happy case, we needn't check anything else.
|
||||||
|
Ok(()) => {},
|
||||||
|
// Some error happened, try to provide a more detailed description.
|
||||||
|
Err(err) => {
|
||||||
|
// For some errors we might be able to provide extra information
|
||||||
|
match err.kind {
|
||||||
|
EvalErrorKind::ReadUndefBytes(offset) => {
|
||||||
|
// Some byte was undefined, determine which
|
||||||
|
// element that byte belongs to so we can
|
||||||
|
// provide an index.
|
||||||
|
let i = (offset.bytes() / ty_size.bytes()) as usize;
|
||||||
|
self.path.push(PathElem::ArrayElem(i));
|
||||||
|
|
||||||
|
return validation_failure!(
|
||||||
|
"undefined bytes", self.path
|
||||||
|
)
|
||||||
|
},
|
||||||
|
// Other errors shouldn't be possible
|
||||||
|
_ => return Err(err),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
_ => {
|
||||||
|
// Remember some stuff that will change for the recursive calls
|
||||||
|
let op = self.op;
|
||||||
|
let path_len = self.path.len();
|
||||||
|
// This handles the unsized case correctly as well, as well as
|
||||||
|
// SIMD and all sorts of other array-like types.
|
||||||
|
for (i, field) in ectx.mplace_array_fields(mplace)?.enumerate() {
|
||||||
|
// Adapt our state
|
||||||
|
self.op = field?.into();
|
||||||
|
self.path.push(PathElem::ArrayElem(i));
|
||||||
|
// Recursive visit
|
||||||
|
ectx.visit_value(self)?;
|
||||||
|
// Restore original state
|
||||||
|
self.op = op;
|
||||||
|
self.path.truncate(path_len);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, 'mir, 'tcx, M: Machine<'a, 'mir, 'tcx>> EvalContext<'a, 'mir, 'tcx, M> {
|
||||||
/// This function checks the data at `op`. `op` is assumed to cover valid memory if it
|
/// This function checks the data at `op`. `op` is assumed to cover valid memory if it
|
||||||
/// is an indirect operand.
|
/// is an indirect operand.
|
||||||
/// It will error if the bits at the destination do not match the ones described by the layout.
|
/// 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
|
|
||||||
/// starts must not be changed!
|
|
||||||
///
|
///
|
||||||
/// `ref_tracking` can be None to avoid recursive checking below references.
|
/// `ref_tracking` can be None to avoid recursive checking below references.
|
||||||
/// This also toggles between "run-time" (no recursion) and "compile-time" (with recursion)
|
/// This also toggles between "run-time" (no recursion) and "compile-time" (with recursion)
|
||||||
/// validation (e.g., pointer values are fine in integers at runtime).
|
/// validation (e.g., pointer values are fine in integers at runtime).
|
||||||
pub fn validate_operand(
|
pub fn validate_operand(
|
||||||
&self,
|
&mut self,
|
||||||
dest: OpTy<'tcx, M::PointerTag>,
|
op: OpTy<'tcx, M::PointerTag>,
|
||||||
path: &mut Vec<PathElem>,
|
path: Vec<PathElem>,
|
||||||
mut ref_tracking: Option<&mut RefTracking<'tcx, M::PointerTag>>,
|
ref_tracking: Option<&mut RefTracking<'tcx, M::PointerTag>>,
|
||||||
const_mode: bool,
|
const_mode: bool,
|
||||||
) -> EvalResult<'tcx> {
|
) -> EvalResult<'tcx> {
|
||||||
trace!("validate_operand: {:?}, {:?}", *dest, dest.layout.ty);
|
trace!("validate_operand: {:?}, {:?}", *op, op.layout.ty);
|
||||||
|
|
||||||
// If this is a multi-variant layout, we have find the right one and proceed with that.
|
// Construct a visitor
|
||||||
// (No good reasoning to make this recursion, but it is equivalent to that.)
|
let mut visitor = ValidityVisitor {
|
||||||
let dest = match dest.layout.variants {
|
op,
|
||||||
layout::Variants::NicheFilling { .. } |
|
path,
|
||||||
layout::Variants::Tagged { .. } => {
|
ref_tracking,
|
||||||
let variant = match self.read_discriminant(dest) {
|
const_mode
|
||||||
Ok(res) => res.1,
|
|
||||||
Err(err) => match err.kind {
|
|
||||||
EvalErrorKind::InvalidDiscriminant(val) =>
|
|
||||||
return validation_failure!(
|
|
||||||
format!("invalid enum discriminant {}", val), path
|
|
||||||
),
|
|
||||||
_ =>
|
|
||||||
return validation_failure!(
|
|
||||||
String::from("non-integer enum discriminant"), path
|
|
||||||
),
|
|
||||||
}
|
|
||||||
};
|
|
||||||
// Put the variant projection onto the path, as a field
|
|
||||||
path.push(PathElem::Field(dest.layout.ty
|
|
||||||
.ty_adt_def()
|
|
||||||
.unwrap()
|
|
||||||
.variants[variant].name));
|
|
||||||
// Proceed with this variant
|
|
||||||
let dest = self.operand_downcast(dest, variant)?;
|
|
||||||
trace!("variant layout: {:#?}", dest.layout);
|
|
||||||
dest
|
|
||||||
},
|
|
||||||
layout::Variants::Single { .. } => dest,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// First thing, find the real type:
|
// Run it
|
||||||
// If it is a trait object, switch to the actual type that was used to create it.
|
self.visit_value(&mut visitor)
|
||||||
let dest = match dest.layout.ty.sty {
|
|
||||||
ty::Dynamic(..) => {
|
|
||||||
let dest = dest.to_mem_place(); // immediate trait objects are not a thing
|
|
||||||
self.unpack_dyn_trait(dest)?.1.into()
|
|
||||||
},
|
|
||||||
_ => dest
|
|
||||||
};
|
|
||||||
|
|
||||||
// If this is a scalar, validate the scalar layout.
|
|
||||||
// Things can be aggregates and have scalar layout at the same time, and that
|
|
||||||
// is very relevant for `NonNull` and similar structs: We need to validate them
|
|
||||||
// at their scalar layout *before* descending into their fields.
|
|
||||||
// FIXME: We could avoid some redundant checks here. For newtypes wrapping
|
|
||||||
// scalars, we do the same check on every "level" (e.g. first we check
|
|
||||||
// MyNewtype and then the scalar in there).
|
|
||||||
match dest.layout.abi {
|
|
||||||
layout::Abi::Uninhabited =>
|
|
||||||
return validation_failure!("a value of an uninhabited type", path),
|
|
||||||
layout::Abi::Scalar(ref layout) => {
|
|
||||||
let value = try_validation!(self.read_scalar(dest),
|
|
||||||
"uninitialized or unrepresentable data", path);
|
|
||||||
self.validate_scalar_layout(value, dest.layout.size, &path, layout)?;
|
|
||||||
}
|
|
||||||
// FIXME: Should we do something for ScalarPair? Vector?
|
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check primitive types. We do this after checking the scalar layout,
|
|
||||||
// just to have that done as well. Primitives can have varying layout,
|
|
||||||
// so we check them separately and before aggregate handling.
|
|
||||||
// It is CRITICAL that we get this check right, or we might be
|
|
||||||
// validating the wrong thing!
|
|
||||||
let primitive = match dest.layout.fields {
|
|
||||||
// Primitives appear as Union with 0 fields -- except for fat pointers.
|
|
||||||
layout::FieldPlacement::Union(0) => true,
|
|
||||||
_ => dest.layout.ty.builtin_deref(true).is_some(),
|
|
||||||
};
|
|
||||||
if primitive {
|
|
||||||
let value = try_validation!(self.read_immediate(dest),
|
|
||||||
"uninitialized or unrepresentable data", path);
|
|
||||||
return self.validate_primitive_type(
|
|
||||||
value,
|
|
||||||
&path,
|
|
||||||
ref_tracking,
|
|
||||||
const_mode,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Validate all fields of compound data structures
|
|
||||||
let path_len = path.len(); // Remember the length, in case we need to truncate
|
|
||||||
match dest.layout.fields {
|
|
||||||
layout::FieldPlacement::Union(fields) => {
|
|
||||||
// Empty unions are not accepted by rustc. That's great, it means we can
|
|
||||||
// use that as an unambiguous signal for detecting primitives. Make sure
|
|
||||||
// we did not miss any primitive.
|
|
||||||
debug_assert!(fields > 0);
|
|
||||||
// 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
|
|
||||||
},
|
|
||||||
layout::FieldPlacement::Arbitrary { ref offsets, .. } => {
|
|
||||||
// Go look at all the fields
|
|
||||||
for i in 0..offsets.len() {
|
|
||||||
let field = self.operand_field(dest, i as u64)?;
|
|
||||||
path.push(self.aggregate_field_path_elem(dest.layout, i));
|
|
||||||
self.validate_operand(
|
|
||||||
field,
|
|
||||||
path,
|
|
||||||
ref_tracking.as_mut().map(|r| &mut **r),
|
|
||||||
const_mode,
|
|
||||||
)?;
|
|
||||||
path.truncate(path_len);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
layout::FieldPlacement::Array { stride, .. } => {
|
|
||||||
let dest = if dest.layout.is_zst() {
|
|
||||||
// it's a ZST, the memory content cannot matter
|
|
||||||
MPlaceTy::dangling(dest.layout, self)
|
|
||||||
} else {
|
|
||||||
// non-ZST array/slice/str cannot be immediate
|
|
||||||
dest.to_mem_place()
|
|
||||||
};
|
|
||||||
match dest.layout.ty.sty {
|
|
||||||
// Special handling for strings to verify UTF-8
|
|
||||||
ty::Str => {
|
|
||||||
try_validation!(self.read_str(dest),
|
|
||||||
"uninitialized or non-UTF-8 data in str", path);
|
|
||||||
}
|
|
||||||
// Special handling for arrays/slices of builtin integer types
|
|
||||||
ty::Array(tys, ..) | ty::Slice(tys) if {
|
|
||||||
// This optimization applies only for integer and floating point types
|
|
||||||
// (i.e., types that can hold arbitrary bytes).
|
|
||||||
match tys.sty {
|
|
||||||
ty::Int(..) | ty::Uint(..) | ty::Float(..) => true,
|
|
||||||
_ => false,
|
|
||||||
}
|
|
||||||
} => {
|
|
||||||
// This is the length of the array/slice.
|
|
||||||
let len = dest.len(self)?;
|
|
||||||
// Since primitive types are naturally aligned and tightly packed in arrays,
|
|
||||||
// we can use the stride to get the size of the integral type.
|
|
||||||
let ty_size = stride.bytes();
|
|
||||||
// This is the size in bytes of the whole array.
|
|
||||||
let size = Size::from_bytes(ty_size * len);
|
|
||||||
|
|
||||||
// NOTE: Keep this in sync with the handling of integer and float
|
|
||||||
// types above, in `validate_primitive_type`.
|
|
||||||
// In run-time mode, we accept pointers in here. This is actually more
|
|
||||||
// permissive than a per-element check would be, e.g. we accept
|
|
||||||
// an &[u8] that contains a pointer even though bytewise checking would
|
|
||||||
// reject it. However, that's good: We don't inherently want
|
|
||||||
// to reject those pointers, we just do not have the machinery to
|
|
||||||
// talk about parts of a pointer.
|
|
||||||
// We also accept undef, for consistency with the type-based checks.
|
|
||||||
match self.memory.check_bytes(
|
|
||||||
dest.ptr,
|
|
||||||
size,
|
|
||||||
/*allow_ptr_and_undef*/!const_mode,
|
|
||||||
) {
|
|
||||||
// In the happy case, we needn't check anything else.
|
|
||||||
Ok(()) => {},
|
|
||||||
// Some error happened, try to provide a more detailed description.
|
|
||||||
Err(err) => {
|
|
||||||
// For some errors we might be able to provide extra information
|
|
||||||
match err.kind {
|
|
||||||
EvalErrorKind::ReadUndefBytes(offset) => {
|
|
||||||
// Some byte was undefined, determine which
|
|
||||||
// element that byte belongs to so we can
|
|
||||||
// provide an index.
|
|
||||||
let i = (offset.bytes() / ty_size) as usize;
|
|
||||||
path.push(PathElem::ArrayElem(i));
|
|
||||||
|
|
||||||
return validation_failure!(
|
|
||||||
"undefined bytes", path
|
|
||||||
)
|
|
||||||
},
|
|
||||||
// Other errors shouldn't be possible
|
|
||||||
_ => return Err(err),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
_ => {
|
|
||||||
// This handles the unsized case correctly as well, as well as
|
|
||||||
// SIMD an all sorts of other array-like types.
|
|
||||||
for (i, field) in self.mplace_array_fields(dest)?.enumerate() {
|
|
||||||
let field = field?;
|
|
||||||
path.push(PathElem::ArrayElem(i));
|
|
||||||
self.validate_operand(
|
|
||||||
field.into(),
|
|
||||||
path,
|
|
||||||
ref_tracking.as_mut().map(|r| &mut **r),
|
|
||||||
const_mode,
|
|
||||||
)?;
|
|
||||||
path.truncate(path_len);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn aggregate_field_path_elem(&self, layout: TyLayout<'tcx>, field: usize) -> PathElem {
|
|
||||||
match layout.ty.sty {
|
|
||||||
// generators and closures.
|
|
||||||
ty::Closure(def_id, _) | ty::Generator(def_id, _, _) => {
|
|
||||||
if let Some(upvar) = self.tcx.optimized_mir(def_id).upvar_decls.get(field) {
|
|
||||||
PathElem::ClosureVar(upvar.debug_name)
|
|
||||||
} else {
|
|
||||||
// Sometimes the index is beyond the number of freevars (seen
|
|
||||||
// for a generator).
|
|
||||||
PathElem::ClosureVar(Symbol::intern(&field.to_string()))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// tuples
|
|
||||||
ty::Tuple(_) => PathElem::TupleElem(field),
|
|
||||||
|
|
||||||
// enums
|
|
||||||
ty::Adt(def, ..) if def.is_enum() => {
|
|
||||||
let variant = match layout.variants {
|
|
||||||
layout::Variants::Single { index } => &def.variants[index],
|
|
||||||
_ => bug!("aggregate_field_path_elem: got enum but not in a specific variant"),
|
|
||||||
};
|
|
||||||
PathElem::Field(variant.fields[field].ident.name)
|
|
||||||
}
|
|
||||||
|
|
||||||
// other ADTs
|
|
||||||
ty::Adt(def, _) => PathElem::Field(def.non_enum_variant().fields[field].ident.name),
|
|
||||||
|
|
||||||
// nothing else has an aggregate layout
|
|
||||||
_ => bug!("aggregate_field_path_elem: got non-aggregate type {:?}", layout.ty),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
125
src/librustc_mir/interpret/visitor.rs
Normal file
125
src/librustc_mir/interpret/visitor.rs
Normal file
@ -0,0 +1,125 @@
|
|||||||
|
//! Visitor for a run-time value with a given layout: Traverse enums, structs and other compound
|
||||||
|
//! types until we arrive at the leaves, with custom handling for primitive types.
|
||||||
|
|
||||||
|
use std::fmt;
|
||||||
|
|
||||||
|
use rustc::ty::layout::{self, TyLayout};
|
||||||
|
use rustc::ty;
|
||||||
|
use rustc::mir::interpret::{
|
||||||
|
EvalResult,
|
||||||
|
};
|
||||||
|
|
||||||
|
use super::{
|
||||||
|
Machine, EvalContext,
|
||||||
|
};
|
||||||
|
|
||||||
|
// How to traverse a value and what to do when we are at the leaves.
|
||||||
|
// In the future, we might want to turn this into two traits, but so far the
|
||||||
|
// only implementations we have couldn't share any code anyway.
|
||||||
|
pub trait ValueVisitor<'a, 'mir, 'tcx, M: Machine<'a, 'mir, 'tcx>>: fmt::Debug {
|
||||||
|
// Get this value's layout.
|
||||||
|
fn layout(&self) -> TyLayout<'tcx>;
|
||||||
|
|
||||||
|
// Downcast functions. These change the value as a side-effect.
|
||||||
|
fn downcast_enum(&mut self, ectx: &EvalContext<'a, 'mir, 'tcx, M>)
|
||||||
|
-> EvalResult<'tcx>;
|
||||||
|
fn downcast_dyn_trait(&mut self, ectx: &EvalContext<'a, 'mir, 'tcx, M>)
|
||||||
|
-> EvalResult<'tcx>;
|
||||||
|
|
||||||
|
// Visit all fields of a compound.
|
||||||
|
// Just call `visit_value` if you want to go on recursively.
|
||||||
|
fn visit_fields(&mut self, ectx: &mut EvalContext<'a, 'mir, 'tcx, M>, num_fields: usize)
|
||||||
|
-> EvalResult<'tcx>;
|
||||||
|
// Optimized handling for arrays -- avoid computing the layout for every field.
|
||||||
|
// Also it is the value's responsibility to figure out the length.
|
||||||
|
fn visit_array(&mut self, ectx: &mut EvalContext<'a, 'mir, 'tcx, M>) -> EvalResult<'tcx>;
|
||||||
|
// Special handling for strings.
|
||||||
|
fn visit_str(&mut self, ectx: &mut EvalContext<'a, 'mir, 'tcx, M>)
|
||||||
|
-> EvalResult<'tcx>;
|
||||||
|
|
||||||
|
// Actions on the leaves.
|
||||||
|
fn visit_scalar(&mut self, ectx: &mut EvalContext<'a, 'mir, 'tcx, M>, layout: &layout::Scalar)
|
||||||
|
-> EvalResult<'tcx>;
|
||||||
|
fn visit_primitive(&mut self, ectx: &mut EvalContext<'a, 'mir, 'tcx, M>)
|
||||||
|
-> EvalResult<'tcx>;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, 'mir, 'tcx, M: Machine<'a, 'mir, 'tcx>> EvalContext<'a, 'mir, 'tcx, M> {
|
||||||
|
pub fn visit_value<V: ValueVisitor<'a, 'mir, 'tcx, M>>(&mut self, v: &mut V) -> EvalResult<'tcx> {
|
||||||
|
trace!("visit_value: {:?}", v);
|
||||||
|
|
||||||
|
// If this is a multi-variant layout, we have find the right one and proceed with that.
|
||||||
|
// (No benefit from making this recursion, but it is equivalent to that.)
|
||||||
|
match v.layout().variants {
|
||||||
|
layout::Variants::NicheFilling { .. } |
|
||||||
|
layout::Variants::Tagged { .. } => {
|
||||||
|
v.downcast_enum(self)?;
|
||||||
|
trace!("variant layout: {:#?}", v.layout());
|
||||||
|
}
|
||||||
|
layout::Variants::Single { .. } => {}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Even for single variants, we might be able to get a more refined type:
|
||||||
|
// If it is a trait object, switch to the actual type that was used to create it.
|
||||||
|
match v.layout().ty.sty {
|
||||||
|
ty::Dynamic(..) => {
|
||||||
|
v.downcast_dyn_trait(self)?;
|
||||||
|
},
|
||||||
|
_ => {},
|
||||||
|
};
|
||||||
|
|
||||||
|
// If this is a scalar, visit it as such.
|
||||||
|
// Things can be aggregates and have scalar layout at the same time, and that
|
||||||
|
// is very relevant for `NonNull` and similar structs: We need to visit them
|
||||||
|
// at their scalar layout *before* descending into their fields.
|
||||||
|
// FIXME: We could avoid some redundant checks here. For newtypes wrapping
|
||||||
|
// scalars, we do the same check on every "level" (e.g. first we check
|
||||||
|
// MyNewtype and then the scalar in there).
|
||||||
|
match v.layout().abi {
|
||||||
|
layout::Abi::Scalar(ref layout) => {
|
||||||
|
v.visit_scalar(self, layout)?;
|
||||||
|
}
|
||||||
|
// FIXME: Should we do something for ScalarPair? Vector?
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check primitive types. We do this after checking the scalar layout,
|
||||||
|
// just to have that done as well. Primitives can have varying layout,
|
||||||
|
// so we check them separately and before aggregate handling.
|
||||||
|
// It is CRITICAL that we get this check right, or we might be
|
||||||
|
// validating the wrong thing!
|
||||||
|
let primitive = match v.layout().fields {
|
||||||
|
// Primitives appear as Union with 0 fields -- except for Boxes and fat pointers.
|
||||||
|
layout::FieldPlacement::Union(0) => true,
|
||||||
|
_ => v.layout().ty.builtin_deref(true).is_some(),
|
||||||
|
};
|
||||||
|
if primitive {
|
||||||
|
return v.visit_primitive(self);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Proceed into the fields.
|
||||||
|
match v.layout().fields {
|
||||||
|
layout::FieldPlacement::Union(fields) => {
|
||||||
|
// Empty unions are not accepted by rustc. That's great, it means we can
|
||||||
|
// use that as an unambiguous signal for detecting primitives. Make sure
|
||||||
|
// we did not miss any primitive.
|
||||||
|
debug_assert!(fields > 0);
|
||||||
|
// We can't traverse 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::Arbitrary { ref offsets, .. } => {
|
||||||
|
v.visit_fields(self, offsets.len())
|
||||||
|
},
|
||||||
|
layout::FieldPlacement::Array { .. } => {
|
||||||
|
match v.layout().ty.sty {
|
||||||
|
// Strings have properties that cannot be expressed pointwise.
|
||||||
|
ty::Str => v.visit_str(self),
|
||||||
|
// General case -- might also be SIMD vector or so
|
||||||
|
_ => v.visit_array(self),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user