Auto merge of #55549 - RalfJung:miri-visitor, r=oli-obk

Value visitors for miri

Generalize the traversal part of validation to a `ValueVisitor`.

~~This includes https://github.com/rust-lang/rust/pull/55316, [click here](https://github.com/RalfJung/rust/compare/retagging...RalfJung:miri-visitor) for just the new commits.~~
This commit is contained in:
bors 2018-11-07 09:14:12 +00:00
commit 8315b11b63
26 changed files with 977 additions and 630 deletions

View File

@ -15,7 +15,7 @@
use ty::layout::{Size, Align, LayoutError};
use rustc_target::spec::abi::Abi;
use super::Pointer;
use super::{Pointer, Scalar};
use backtrace::Backtrace;
@ -240,7 +240,7 @@ pub enum EvalErrorKind<'tcx, O> {
InvalidMemoryAccess,
InvalidFunctionPointer,
InvalidBool,
InvalidDiscriminant(u128),
InvalidDiscriminant(Scalar),
PointerOutOfBounds {
ptr: Pointer,
access: bool,

View File

@ -91,12 +91,33 @@ fn pointer_size(&self) -> Size {
}
//// Trunace the given value to the pointer size; also return whether there was an overflow
#[inline]
fn truncate_to_ptr(&self, val: u128) -> (u64, bool) {
let max_ptr_plus_1 = 1u128 << self.pointer_size().bits();
((val % max_ptr_plus_1) as u64, val >= max_ptr_plus_1)
}
#[inline]
fn offset<'tcx>(&self, val: u64, i: u64) -> EvalResult<'tcx, u64> {
let (res, over) = self.overflowing_offset(val, i);
if over { err!(Overflow(mir::BinOp::Add)) } else { Ok(res) }
}
#[inline]
fn overflowing_offset(&self, val: u64, i: u64) -> (u64, bool) {
let (res, over1) = val.overflowing_add(i);
let (res, over2) = self.truncate_to_ptr(u128::from(res));
(res, over1 || over2)
}
#[inline]
fn signed_offset<'tcx>(&self, val: u64, i: i64) -> EvalResult<'tcx, u64> {
let (res, over) = self.overflowing_signed_offset(val, i128::from(i));
if over { err!(Overflow(mir::BinOp::Add)) } else { Ok(res) }
}
// Overflow checking only works properly on the range from -u64 to +u64.
#[inline]
fn overflowing_signed_offset(&self, val: u64, i: i128) -> (u64, bool) {
// FIXME: is it possible to over/underflow here?
if i < 0 {
@ -108,26 +129,6 @@ fn overflowing_signed_offset(&self, val: u64, i: i128) -> (u64, bool) {
self.overflowing_offset(val, i as u64)
}
}
fn overflowing_offset(&self, val: u64, i: u64) -> (u64, bool) {
let (res, over1) = val.overflowing_add(i);
let (res, over2) = self.truncate_to_ptr(res as u128);
(res, over1 || over2)
}
fn signed_offset<'tcx>(&self, val: u64, i: i64) -> EvalResult<'tcx, u64> {
let (res, over) = self.overflowing_signed_offset(val, i as i128);
if over { err!(Overflow(mir::BinOp::Add)) } else { Ok(res) }
}
fn offset<'tcx>(&self, val: u64, i: u64) -> EvalResult<'tcx, u64> {
let (res, over) = self.overflowing_offset(val, i);
if over { err!(Overflow(mir::BinOp::Add)) } else { Ok(res) }
}
fn wrapping_signed_offset(&self, val: u64, i: i64) -> u64 {
self.overflowing_signed_offset(val, i as i128).0
}
}
impl<T: layout::HasDataLayout> PointerArithmetic for T {}
@ -176,32 +177,7 @@ pub fn new_with_tag(alloc_id: AllocId, offset: Size, tag: Tag) -> Self {
Pointer { alloc_id, offset, tag }
}
pub fn wrapping_signed_offset(self, i: i64, cx: &impl HasDataLayout) -> Self {
Pointer::new_with_tag(
self.alloc_id,
Size::from_bytes(cx.data_layout().wrapping_signed_offset(self.offset.bytes(), i)),
self.tag,
)
}
pub fn overflowing_signed_offset(self, i: i128, cx: &impl HasDataLayout) -> (Self, bool) {
let (res, over) = cx.data_layout().overflowing_signed_offset(self.offset.bytes(), i);
(Pointer::new_with_tag(self.alloc_id, Size::from_bytes(res), self.tag), over)
}
pub fn signed_offset(self, i: i64, cx: &impl HasDataLayout) -> EvalResult<'tcx, Self> {
Ok(Pointer::new_with_tag(
self.alloc_id,
Size::from_bytes(cx.data_layout().signed_offset(self.offset.bytes(), i)?),
self.tag,
))
}
pub fn overflowing_offset(self, i: Size, cx: &impl HasDataLayout) -> (Self, bool) {
let (res, over) = cx.data_layout().overflowing_offset(self.offset.bytes(), i.bytes());
(Pointer::new_with_tag(self.alloc_id, Size::from_bytes(res), self.tag), over)
}
#[inline]
pub fn offset(self, i: Size, cx: &impl HasDataLayout) -> EvalResult<'tcx, Self> {
Ok(Pointer::new_with_tag(
self.alloc_id,
@ -211,6 +187,37 @@ pub fn offset(self, i: Size, cx: &impl HasDataLayout) -> EvalResult<'tcx, Self>
}
#[inline]
pub fn overflowing_offset(self, i: Size, cx: &impl HasDataLayout) -> (Self, bool) {
let (res, over) = cx.data_layout().overflowing_offset(self.offset.bytes(), i.bytes());
(Pointer::new_with_tag(self.alloc_id, Size::from_bytes(res), self.tag), over)
}
#[inline(always)]
pub fn wrapping_offset(self, i: Size, cx: &impl HasDataLayout) -> Self {
self.overflowing_offset(i, cx).0
}
#[inline]
pub fn signed_offset(self, i: i64, cx: &impl HasDataLayout) -> EvalResult<'tcx, Self> {
Ok(Pointer::new_with_tag(
self.alloc_id,
Size::from_bytes(cx.data_layout().signed_offset(self.offset.bytes(), i)?),
self.tag,
))
}
#[inline]
pub fn overflowing_signed_offset(self, i: i128, cx: &impl HasDataLayout) -> (Self, bool) {
let (res, over) = cx.data_layout().overflowing_signed_offset(self.offset.bytes(), i);
(Pointer::new_with_tag(self.alloc_id, Size::from_bytes(res), self.tag), over)
}
#[inline(always)]
pub fn wrapping_signed_offset(self, i: i64, cx: &impl HasDataLayout) -> Self {
self.overflowing_signed_offset(i128::from(i), cx).0
}
#[inline(always)]
pub fn erase_tag(self) -> Pointer {
Pointer { alloc_id: self.alloc_id, offset: self.offset, tag: () }
}

View File

@ -8,7 +8,7 @@
// option. This file may not be copied, modified, or distributed
// except according to those terms.
#![allow(unknown_lints)]
use std::fmt;
use ty::layout::{HasDataLayout, Size};
use ty::subst::Substs;
@ -99,6 +99,15 @@ pub enum Scalar<Tag=(), Id=AllocId> {
Ptr(Pointer<Tag, Id>),
}
impl<Tag> fmt::Display for Scalar<Tag> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Scalar::Ptr(_) => write!(f, "a pointer"),
Scalar::Bits { bits, .. } => write!(f, "{}", bits),
}
}
}
impl<'tcx> Scalar<()> {
#[inline]
pub fn with_default_tag<Tag>(self) -> Scalar<Tag>
@ -133,21 +142,6 @@ pub fn zst() -> Self {
Scalar::Bits { bits: 0, size: 0 }
}
#[inline]
pub fn ptr_signed_offset(self, i: i64, cx: &impl HasDataLayout) -> EvalResult<'tcx, Self> {
let dl = cx.data_layout();
match self {
Scalar::Bits { bits, size } => {
assert_eq!(size as u64, dl.pointer_size.bytes());
Ok(Scalar::Bits {
bits: dl.signed_offset(bits as u64, i)? as u128,
size,
})
}
Scalar::Ptr(ptr) => ptr.signed_offset(i, dl).map(Scalar::Ptr),
}
}
#[inline]
pub fn ptr_offset(self, i: Size, cx: &impl HasDataLayout) -> EvalResult<'tcx, Self> {
let dl = cx.data_layout();
@ -163,6 +157,36 @@ pub fn ptr_offset(self, i: Size, cx: &impl HasDataLayout) -> EvalResult<'tcx, Se
}
}
#[inline]
pub fn ptr_wrapping_offset(self, i: Size, cx: &impl HasDataLayout) -> Self {
let dl = cx.data_layout();
match self {
Scalar::Bits { bits, size } => {
assert_eq!(size as u64, dl.pointer_size.bytes());
Scalar::Bits {
bits: dl.overflowing_offset(bits as u64, i.bytes()).0 as u128,
size,
}
}
Scalar::Ptr(ptr) => Scalar::Ptr(ptr.wrapping_offset(i, dl)),
}
}
#[inline]
pub fn ptr_signed_offset(self, i: i64, cx: &impl HasDataLayout) -> EvalResult<'tcx, Self> {
let dl = cx.data_layout();
match self {
Scalar::Bits { bits, size } => {
assert_eq!(size as u64, dl.pointer_size().bytes());
Ok(Scalar::Bits {
bits: dl.signed_offset(bits as u64, i)? as u128,
size,
})
}
Scalar::Ptr(ptr) => ptr.signed_offset(i, dl).map(Scalar::Ptr),
}
}
#[inline]
pub fn ptr_wrapping_signed_offset(self, i: i64, cx: &impl HasDataLayout) -> Self {
let dl = cx.data_layout();
@ -170,7 +194,7 @@ pub fn ptr_wrapping_signed_offset(self, i: i64, cx: &impl HasDataLayout) -> Self
Scalar::Bits { bits, size } => {
assert_eq!(size as u64, dl.pointer_size.bytes());
Scalar::Bits {
bits: dl.wrapping_signed_offset(bits as u64, i) as u128,
bits: dl.overflowing_signed_offset(bits as u64, i128::from(i)).0 as u128,
size,
}
}
@ -178,6 +202,19 @@ pub fn ptr_wrapping_signed_offset(self, i: i64, cx: &impl HasDataLayout) -> Self
}
}
/// Returns this pointers offset from the allocation base, or from NULL (for
/// integer pointers).
#[inline]
pub fn get_ptr_offset(self, cx: &impl HasDataLayout) -> Size {
match self {
Scalar::Bits { bits, size } => {
assert_eq!(size as u64, cx.pointer_size().bytes());
Size::from_bytes(bits as u64)
}
Scalar::Ptr(ptr) => ptr.offset,
}
}
#[inline]
pub fn is_null_ptr(self, cx: &impl HasDataLayout) -> bool {
match self {

View File

@ -539,10 +539,10 @@ fn validate_const<'a, 'tcx>(
let val = (|| {
let op = ecx.const_to_op(constant)?;
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(
op,
&mut path,
path,
Some(&mut ref_tracking),
/* const_mode */ true,
)?;

View File

@ -521,7 +521,7 @@ pub(super) fn pop_stack_frame(&mut self) -> EvalResult<'tcx> {
// return place is always a local and then this cannot happen.
self.validate_operand(
self.place_to_op(return_place)?,
&mut vec![],
vec![],
None,
/*const_mode*/false,
)?;

View File

@ -17,11 +17,11 @@
use rustc::hir::{self, def_id::DefId};
use rustc::mir;
use rustc::ty::{self, Ty, layout::{Size, TyLayout}, query::TyCtxtAt};
use rustc::ty::{self, layout::{Size, TyLayout}, query::TyCtxtAt};
use super::{
Allocation, AllocId, EvalResult, Scalar,
EvalContext, PlaceTy, OpTy, Pointer, MemPlace, MemoryKind,
EvalContext, PlaceTy, MPlaceTy, OpTy, Pointer, MemoryKind,
};
/// Whether this kind of memory is allowed to leak
@ -217,26 +217,22 @@ fn tag_new_allocation(
#[inline]
fn tag_reference(
_ecx: &mut EvalContext<'a, 'mir, 'tcx, Self>,
place: MemPlace<Self::PointerTag>,
_ty: Ty<'tcx>,
_size: Size,
place: MPlaceTy<'tcx, Self::PointerTag>,
_mutability: Option<hir::Mutability>,
) -> EvalResult<'tcx, MemPlace<Self::PointerTag>> {
Ok(place)
) -> EvalResult<'tcx, Scalar<Self::PointerTag>> {
Ok(place.ptr)
}
/// Executed when evaluating the `*` operator: Following a reference.
/// This has the change to adjust the tag. It should not change anything else!
/// 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 dereferenced.
#[inline]
fn tag_dereference(
_ecx: &EvalContext<'a, 'mir, 'tcx, Self>,
place: MemPlace<Self::PointerTag>,
_ty: Ty<'tcx>,
_size: Size,
place: MPlaceTy<'tcx, Self::PointerTag>,
_mutability: Option<hir::Mutability>,
) -> EvalResult<'tcx, MemPlace<Self::PointerTag>> {
Ok(place)
) -> EvalResult<'tcx, Scalar<Self::PointerTag>> {
Ok(place.ptr)
}
/// Execute a validation operation

View File

@ -23,6 +23,7 @@
mod traits;
mod validity;
mod intrinsics;
mod visitor;
pub use rustc::mir::interpret::*; // have all the `interpret` symbols in one place: here
@ -38,4 +39,6 @@
pub use self::operand::{ScalarMaybeUndef, Immediate, ImmTy, Operand, OpTy};
pub use self::visitor::ValueVisitor;
pub use self::validity::RefTracking;

View File

@ -12,6 +12,7 @@
//! All high-level functions to read from memory work on operands as sources.
use std::convert::TryInto;
use std::fmt;
use rustc::{mir, ty};
use rustc::ty::layout::{self, Size, LayoutOf, TyLayout, HasDataLayout, IntegerExt};
@ -36,6 +37,15 @@ fn from(s: Scalar<Tag>) -> Self {
}
}
impl<Tag> fmt::Display for ScalarMaybeUndef<Tag> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
ScalarMaybeUndef::Undef => write!(f, "uninitialized bytes"),
ScalarMaybeUndef::Scalar(s) => write!(f, "{}", s),
}
}
}
impl<'tcx> ScalarMaybeUndef<()> {
#[inline]
pub fn with_default_tag<Tag>(self) -> ScalarMaybeUndef<Tag>
@ -732,8 +742,12 @@ pub fn read_discriminant(
Ok(match rval.layout.variants {
layout::Variants::Single { .. } => bug!(),
layout::Variants::Tagged { .. } => {
let bits_discr = match raw_discr.to_bits(discr_val.layout.size) {
Ok(raw_discr) => raw_discr,
Err(_) => return err!(InvalidDiscriminant(raw_discr.erase_tag())),
};
let real_discr = if discr_val.layout.ty.is_signed() {
let i = raw_discr.to_bits(discr_val.layout.size)? as i128;
let i = bits_discr as i128;
// going from layout tag type to typeck discriminant type
// requires first sign extending with the layout discriminant
let shift = 128 - discr_val.layout.size.bits();
@ -748,7 +762,7 @@ pub fn read_discriminant(
let truncatee = sexted as u128;
(truncatee << shift) >> shift
} else {
raw_discr.to_bits(discr_val.layout.size)?
bits_discr
};
// Make sure we catch invalid discriminants
let index = rval.layout.ty
@ -756,7 +770,7 @@ pub fn read_discriminant(
.expect("tagged layout for non adt")
.discriminants(self.tcx.tcx)
.position(|var| var.val == real_discr)
.ok_or_else(|| EvalErrorKind::InvalidDiscriminant(real_discr))?;
.ok_or_else(|| EvalErrorKind::InvalidDiscriminant(raw_discr.erase_tag()))?;
(real_discr, index)
},
layout::Variants::NicheFilling {

View File

@ -278,11 +278,9 @@ pub fn ref_to_mplace(
let meta = val.to_meta()?;
let ptr = val.to_scalar_ptr()?;
let mplace = MemPlace { ptr, align, meta };
let mut mplace = MPlaceTy { mplace, layout };
// Pointer tag tracking might want to adjust the tag.
let mplace = if M::ENABLE_PTR_TRACKING_HOOKS {
let (size, _) = self.size_and_align_of(meta, layout)?
// for extern types, just cover what we can
.unwrap_or_else(|| layout.size_and_align());
if M::ENABLE_PTR_TRACKING_HOOKS {
let mutbl = match val.layout.ty.sty {
// `builtin_deref` considers boxes immutable, that's useless for our purposes
ty::Ref(_, _, mutbl) => Some(mutbl),
@ -290,11 +288,10 @@ pub fn ref_to_mplace(
ty::RawPtr(_) => None,
_ => bug!("Unexpected pointer type {}", val.layout.ty.sty),
};
M::tag_dereference(self, mplace, pointee_type, size, mutbl)?
} else {
mplace
};
Ok(MPlaceTy { mplace, layout })
mplace.mplace.ptr = M::tag_dereference(self, mplace, mutbl)?;
}
// Done
Ok(mplace)
}
/// Turn a mplace into a (thin or fat) pointer, as a reference, pointing to the same space.
@ -302,18 +299,13 @@ pub fn ref_to_mplace(
/// `mutbl` indicates whether we are create a shared or mutable ref, or a raw pointer (`None`).
pub fn create_ref(
&mut self,
place: MPlaceTy<'tcx, M::PointerTag>,
mut place: MPlaceTy<'tcx, M::PointerTag>,
mutbl: Option<hir::Mutability>,
) -> EvalResult<'tcx, Immediate<M::PointerTag>> {
// Pointer tag tracking might want to adjust the tag
let place = if M::ENABLE_PTR_TRACKING_HOOKS {
let (size, _) = self.size_and_align_of_mplace(place)?
// for extern types, just cover what we can
.unwrap_or_else(|| place.layout.size_and_align());
M::tag_reference(self, *place, place.layout.ty, size, mutbl)?
} else {
*place
};
if M::ENABLE_PTR_TRACKING_HOOKS {
place.mplace.ptr = M::tag_reference(self, place, mutbl)?
}
Ok(match place.meta {
None => Immediate::Scalar(place.ptr.into()),
Some(meta) => Immediate::ScalarPair(place.ptr.into(), meta.into()),
@ -489,6 +481,8 @@ pub fn mplace_projection(
/// Get the place of a field inside the place, and also the field's type.
/// 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(
&mut self,
base: PlaceTy<'tcx, M::PointerTag>,
@ -501,7 +495,7 @@ pub fn place_field(
}
pub fn place_downcast(
&mut self,
&self,
base: PlaceTy<'tcx, M::PointerTag>,
variant: usize,
) -> EvalResult<'tcx, PlaceTy<'tcx, M::PointerTag>> {
@ -643,7 +637,7 @@ pub fn write_immediate(
if M::enforce_validity(self) {
// 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(())
@ -765,7 +759,7 @@ pub fn copy_op(
if M::enforce_validity(self) {
// 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(())
@ -843,7 +837,7 @@ pub fn copy_op_transmute(
if M::enforce_validity(self) {
// 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(())

View File

@ -20,12 +20,12 @@
};
use super::{
ImmTy, OpTy, MPlaceTy, Machine, EvalContext, ScalarMaybeUndef
OpTy, MPlaceTy, ImmTy, Machine, EvalContext, ValueVisitor
};
macro_rules! validation_failure {
($what:expr, $where:expr, $details:expr) => {{
let where_ = path_format($where);
let where_ = path_format(&$where);
let where_ = if where_.is_empty() {
String::new()
} else {
@ -37,7 +37,7 @@ macro_rules! validation_failure {
)))
}};
($what:expr, $where:expr) => {{
let where_ = path_format($where);
let where_ = path_format(&$where);
let where_ = if where_.is_empty() {
String::new()
} else {
@ -78,6 +78,7 @@ pub enum PathElem {
TupleElem(usize),
Deref,
Tag,
DynDowncast,
}
/// State for tracking recursive validation of references
@ -97,15 +98,6 @@ pub fn new(op: OpTy<'tcx, Tag>) -> Self {
}
}
// 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> {
let mut new_path = Vec::with_capacity(path.len()+1);
new_path.clone_from(path);
new_path.push(PathElem::Deref);
new_path
}
/// Format a path
fn path_format(path: &Vec<PathElem>) -> String {
use self::PathElem::*;
@ -113,488 +105,43 @@ fn path_format(path: &Vec<PathElem>) -> String {
let mut out = String::new();
for elem in path.iter() {
match elem {
Field(name) => write!(out, ".{}", name).unwrap(),
ClosureVar(name) => write!(out, ".<closure-var({})>", name).unwrap(),
TupleElem(idx) => write!(out, ".{}", idx).unwrap(),
ArrayElem(idx) => write!(out, "[{}]", idx).unwrap(),
Field(name) => write!(out, ".{}", name),
ClosureVar(name) => write!(out, ".<closure-var({})>", name),
TupleElem(idx) => write!(out, ".{}", idx),
ArrayElem(idx) => write!(out, "[{}]", idx),
Deref =>
// This does not match Rust syntax, but it is more readable for long paths -- and
// some of the other items here also are not Rust syntax. Actually we can't
// even use the usual syntax because we are just showing the projections,
// not the root.
write!(out, ".<deref>").unwrap(),
Tag => write!(out, ".<enum-tag>").unwrap(),
}
write!(out, ".<deref>"),
Tag => write!(out, ".<enum-tag>"),
DynDowncast => write!(out, ".<dyn-downcast>"),
}.unwrap()
}
out
}
fn scalar_format<Tag>(value: ScalarMaybeUndef<Tag>) -> String {
match value {
ScalarMaybeUndef::Undef =>
"uninitialized bytes".to_owned(),
ScalarMaybeUndef::Scalar(Scalar::Ptr(_)) =>
"a pointer".to_owned(),
ScalarMaybeUndef::Scalar(Scalar::Bits { bits, .. }) =>
bits.to_string(),
}
struct ValidityVisitor<'rt, 'a: 'rt, 'mir: 'rt, 'tcx: 'a+'rt+'mir, M: Machine<'a, 'mir, 'tcx>+'rt> {
/// The `path` may be pushed to, but the part that is present when a function
/// starts must not be changed! `visit_fields` and `visit_array` rely on
/// this stack discipline.
path: Vec<PathElem>,
ref_tracking: Option<&'rt mut RefTracking<'tcx, M::PointerTag>>,
const_mode: bool,
ecx: &'rt EvalContext<'a, 'mir, 'tcx, M>,
}
impl<'a, 'mir, 'tcx, M: Machine<'a, 'mir, 'tcx>> EvalContext<'a, 'mir, 'tcx, M> {
/// Make sure that `value` is valid for `ty`, *assuming* `ty` is a primitive type.
fn validate_primitive_type(
&self,
value: ImmTy<'tcx, M::PointerTag>,
path: &Vec<PathElem>,
ref_tracking: Option<&mut RefTracking<'tcx, M::PointerTag>>,
const_mode: bool,
) -> EvalResult<'tcx> {
// Go over all the primitive types
let ty = value.layout.ty;
match ty.sty {
ty::Bool => {
let value = value.to_scalar_or_undef();
try_validation!(value.to_bool(),
scalar_format(value), path, "a boolean");
},
ty::Char => {
let value = value.to_scalar_or_undef();
try_validation!(value.to_char(),
scalar_format(value), path, "a valid unicode codepoint");
},
ty::Float(_) | ty::Int(_) | ty::Uint(_) => {
// NOTE: Keep this in sync with the array optimization for int/float
// types below!
let size = value.layout.size;
let value = value.to_scalar_or_undef();
if const_mode {
// Integers/floats in CTFE: Must be scalar bits, pointers are dangerous
try_validation!(value.to_bits(size),
scalar_format(value), path, "initialized plain bits");
} else {
// At run-time, for now, we accept *anything* for these types, including
// undef. We should fix that, but let's start low.
}
}
ty::RawPtr(..) => {
// No undef allowed here. Eventually this should be consistent with
// the integer types.
let _ptr = try_validation!(value.to_scalar_ptr(),
"undefined address in pointer", path);
let _meta = try_validation!(value.to_meta(),
"uninitialized data in fat pointer metadata", path);
}
_ if ty.is_box() || ty.is_region_ptr() => {
// Handle fat pointers.
// Check metadata early, for better diagnostics
let ptr = try_validation!(value.to_scalar_ptr(),
"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 {
ty::Dynamic(..) => {
let vtable = try_validation!(meta.unwrap().to_ptr(),
"non-pointer vtable in fat pointer", path);
try_validation!(self.read_drop_type_from_vtable(vtable),
"invalid drop fn in vtable", path);
try_validation!(self.read_size_and_align_from_vtable(vtable),
"invalid size or align in vtable", path);
// FIXME: More checks for the vtable.
}
ty::Slice(..) | ty::Str => {
try_validation!(meta.unwrap().to_usize(self),
"non-integer slice length in fat pointer", path);
}
ty::Foreign(..) => {
// Unsized, but not fat.
}
_ =>
bug!("Unexpected unsized type tail: {:?}", tail),
}
}
// Make sure this is non-NULL and aligned
let (size, align) = self.size_and_align_of(meta, layout)?
// for the purpose of validity, consider foreign types to have
// alignment and size determined by the layout (size will be 0,
// alignment should take attributes into account).
.unwrap_or_else(|| layout.size_and_align());
match self.memory.check_align(ptr, align) {
Ok(_) => {},
Err(err) => {
error!("{:?} is not aligned to {:?}", ptr, align);
match err.kind {
EvalErrorKind::InvalidNullPointerUsage =>
return validation_failure!("NULL reference", path),
EvalErrorKind::AlignmentCheckFailed { .. } =>
return validation_failure!("unaligned reference", path),
_ =>
return validation_failure!(
"dangling (out-of-bounds) reference (might be NULL at \
run-time)",
path
),
}
}
}
// 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 {
// Non-ZST also have to be dereferencable
let ptr = try_validation!(place.ptr.to_ptr(),
"integer pointer in non-ZST reference", path);
// Skip validation entirely for some external statics
let alloc_kind = self.tcx.alloc_map.lock().get(ptr.alloc_id);
if let Some(AllocType::Static(did)) = alloc_kind {
// `extern static` cannot be validated as they have no body.
// FIXME: Statics from other crates are also skipped.
// They might be checked at a different type, but for now we
// want to avoid recursing too deeply. This is not sound!
if !did.is_local() || self.tcx.is_foreign_item(did) {
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),
"dangling (not entirely in bounds) reference", path);
}
// Check if we have encountered this pointer+layout combination
// before. Proceed recursively even for integer pointers, no
// reason to skip them! They are (recursively) valid for some ZST,
// but not for others (e.g. `!` is a ZST).
let op = place.into();
if ref_tracking.seen.insert(op) {
trace!("Recursing below ptr {:#?}", *op);
ref_tracking.todo.push((op, path_clone_and_deref(path)));
}
}
}
ty::FnPtr(_sig) => {
let value = value.to_scalar_or_undef();
let ptr = try_validation!(value.to_ptr(),
scalar_format(value), path, "a pointer");
let _fn = try_validation!(self.memory.get_fn(ptr),
scalar_format(value), path, "a function pointer");
// FIXME: Check if the signature matches
}
// This should be all the primitive types
ty::Never => bug!("Uninhabited type should have been caught earlier"),
_ => bug!("Unexpected primitive type {}", value.layout.ty)
}
Ok(())
}
/// Make sure that `value` matches the
fn validate_scalar_layout(
&self,
value: ScalarMaybeUndef<M::PointerTag>,
size: Size,
path: &Vec<PathElem>,
layout: &layout::Scalar,
) -> EvalResult<'tcx> {
let (lo, hi) = layout.valid_range.clone().into_inner();
let max_hi = u128::max_value() >> (128 - size.bits()); // as big as the size fits
assert!(hi <= max_hi);
// 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) {
// Nothing to check
return Ok(());
}
// At least one value is excluded. Get the bits.
let value = try_validation!(value.not_undef(),
scalar_format(value), path, format!("something in the range {:?}", layout.valid_range));
let bits = match value {
Scalar::Ptr(ptr) => {
if lo == 1 && hi == max_hi {
// only NULL is not allowed.
// We can call `check_align` to check non-NULL-ness, but have to also look
// for function pointers.
let non_null =
self.memory.check_align(
Scalar::Ptr(ptr), Align::from_bytes(1, 1).unwrap()
).is_ok() ||
self.memory.get_fn(ptr).is_ok();
if !non_null {
// could be NULL
return validation_failure!("a potentially NULL pointer", path);
}
return Ok(());
} else {
// Conservatively, we reject, because the pointer *could* have this
// value.
return validation_failure!(
"a pointer",
path,
format!(
"something that cannot possibly be outside the (wrapping) range {:?}",
layout.valid_range
)
);
}
}
Scalar::Bits { bits, size: value_size } => {
assert_eq!(value_size as u64, size.bytes());
bits
}
};
// Now compare. This is slightly subtle because this is a special "wrap-around" range.
use std::ops::RangeInclusive;
let in_range = |bound: RangeInclusive<u128>| bound.contains(&bits);
if lo > hi {
// wrapping around
if in_range(0..=hi) || in_range(lo..=max_hi) {
Ok(())
} else {
validation_failure!(
bits,
path,
format!("something in the range {:?} or {:?}", 0..=hi, lo..=max_hi)
)
}
} else {
if in_range(layout.valid_range.clone()) {
Ok(())
} else {
validation_failure!(
bits,
path,
if hi == max_hi {
format!("something greater or equal to {}", lo)
} else {
format!("something in the range {:?}", layout.valid_range)
}
)
}
}
}
/// This function checks the data at `op`. `op` is assumed to cover valid memory if it
/// is an indirect operand.
/// 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.
/// This also toggles between "run-time" (no recursion) and "compile-time" (with recursion)
/// validation (e.g., pointer values are fine in integers at runtime).
pub fn validate_operand(
&self,
dest: OpTy<'tcx, M::PointerTag>,
path: &mut Vec<PathElem>,
mut ref_tracking: Option<&mut RefTracking<'tcx, M::PointerTag>>,
const_mode: bool,
) -> EvalResult<'tcx> {
trace!("validate_operand: {:?}, {:?}", *dest, dest.layout.ty);
// If this is a multi-variant layout, we have find the right one and proceed with that.
// (No good reasoning to make this recursion, but it is equivalent to that.)
let dest = match dest.layout.variants {
layout::Variants::NicheFilling { .. } |
layout::Variants::Tagged { .. } => {
let variant = match self.read_discriminant(dest) {
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:
// If it is a trait object, switch to the actual type that was used to create it.
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 {
impl<'rt, 'a, 'mir, 'tcx, M: Machine<'a, 'mir, 'tcx>> ValidityVisitor<'rt, 'a, 'mir, 'tcx, M> {
fn push_aggregate_field_path_elem(
&mut self,
layout: TyLayout<'tcx>,
field: usize,
) {
let elem = 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) {
if let Some(upvar) = self.ecx.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
@ -608,18 +155,425 @@ fn aggregate_field_path_elem(&self, layout: TyLayout<'tcx>, field: usize) -> Pat
// 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)
// we might be projecting *to* a variant, or to a field *in*a variant.
match layout.variants {
layout::Variants::Single { index } =>
// Inside a variant
PathElem::Field(def.variants[index].fields[field].ident.name),
_ =>
// To a variant
PathElem::Field(def.variants[field].name)
}
}
// other ADTs
ty::Adt(def, _) => PathElem::Field(def.non_enum_variant().fields[field].ident.name),
// arrays/slices
ty::Array(..) | ty::Slice(..) => PathElem::ArrayElem(field),
// dyn traits
ty::Dynamic(..) => PathElem::DynDowncast,
// nothing else has an aggregate layout
_ => bug!("aggregate_field_path_elem: got non-aggregate type {:?}", layout.ty),
}
};
self.path.push(elem);
}
}
impl<'rt, 'a, 'mir, 'tcx, M: Machine<'a, 'mir, 'tcx>>
ValueVisitor<'a, 'mir, 'tcx, M> for ValidityVisitor<'rt, 'a, 'mir, 'tcx, M>
{
type V = OpTy<'tcx, M::PointerTag>;
#[inline(always)]
fn ecx(&self) -> &EvalContext<'a, 'mir, 'tcx, M> {
&self.ecx
}
#[inline]
fn visit_field(
&mut self,
old_op: OpTy<'tcx, M::PointerTag>,
field: usize,
new_op: OpTy<'tcx, M::PointerTag>
) -> EvalResult<'tcx> {
// Remember the old state
let path_len = self.path.len();
// Perform operation
self.push_aggregate_field_path_elem(old_op.layout, field);
self.visit_value(new_op)?;
// Undo changes
self.path.truncate(path_len);
Ok(())
}
#[inline]
fn visit_value(&mut self, op: OpTy<'tcx, M::PointerTag>) -> EvalResult<'tcx>
{
trace!("visit_value: {:?}, {:?}", *op, op.layout);
// Translate some possible errors to something nicer.
match self.walk_value(op) {
Ok(()) => Ok(()),
Err(err) => match err.kind {
EvalErrorKind::InvalidDiscriminant(val) =>
validation_failure!(
val, self.path, "a valid enum discriminant"
),
EvalErrorKind::ReadPointerAsBytes =>
validation_failure!(
"a pointer", self.path, "plain bytes"
),
_ => Err(err),
}
}
}
fn visit_primitive(&mut self, value: ImmTy<'tcx, M::PointerTag>) -> EvalResult<'tcx>
{
// Go over all the primitive types
let ty = value.layout.ty;
match ty.sty {
ty::Bool => {
let value = value.to_scalar_or_undef();
try_validation!(value.to_bool(),
value, self.path, "a boolean");
},
ty::Char => {
let value = value.to_scalar_or_undef();
try_validation!(value.to_char(),
value, self.path, "a valid unicode codepoint");
},
ty::Float(_) | ty::Int(_) | ty::Uint(_) => {
// NOTE: Keep this in sync with the array optimization for int/float
// types below!
let size = value.layout.size;
let value = value.to_scalar_or_undef();
if self.const_mode {
// Integers/floats in CTFE: Must be scalar bits, pointers are dangerous
try_validation!(value.to_bits(size),
value, self.path, "initialized plain bits");
} else {
// At run-time, for now, we accept *anything* for these types, including
// undef. We should fix that, but let's start low.
}
}
ty::RawPtr(..) => {
// No undef allowed here. Eventually this should be consistent with
// the integer types.
let _ptr = try_validation!(value.to_scalar_ptr(),
"undefined address in pointer", self.path);
let _meta = try_validation!(value.to_meta(),
"uninitialized data in fat pointer metadata", self.path);
}
_ if ty.is_box() || ty.is_region_ptr() => {
// Handle fat pointers.
// Check metadata early, for better diagnostics
let ptr = try_validation!(value.to_scalar_ptr(),
"undefined address in pointer", self.path);
let meta = try_validation!(value.to_meta(),
"uninitialized data in fat pointer metadata", self.path);
let layout = self.ecx.layout_of(value.layout.ty.builtin_deref(true).unwrap().ty)?;
if layout.is_unsized() {
let tail = self.ecx.tcx.struct_tail(layout.ty);
match tail.sty {
ty::Dynamic(..) => {
let vtable = try_validation!(meta.unwrap().to_ptr(),
"non-pointer vtable in fat pointer", self.path);
try_validation!(self.ecx.read_drop_type_from_vtable(vtable),
"invalid drop fn in vtable", self.path);
try_validation!(self.ecx.read_size_and_align_from_vtable(vtable),
"invalid size or align in vtable", self.path);
// FIXME: More checks for the vtable.
}
ty::Slice(..) | ty::Str => {
try_validation!(meta.unwrap().to_usize(self.ecx),
"non-integer slice length in fat pointer", self.path);
}
ty::Foreign(..) => {
// Unsized, but not fat.
}
_ =>
bug!("Unexpected unsized type tail: {:?}", tail),
}
}
// Make sure this is non-NULL and aligned
let (size, align) = self.ecx.size_and_align_of(meta, layout)?
// for the purpose of validity, consider foreign types to have
// alignment and size determined by the layout (size will be 0,
// alignment should take attributes into account).
.unwrap_or_else(|| layout.size_and_align());
match self.ecx.memory.check_align(ptr, align) {
Ok(_) => {},
Err(err) => {
error!("{:?} is not aligned to {:?}", ptr, align);
match err.kind {
EvalErrorKind::InvalidNullPointerUsage =>
return validation_failure!("NULL reference", self.path),
EvalErrorKind::AlignmentCheckFailed { .. } =>
return validation_failure!("unaligned reference", self.path),
_ =>
return validation_failure!(
"dangling (out-of-bounds) reference (might be NULL at \
run-time)",
self.path
),
}
}
}
// 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.ecx.ref_to_mplace(value)?;
// Recursive checking
if let Some(ref mut ref_tracking) = self.ref_tracking {
assert!(self.const_mode, "We should only do recursie checking in const mode");
if size != Size::ZERO {
// Non-ZST also have to be dereferencable
let ptr = try_validation!(place.ptr.to_ptr(),
"integer pointer in non-ZST reference", self.path);
// Skip validation entirely for some external statics
let alloc_kind = self.ecx.tcx.alloc_map.lock().get(ptr.alloc_id);
if let Some(AllocType::Static(did)) = alloc_kind {
// `extern static` cannot be validated as they have no body.
// FIXME: Statics from other crates are also skipped.
// They might be checked at a different type, but for now we
// want to avoid recursing too deeply. This is not sound!
if !did.is_local() || self.ecx.tcx.is_foreign_item(did) {
return Ok(());
}
}
// Maintain the invariant that the place we are checking is
// already verified to be in-bounds.
try_validation!(self.ecx.memory.check_bounds(ptr, size, false),
"dangling (not entirely in bounds) reference", self.path);
}
// Check if we have encountered this pointer+layout combination
// before. Proceed recursively even for integer pointers, no
// reason to skip them! They are (recursively) valid for some ZST,
// but not for others (e.g. `!` is a ZST).
let op = place.into();
if ref_tracking.seen.insert(op) {
trace!("Recursing below ptr {:#?}", *op);
// We need to clone the path anyway, make sure it gets created
// with enough space for the additional `Deref`.
let mut new_path = Vec::with_capacity(self.path.len()+1);
new_path.clone_from(&self.path);
new_path.push(PathElem::Deref);
// Remember to come back to this later.
ref_tracking.todo.push((op, new_path));
}
}
}
ty::FnPtr(_sig) => {
let value = value.to_scalar_or_undef();
let ptr = try_validation!(value.to_ptr(),
value, self.path, "a pointer");
let _fn = try_validation!(self.ecx.memory.get_fn(ptr),
value, self.path, "a function pointer");
// FIXME: Check if the signature matches
}
// This should be all the primitive types
_ => bug!("Unexpected primitive type {}", value.layout.ty)
}
Ok(())
}
fn visit_uninhabited(&mut self) -> EvalResult<'tcx>
{
validation_failure!("a value of an uninhabited type", self.path)
}
fn visit_scalar(
&mut self,
op: OpTy<'tcx, M::PointerTag>,
layout: &layout::Scalar,
) -> EvalResult<'tcx> {
let value = self.ecx.read_scalar(op)?;
// Determine the allowed range
let (lo, hi) = layout.valid_range.clone().into_inner();
// `max_hi` is as big as the size fits
let max_hi = u128::max_value() >> (128 - op.layout.size.bits());
assert!(hi <= max_hi);
// 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) {
// Nothing to check
return Ok(());
}
// At least one value is excluded. Get the bits.
let value = try_validation!(value.not_undef(),
value, self.path,
format!("something in the range {:?}", layout.valid_range));
let bits = match value {
Scalar::Ptr(ptr) => {
if lo == 1 && hi == max_hi {
// only NULL is not allowed.
// We can call `check_align` to check non-NULL-ness, but have to also look
// for function pointers.
let non_null =
self.ecx.memory.check_align(
Scalar::Ptr(ptr), Align::from_bytes(1, 1).unwrap()
).is_ok() ||
self.ecx.memory.get_fn(ptr).is_ok();
if !non_null {
// could be NULL
return validation_failure!("a potentially NULL pointer", self.path);
}
return Ok(());
} else {
// Conservatively, we reject, because the pointer *could* have this
// value.
return validation_failure!(
"a pointer",
self.path,
format!(
"something that cannot possibly be outside the (wrapping) range {:?}",
layout.valid_range
)
);
}
}
Scalar::Bits { bits, size } => {
assert_eq!(size as u64, op.layout.size.bytes());
bits
}
};
// Now compare. This is slightly subtle because this is a special "wrap-around" range.
use std::ops::RangeInclusive;
let in_range = |bound: RangeInclusive<u128>| bound.contains(&bits);
if lo > hi {
// wrapping around
if in_range(0..=hi) || in_range(lo..=max_hi) {
Ok(())
} else {
validation_failure!(
bits,
self.path,
format!("something in the range {:?} or {:?}", 0..=hi, lo..=max_hi)
)
}
} else {
if in_range(layout.valid_range.clone()) {
Ok(())
} else {
validation_failure!(
bits,
self.path,
if hi == max_hi {
format!("something greater or equal to {}", lo)
} else {
format!("something in the range {:?}", layout.valid_range)
}
)
}
}
}
fn visit_aggregate(
&mut self,
op: OpTy<'tcx, M::PointerTag>,
fields: impl Iterator<Item=EvalResult<'tcx, Self::V>>,
) -> EvalResult<'tcx> {
match op.layout.ty.sty {
ty::Str => {
let mplace = op.to_mem_place(); // strings are never immediate
try_validation!(self.ecx.read_str(mplace),
"uninitialized or non-UTF-8 data in str", self.path);
}
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,
}
} => {
let mplace = if op.layout.is_zst() {
// it's a ZST, the memory content cannot matter
MPlaceTy::dangling(op.layout, self.ecx)
} else {
// non-ZST array/slice/str cannot be immediate
op.to_mem_place()
};
// This is the length of the array/slice.
let len = mplace.len(self.ecx)?;
// This is the element type size.
let ty_size = self.ecx.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 self.ecx.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),
}
}
}
}
_ => {
self.walk_aggregate(op, fields)? // default handler
}
}
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
/// is an indirect operand.
/// It will error if the bits at the destination do not match the ones described by the layout.
///
/// `ref_tracking` can be None to avoid recursive checking below references.
/// This also toggles between "run-time" (no recursion) and "compile-time" (with recursion)
/// validation (e.g., pointer values are fine in integers at runtime).
pub fn validate_operand(
&self,
op: OpTy<'tcx, M::PointerTag>,
path: Vec<PathElem>,
ref_tracking: Option<&mut RefTracking<'tcx, M::PointerTag>>,
const_mode: bool,
) -> EvalResult<'tcx> {
trace!("validate_operand: {:?}, {:?}", *op, op.layout.ty);
// Construct a visitor
let mut visitor = ValidityVisitor {
path,
ref_tracking,
const_mode,
ecx: self,
};
// Run it
visitor.visit_value(op)
}
}

View File

@ -0,0 +1,320 @@
//! 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 rustc::ty::layout::{self, TyLayout};
use rustc::ty;
use rustc::mir::interpret::{
EvalResult,
};
use super::{
Machine, EvalContext, MPlaceTy, OpTy, ImmTy,
};
// A thing that we can project into, and that has a layout.
// This wouldn't have to depend on `Machine` but with the current type inference,
// that's just more convenient to work with (avoids repeating all the `Machine` bounds).
pub trait Value<'a, 'mir, 'tcx, M: Machine<'a, 'mir, 'tcx>>: Copy
{
/// Get this value's layout.
fn layout(&self) -> TyLayout<'tcx>;
/// Make this into an `OpTy`.
fn to_op(
self,
ecx: &EvalContext<'a, 'mir, 'tcx, M>,
) -> EvalResult<'tcx, OpTy<'tcx, M::PointerTag>>;
/// Create this from an `MPlaceTy`.
fn from_mem_place(MPlaceTy<'tcx, M::PointerTag>) -> Self;
/// Project to the given enum variant.
fn project_downcast(
self,
ecx: &EvalContext<'a, 'mir, 'tcx, M>,
variant: usize,
) -> EvalResult<'tcx, Self>;
/// Project to the n-th field.
fn project_field(
self,
ecx: &EvalContext<'a, 'mir, 'tcx, M>,
field: u64,
) -> EvalResult<'tcx, Self>;
}
// Operands and memory-places are both values.
// Places in general are not due to `place_field` having to do `force_allocation`.
impl<'a, 'mir, 'tcx, M: Machine<'a, 'mir, 'tcx>> Value<'a, 'mir, 'tcx, M>
for OpTy<'tcx, M::PointerTag>
{
#[inline(always)]
fn layout(&self) -> TyLayout<'tcx> {
self.layout
}
#[inline(always)]
fn to_op(
self,
_ecx: &EvalContext<'a, 'mir, 'tcx, M>,
) -> EvalResult<'tcx, OpTy<'tcx, M::PointerTag>> {
Ok(self)
}
#[inline(always)]
fn from_mem_place(mplace: MPlaceTy<'tcx, M::PointerTag>) -> Self {
mplace.into()
}
#[inline(always)]
fn project_downcast(
self,
ecx: &EvalContext<'a, 'mir, 'tcx, M>,
variant: usize,
) -> EvalResult<'tcx, Self> {
ecx.operand_downcast(self, variant)
}
#[inline(always)]
fn project_field(
self,
ecx: &EvalContext<'a, 'mir, 'tcx, M>,
field: u64,
) -> EvalResult<'tcx, Self> {
ecx.operand_field(self, field)
}
}
impl<'a, 'mir, 'tcx, M: Machine<'a, 'mir, 'tcx>> Value<'a, 'mir, 'tcx, M>
for MPlaceTy<'tcx, M::PointerTag>
{
#[inline(always)]
fn layout(&self) -> TyLayout<'tcx> {
self.layout
}
#[inline(always)]
fn to_op(
self,
_ecx: &EvalContext<'a, 'mir, 'tcx, M>,
) -> EvalResult<'tcx, OpTy<'tcx, M::PointerTag>> {
Ok(self.into())
}
#[inline(always)]
fn from_mem_place(mplace: MPlaceTy<'tcx, M::PointerTag>) -> Self {
mplace
}
#[inline(always)]
fn project_downcast(
self,
ecx: &EvalContext<'a, 'mir, 'tcx, M>,
variant: usize,
) -> EvalResult<'tcx, Self> {
ecx.mplace_downcast(self, variant)
}
#[inline(always)]
fn project_field(
self,
ecx: &EvalContext<'a, 'mir, 'tcx, M>,
field: u64,
) -> EvalResult<'tcx, Self> {
ecx.mplace_field(self, field)
}
}
macro_rules! make_value_visitor {
($visitor_trait_name:ident, $($mutability:ident)*) => {
// How to traverse a value and what to do when we are at the leaves.
pub trait $visitor_trait_name<'a, 'mir, 'tcx: 'mir+'a, M: Machine<'a, 'mir, 'tcx>>: Sized {
type V: Value<'a, 'mir, 'tcx, M>;
/// The visitor must have an `EvalContext` in it.
fn ecx(&$($mutability)* self)
-> &$($mutability)* EvalContext<'a, 'mir, 'tcx, M>;
// Recursive actions, ready to be overloaded.
/// Visit the given value, dispatching as appropriate to more specialized visitors.
#[inline(always)]
fn visit_value(&mut self, v: Self::V) -> EvalResult<'tcx>
{
self.walk_value(v)
}
/// Visit the given value as a union. No automatic recursion can happen here.
#[inline(always)]
fn visit_union(&mut self, _v: Self::V) -> EvalResult<'tcx>
{
Ok(())
}
/// Visit this vale as an aggregate, you are even getting an iterator yielding
/// all the fields (still in an `EvalResult`, you have to do error handling yourself).
/// Recurses into the fields.
#[inline(always)]
fn visit_aggregate(
&mut self,
v: Self::V,
fields: impl Iterator<Item=EvalResult<'tcx, Self::V>>,
) -> EvalResult<'tcx> {
self.walk_aggregate(v, fields)
}
/// Called each time we recurse down to a field, passing in old and new value.
/// This gives the visitor the chance to track the stack of nested fields that
/// we are descending through.
#[inline(always)]
fn visit_field(
&mut self,
_old_val: Self::V,
_field: usize,
new_val: Self::V,
) -> EvalResult<'tcx> {
self.visit_value(new_val)
}
/// Called whenever we reach a value with uninhabited layout.
/// Recursing to fields will *always* continue after this! This is not meant to control
/// whether and how we descend recursively/ into the scalar's fields if there are any,
/// it is meant to provide the chance for additional checks when a value of uninhabited
/// layout is detected.
#[inline(always)]
fn visit_uninhabited(&mut self) -> EvalResult<'tcx>
{ Ok(()) }
/// Called whenever we reach a value with scalar layout.
/// We do NOT provide a `ScalarMaybeUndef` here to avoid accessing memory if the
/// visitor is not even interested in scalars.
/// Recursing to fields will *always* continue after this! This is not meant to control
/// whether and how we descend recursively/ into the scalar's fields if there are any,
/// it is meant to provide the chance for additional checks when a value of scalar
/// layout is detected.
#[inline(always)]
fn visit_scalar(&mut self, _v: Self::V, _layout: &layout::Scalar) -> EvalResult<'tcx>
{ Ok(()) }
/// Called whenever we reach a value of primitive type. There can be no recursion
/// below such a value. This is the leave function.
#[inline(always)]
fn visit_primitive(&mut self, _val: ImmTy<'tcx, M::PointerTag>) -> EvalResult<'tcx>
{ Ok(()) }
// Default recursors. Not meant to be overloaded.
fn walk_aggregate(
&mut self,
v: Self::V,
fields: impl Iterator<Item=EvalResult<'tcx, Self::V>>,
) -> EvalResult<'tcx> {
// Now iterate over it.
for (idx, field_val) in fields.enumerate() {
self.visit_field(v, idx, field_val?)?;
}
Ok(())
}
fn walk_value(&mut self, v: Self::V) -> EvalResult<'tcx>
{
trace!("walk_value: type: {}", v.layout().ty);
// If this is a multi-variant layout, we have find the right one and proceed with
// that.
match v.layout().variants {
layout::Variants::NicheFilling { .. } |
layout::Variants::Tagged { .. } => {
let op = v.to_op(self.ecx())?;
let idx = self.ecx().read_discriminant(op)?.1;
let inner = v.project_downcast(self.ecx(), idx)?;
trace!("walk_value: variant layout: {:#?}", inner.layout());
// recurse with the inner type
return self.visit_field(v, idx, inner);
}
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(..) => {
// immediate trait objects are not a thing
let dest = v.to_op(self.ecx())?.to_mem_place();
let inner = self.ecx().unpack_dyn_trait(dest)?.1;
trace!("walk_value: dyn object layout: {:#?}", inner.layout);
// recurse with the inner type
return self.visit_field(v, 0, Value::from_mem_place(inner));
},
_ => {},
};
// 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::Uninhabited => {
self.visit_uninhabited()?;
}
layout::Abi::Scalar(ref layout) => {
self.visit_scalar(v, 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 {
let op = v.to_op(self.ecx())?;
let val = self.ecx().read_immediate(op)?;
return self.visit_primitive(val);
}
// 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);
self.visit_union(v)?;
},
layout::FieldPlacement::Arbitrary { ref offsets, .. } => {
// FIXME: We collect in a vec because otherwise there are lifetime errors:
// Projecting to a field needs (mutable!) access to `ecx`.
let fields: Vec<EvalResult<'tcx, Self::V>> =
(0..offsets.len()).map(|i| {
v.project_field(self.ecx(), i as u64)
})
.collect();
self.visit_aggregate(v, fields.into_iter())?;
},
layout::FieldPlacement::Array { .. } => {
// Let's get an mplace first.
let mplace = if v.layout().is_zst() {
// it's a ZST, the memory content cannot matter
MPlaceTy::dangling(v.layout(), self.ecx())
} else {
// non-ZST array/slice/str cannot be immediate
v.to_op(self.ecx())?.to_mem_place()
};
// Now we can go over all the fields.
let iter = self.ecx().mplace_array_fields(mplace)?
.map(|f| f.and_then(|f| {
Ok(Value::from_mem_place(f))
}));
self.visit_aggregate(v, iter)?;
}
}
Ok(())
}
}
}
}
make_value_visitor!(ValueVisitor,);
make_value_visitor!(MutValueVisitor,mut);

View File

@ -5,7 +5,7 @@ LL | / static FOO: (&Foo, &Bar) = unsafe {( //~ undefined behavior
LL | | Union { u8: &BAR }.foo,
LL | | Union { u8: &BAR }.bar,
LL | | )};
| |___^ type validation failed: encountered invalid enum discriminant 5 at .1.<deref>
| |___^ type validation failed: encountered 5 at .1.<deref>, but expected a valid enum discriminant
|
= note: The rules on what exactly is undefined behavior aren't clear, so this check might be overzealous. Please open an issue on the rust compiler repository if you believe it should not be considered undefined behavior

View File

@ -8,6 +8,8 @@
// option. This file may not be copied, modified, or distributed
// except according to those terms.
#![allow(const_err)] // make sure we cannot allow away the errors tested here
#[repr(usize)]
#[derive(Copy, Clone)]
enum Enum {
@ -31,10 +33,15 @@ enum Enum2 {
union TransmuteEnum2 {
a: usize,
b: Enum2,
c: (),
}
const BAD_ENUM2 : Enum2 = unsafe { TransmuteEnum2 { a: 0 }.b };
//~^ ERROR is undefined behavior
// Undef enum discriminant. In an arry to avoid `Scalar` layout.
const BAD_ENUM3 : [Enum2; 2] = [unsafe { TransmuteEnum2 { c: () }.b }; 2];
//~^ ERROR is undefined behavior
// Invalid enum field content (mostly to test printing of apths for enum tuple
// variants and tuples).
union TransmuteChar {

View File

@ -1,27 +1,35 @@
error[E0080]: it is undefined behavior to use this value
--> $DIR/ub-enum.rs:22:1
--> $DIR/ub-enum.rs:24:1
|
LL | const BAD_ENUM: Enum = unsafe { TransmuteEnum { a: &1 }.b };
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ type validation failed: encountered non-integer enum discriminant
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ type validation failed: encountered a pointer, but expected a valid enum discriminant
|
= note: The rules on what exactly is undefined behavior aren't clear, so this check might be overzealous. Please open an issue on the rust compiler repository if you believe it should not be considered undefined behavior
error[E0080]: it is undefined behavior to use this value
--> $DIR/ub-enum.rs:35:1
--> $DIR/ub-enum.rs:38:1
|
LL | const BAD_ENUM2 : Enum2 = unsafe { TransmuteEnum2 { a: 0 }.b };
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ type validation failed: encountered invalid enum discriminant 0
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ type validation failed: encountered 0, but expected a valid enum discriminant
|
= note: The rules on what exactly is undefined behavior aren't clear, so this check might be overzealous. Please open an issue on the rust compiler repository if you believe it should not be considered undefined behavior
error[E0080]: it is undefined behavior to use this value
--> $DIR/ub-enum.rs:45:1
--> $DIR/ub-enum.rs:42:1
|
LL | const BAD_ENUM3 : [Enum2; 2] = [unsafe { TransmuteEnum2 { c: () }.b }; 2];
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ attempted to read undefined bytes
|
= note: The rules on what exactly is undefined behavior aren't clear, so this check might be overzealous. Please open an issue on the rust compiler repository if you believe it should not be considered undefined behavior
error[E0080]: it is undefined behavior to use this value
--> $DIR/ub-enum.rs:52:1
|
LL | const BAD_ENUM_CHAR : Option<(char, char)> = Some(('x', unsafe { TransmuteChar { a: !0 }.b }));
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ type validation failed: encountered 4294967295 at .Some.0.1, but expected something in the range 0..=1114111
|
= note: The rules on what exactly is undefined behavior aren't clear, so this check might be overzealous. Please open an issue on the rust compiler repository if you believe it should not be considered undefined behavior
error: aborting due to 3 previous errors
error: aborting due to 4 previous errors
For more information about this error, try `rustc --explain E0080`.

View File

@ -9,6 +9,7 @@
// except according to those terms.
#![feature(const_transmute)]
#![allow(const_err)] // make sure we cannot allow away the errors tested here
use std::mem;
use std::ptr::NonNull;

View File

@ -1,5 +1,5 @@
error[E0080]: it is undefined behavior to use this value
--> $DIR/ub-nonnull.rs:17:1
--> $DIR/ub-nonnull.rs:18:1
|
LL | const NULL_PTR: NonNull<u8> = unsafe { mem::transmute(0usize) };
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ type validation failed: encountered 0, but expected something greater or equal to 1
@ -7,7 +7,7 @@ LL | const NULL_PTR: NonNull<u8> = unsafe { mem::transmute(0usize) };
= note: The rules on what exactly is undefined behavior aren't clear, so this check might be overzealous. Please open an issue on the rust compiler repository if you believe it should not be considered undefined behavior
error[E0080]: it is undefined behavior to use this value
--> $DIR/ub-nonnull.rs:20:1
--> $DIR/ub-nonnull.rs:21:1
|
LL | const NULL_U8: NonZeroU8 = unsafe { mem::transmute(0u8) };
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ type validation failed: encountered 0, but expected something greater or equal to 1
@ -15,7 +15,7 @@ LL | const NULL_U8: NonZeroU8 = unsafe { mem::transmute(0u8) };
= note: The rules on what exactly is undefined behavior aren't clear, so this check might be overzealous. Please open an issue on the rust compiler repository if you believe it should not be considered undefined behavior
error[E0080]: it is undefined behavior to use this value
--> $DIR/ub-nonnull.rs:22:1
--> $DIR/ub-nonnull.rs:23:1
|
LL | const NULL_USIZE: NonZeroUsize = unsafe { mem::transmute(0usize) };
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ type validation failed: encountered 0, but expected something greater or equal to 1

View File

@ -9,6 +9,7 @@
// except according to those terms.
#![feature(const_transmute)]
#![allow(const_err)] // make sure we cannot allow away the errors tested here
use std::mem;

View File

@ -1,5 +1,5 @@
error[E0080]: it is undefined behavior to use this value
--> $DIR/ub-ref.rs:15:1
--> $DIR/ub-ref.rs:16:1
|
LL | const UNALIGNED: &u16 = unsafe { mem::transmute(&[0u8; 4]) };
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ type validation failed: encountered unaligned reference
@ -7,7 +7,7 @@ LL | const UNALIGNED: &u16 = unsafe { mem::transmute(&[0u8; 4]) };
= note: The rules on what exactly is undefined behavior aren't clear, so this check might be overzealous. Please open an issue on the rust compiler repository if you believe it should not be considered undefined behavior
error[E0080]: it is undefined behavior to use this value
--> $DIR/ub-ref.rs:18:1
--> $DIR/ub-ref.rs:19:1
|
LL | const NULL: &u16 = unsafe { mem::transmute(0usize) };
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ type validation failed: encountered 0, but expected something greater or equal to 1
@ -15,7 +15,7 @@ LL | const NULL: &u16 = unsafe { mem::transmute(0usize) };
= note: The rules on what exactly is undefined behavior aren't clear, so this check might be overzealous. Please open an issue on the rust compiler repository if you believe it should not be considered undefined behavior
error[E0080]: it is undefined behavior to use this value
--> $DIR/ub-ref.rs:21:1
--> $DIR/ub-ref.rs:22:1
|
LL | const REF_AS_USIZE: usize = unsafe { mem::transmute(&0) };
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ type validation failed: encountered a pointer, but expected initialized plain bits
@ -23,15 +23,15 @@ LL | const REF_AS_USIZE: usize = unsafe { mem::transmute(&0) };
= note: The rules on what exactly is undefined behavior aren't clear, so this check might be overzealous. Please open an issue on the rust compiler repository if you believe it should not be considered undefined behavior
error[E0080]: it is undefined behavior to use this value
--> $DIR/ub-ref.rs:24:1
--> $DIR/ub-ref.rs:25:1
|
LL | const REF_AS_USIZE_SLICE: &[usize] = &[unsafe { mem::transmute(&0) }];
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ a raw memory access tried to access part of a pointer value as raw bytes
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ type validation failed: encountered a pointer at .<deref>, but expected plain bytes
|
= note: The rules on what exactly is undefined behavior aren't clear, so this check might be overzealous. Please open an issue on the rust compiler repository if you believe it should not be considered undefined behavior
error[E0080]: it is undefined behavior to use this value
--> $DIR/ub-ref.rs:27:1
--> $DIR/ub-ref.rs:28:1
|
LL | const USIZE_AS_REF: &'static u8 = unsafe { mem::transmute(1337usize) };
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ type validation failed: encountered integer pointer in non-ZST reference

View File

@ -9,6 +9,7 @@
// except according to those terms.
#![feature(const_transmute)]
#![allow(const_err)] // make sure we cannot allow away the errors tested here
use std::mem;

View File

@ -1,5 +1,5 @@
error[E0080]: it is undefined behavior to use this value
--> $DIR/ub-uninhabit.rs:18:1
--> $DIR/ub-uninhabit.rs:19:1
|
LL | const BAD_BAD_BAD: Bar = unsafe { mem::transmute(()) };
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ type validation failed: encountered a value of an uninhabited type
@ -7,7 +7,7 @@ LL | const BAD_BAD_BAD: Bar = unsafe { mem::transmute(()) };
= note: The rules on what exactly is undefined behavior aren't clear, so this check might be overzealous. Please open an issue on the rust compiler repository if you believe it should not be considered undefined behavior
error[E0080]: it is undefined behavior to use this value
--> $DIR/ub-uninhabit.rs:21:1
--> $DIR/ub-uninhabit.rs:22:1
|
LL | const BAD_BAD_REF: &Bar = unsafe { mem::transmute(1usize) };
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ type validation failed: encountered a value of an uninhabited type at .<deref>
@ -15,7 +15,7 @@ LL | const BAD_BAD_REF: &Bar = unsafe { mem::transmute(1usize) };
= note: The rules on what exactly is undefined behavior aren't clear, so this check might be overzealous. Please open an issue on the rust compiler repository if you believe it should not be considered undefined behavior
error[E0080]: it is undefined behavior to use this value
--> $DIR/ub-uninhabit.rs:24:1
--> $DIR/ub-uninhabit.rs:25:1
|
LL | const BAD_BAD_ARRAY: [Bar; 1] = unsafe { mem::transmute(()) };
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ type validation failed: encountered a value of an uninhabited type at [0]

View File

@ -9,6 +9,7 @@
// except according to those terms.
#![feature(const_transmute,const_let)]
#![allow(const_err)] // make sure we cannot allow away the errors tested here
use std::mem;

View File

@ -1,12 +1,12 @@
error[E0080]: it is undefined behavior to use this value
--> $DIR/ub-upvars.rs:15:1
--> $DIR/ub-upvars.rs:16:1
|
LL | / const BAD_UPVAR: &FnOnce() = &{ //~ ERROR it is undefined behavior to use this value
LL | | let bad_ref: &'static u16 = unsafe { mem::transmute(0usize) };
LL | | let another_var = 13;
LL | | move || { let _ = bad_ref; let _ = another_var; }
LL | | };
| |__^ type validation failed: encountered 0 at .<deref>.<closure-var(bad_ref)>, but expected something greater or equal to 1
| |__^ type validation failed: encountered 0 at .<deref>.<dyn-downcast>.<closure-var(bad_ref)>, but expected something greater or equal to 1
|
= note: The rules on what exactly is undefined behavior aren't clear, so this check might be overzealous. Please open an issue on the rust compiler repository if you believe it should not be considered undefined behavior

View File

@ -9,6 +9,7 @@
// except according to those terms.
#![allow(unused)]
#![allow(const_err)] // make sure we cannot allow away the errors tested here
// normalize-stderr-test "alignment \d+" -> "alignment N"
// normalize-stderr-test "offset \d+" -> "offset N"

View File

@ -1,5 +1,5 @@
error[E0080]: it is undefined behavior to use this value
--> $DIR/union-ub-fat-ptr.rs:87:1
--> $DIR/union-ub-fat-ptr.rs:88:1
|
LL | const B: &str = unsafe { SliceTransmute { repr: SliceRepr { ptr: &42, len: 999 } }.str};
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ type validation failed: encountered dangling (not entirely in bounds) reference
@ -7,7 +7,7 @@ LL | const B: &str = unsafe { SliceTransmute { repr: SliceRepr { ptr: &42, len:
= note: The rules on what exactly is undefined behavior aren't clear, so this check might be overzealous. Please open an issue on the rust compiler repository if you believe it should not be considered undefined behavior
error[E0080]: it is undefined behavior to use this value
--> $DIR/union-ub-fat-ptr.rs:90:1
--> $DIR/union-ub-fat-ptr.rs:91:1
|
LL | const C: &str = unsafe { SliceTransmute { bad: BadSliceRepr { ptr: &42, len: &3 } }.str};
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ type validation failed: encountered non-integer slice length in fat pointer
@ -15,7 +15,7 @@ LL | const C: &str = unsafe { SliceTransmute { bad: BadSliceRepr { ptr: &42, len
= note: The rules on what exactly is undefined behavior aren't clear, so this check might be overzealous. Please open an issue on the rust compiler repository if you believe it should not be considered undefined behavior
error[E0080]: it is undefined behavior to use this value
--> $DIR/union-ub-fat-ptr.rs:93:1
--> $DIR/union-ub-fat-ptr.rs:94:1
|
LL | const C2: &MyStr = unsafe { SliceTransmute { bad: BadSliceRepr { ptr: &42, len: &3 } }.my_str};
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ type validation failed: encountered non-integer slice length in fat pointer
@ -23,7 +23,7 @@ LL | const C2: &MyStr = unsafe { SliceTransmute { bad: BadSliceRepr { ptr: &42,
= note: The rules on what exactly is undefined behavior aren't clear, so this check might be overzealous. Please open an issue on the rust compiler repository if you believe it should not be considered undefined behavior
error[E0080]: it is undefined behavior to use this value
--> $DIR/union-ub-fat-ptr.rs:99:1
--> $DIR/union-ub-fat-ptr.rs:100:1
|
LL | const B2: &[u8] = unsafe { SliceTransmute { repr: SliceRepr { ptr: &42, len: 999 } }.slice};
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ type validation failed: encountered dangling (not entirely in bounds) reference
@ -31,7 +31,7 @@ LL | const B2: &[u8] = unsafe { SliceTransmute { repr: SliceRepr { ptr: &42, len
= note: The rules on what exactly is undefined behavior aren't clear, so this check might be overzealous. Please open an issue on the rust compiler repository if you believe it should not be considered undefined behavior
error[E0080]: it is undefined behavior to use this value
--> $DIR/union-ub-fat-ptr.rs:102:1
--> $DIR/union-ub-fat-ptr.rs:103:1
|
LL | const C3: &[u8] = unsafe { SliceTransmute { bad: BadSliceRepr { ptr: &42, len: &3 } }.slice};
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ type validation failed: encountered non-integer slice length in fat pointer
@ -39,7 +39,7 @@ LL | const C3: &[u8] = unsafe { SliceTransmute { bad: BadSliceRepr { ptr: &42, l
= note: The rules on what exactly is undefined behavior aren't clear, so this check might be overzealous. Please open an issue on the rust compiler repository if you believe it should not be considered undefined behavior
error[E0080]: it is undefined behavior to use this value
--> $DIR/union-ub-fat-ptr.rs:106:1
--> $DIR/union-ub-fat-ptr.rs:107:1
|
LL | const D: &Trait = unsafe { DynTransmute { repr: DynRepr { ptr: &92, vtable: &3 } }.rust};
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ type validation failed: encountered invalid drop fn in vtable
@ -47,7 +47,7 @@ LL | const D: &Trait = unsafe { DynTransmute { repr: DynRepr { ptr: &92, vtable:
= note: The rules on what exactly is undefined behavior aren't clear, so this check might be overzealous. Please open an issue on the rust compiler repository if you believe it should not be considered undefined behavior
error[E0080]: it is undefined behavior to use this value
--> $DIR/union-ub-fat-ptr.rs:109:1
--> $DIR/union-ub-fat-ptr.rs:110:1
|
LL | const E: &Trait = unsafe { DynTransmute { repr2: DynRepr2 { ptr: &92, vtable: &3 } }.rust};
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ type validation failed: encountered invalid drop fn in vtable
@ -55,7 +55,7 @@ LL | const E: &Trait = unsafe { DynTransmute { repr2: DynRepr2 { ptr: &92, vtabl
= note: The rules on what exactly is undefined behavior aren't clear, so this check might be overzealous. Please open an issue on the rust compiler repository if you believe it should not be considered undefined behavior
error[E0080]: it is undefined behavior to use this value
--> $DIR/union-ub-fat-ptr.rs:112:1
--> $DIR/union-ub-fat-ptr.rs:113:1
|
LL | const F: &Trait = unsafe { DynTransmute { bad: BadDynRepr { ptr: &92, vtable: 3 } }.rust};
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ type validation failed: encountered non-pointer vtable in fat pointer
@ -63,15 +63,15 @@ LL | const F: &Trait = unsafe { DynTransmute { bad: BadDynRepr { ptr: &92, vtabl
= note: The rules on what exactly is undefined behavior aren't clear, so this check might be overzealous. Please open an issue on the rust compiler repository if you believe it should not be considered undefined behavior
error[E0080]: it is undefined behavior to use this value
--> $DIR/union-ub-fat-ptr.rs:116:1
--> $DIR/union-ub-fat-ptr.rs:117:1
|
LL | const G: &Trait = &unsafe { BoolTransmute { val: 3 }.bl };
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ type validation failed: encountered 3 at .<deref>, but expected something in the range 0..=1
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ type validation failed: encountered 3 at .<deref>.<dyn-downcast>, but expected something in the range 0..=1
|
= note: The rules on what exactly is undefined behavior aren't clear, so this check might be overzealous. Please open an issue on the rust compiler repository if you believe it should not be considered undefined behavior
error[E0080]: it is undefined behavior to use this value
--> $DIR/union-ub-fat-ptr.rs:120:1
--> $DIR/union-ub-fat-ptr.rs:121:1
|
LL | const H: &[bool] = &[unsafe { BoolTransmute { val: 3 }.bl }];
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ type validation failed: encountered 3 at .<deref>[0], but expected something in the range 0..=1
@ -79,7 +79,7 @@ LL | const H: &[bool] = &[unsafe { BoolTransmute { val: 3 }.bl }];
= note: The rules on what exactly is undefined behavior aren't clear, so this check might be overzealous. Please open an issue on the rust compiler repository if you believe it should not be considered undefined behavior
error[E0080]: it is undefined behavior to use this value
--> $DIR/union-ub-fat-ptr.rs:126:1
--> $DIR/union-ub-fat-ptr.rs:127:1
|
LL | const I2: &MySliceBool = &MySlice(unsafe { BoolTransmute { val: 3 }.bl }, [false]);
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ type validation failed: encountered 3 at .<deref>.0, but expected something in the range 0..=1
@ -87,7 +87,7 @@ LL | const I2: &MySliceBool = &MySlice(unsafe { BoolTransmute { val: 3 }.bl }, [
= note: The rules on what exactly is undefined behavior aren't clear, so this check might be overzealous. Please open an issue on the rust compiler repository if you believe it should not be considered undefined behavior
error[E0080]: it is undefined behavior to use this value
--> $DIR/union-ub-fat-ptr.rs:129:1
--> $DIR/union-ub-fat-ptr.rs:130:1
|
LL | const I3: &MySliceBool = &MySlice(true, [unsafe { BoolTransmute { val: 3 }.bl }]);
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ type validation failed: encountered 3 at .<deref>.1[0], but expected something in the range 0..=1
@ -95,7 +95,7 @@ LL | const I3: &MySliceBool = &MySlice(true, [unsafe { BoolTransmute { val: 3 }.
= note: The rules on what exactly is undefined behavior aren't clear, so this check might be overzealous. Please open an issue on the rust compiler repository if you believe it should not be considered undefined behavior
error[E0080]: it is undefined behavior to use this value
--> $DIR/union-ub-fat-ptr.rs:133:1
--> $DIR/union-ub-fat-ptr.rs:134:1
|
LL | const J1: &str = unsafe { SliceTransmute { slice: &[0xFF] }.str };
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ type validation failed: encountered uninitialized or non-UTF-8 data in str at .<deref>
@ -103,7 +103,7 @@ LL | const J1: &str = unsafe { SliceTransmute { slice: &[0xFF] }.str };
= note: The rules on what exactly is undefined behavior aren't clear, so this check might be overzealous. Please open an issue on the rust compiler repository if you believe it should not be considered undefined behavior
error[E0080]: it is undefined behavior to use this value
--> $DIR/union-ub-fat-ptr.rs:136:1
--> $DIR/union-ub-fat-ptr.rs:137:1
|
LL | const J2: &MyStr = unsafe { SliceTransmute { slice: &[0xFF] }.my_str };
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ type validation failed: encountered uninitialized or non-UTF-8 data in str at .<deref>.0

View File

@ -8,6 +8,8 @@
// option. This file may not be copied, modified, or distributed
// except according to those terms.
#![allow(const_err)] // make sure we cannot allow away the errors tested here
union DummyUnion {
u8: u8,
bool: bool,

View File

@ -1,5 +1,5 @@
error[E0080]: it is undefined behavior to use this value
--> $DIR/union-ub.rs:36:1
--> $DIR/union-ub.rs:38:1
|
LL | const BAD_BOOL: bool = unsafe { DummyUnion { u8: 42 }.bool};
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ type validation failed: encountered 42, but expected something in the range 0..=1