Auto merge of #54762 - RalfJung:miri-validate, r=oli-obk

Prepare miri engine for enforcing validity invariant during execution

In particular, make recursive checking of references optional, and add a `const_mode` parameter that says whether `usize` is allowed to contain a pointer. Also refactor validation a bit to be type-driven at the "leafs" (primitive types), and separately validate scalar layout to catch `NonNull` violations (which it did not properly validate before).

Fixes https://github.com/rust-lang/rust/issues/53826
Also fixes https://github.com/rust-lang/rust/issues/54751

r? @oli-obk
This commit is contained in:
bors 2018-10-09 17:19:56 +00:00
commit 0e07c4281c
31 changed files with 635 additions and 352 deletions

View File

@ -560,6 +560,10 @@ for ::mir::interpret::EvalErrorKind<'gcx, O> {
a.hash_stable(hcx, hasher);
b.hash_stable(hcx, hasher)
},
FunctionRetMismatch(a, b) => {
a.hash_stable(hcx, hasher);
b.hash_stable(hcx, hasher)
},
NoMirFor(ref s) => s.hash_stable(hcx, hasher),
UnterminatedCString(ptr) => ptr.hash_stable(hcx, hasher),
PointerOutOfBounds {

View File

@ -186,6 +186,7 @@ pub enum EvalErrorKind<'tcx, O> {
FunctionAbiMismatch(Abi, Abi),
FunctionArgMismatch(Ty<'tcx>, Ty<'tcx>),
FunctionRetMismatch(Ty<'tcx>, Ty<'tcx>),
FunctionArgCountMismatch,
NoMirFor(String),
UnterminatedCString(Pointer),
@ -294,7 +295,8 @@ impl<'tcx, O> EvalErrorKind<'tcx, O> {
use self::EvalErrorKind::*;
match *self {
MachineError(ref inner) => inner,
FunctionAbiMismatch(..) | FunctionArgMismatch(..) | FunctionArgCountMismatch =>
FunctionAbiMismatch(..) | FunctionArgMismatch(..) | FunctionRetMismatch(..)
| FunctionArgCountMismatch =>
"tried to call a function through a function pointer of incompatible type",
InvalidMemoryAccess =>
"tried to access memory through an invalid pointer",
@ -470,6 +472,10 @@ impl<'tcx, O: fmt::Debug> fmt::Debug for EvalErrorKind<'tcx, O> {
write!(f, "tried to call a function with argument of type {:?} \
passing data of type {:?}",
callee_ty, caller_ty),
FunctionRetMismatch(caller_ty, callee_ty) =>
write!(f, "tried to call a function with return type {:?} \
passing return place of type {:?}",
callee_ty, caller_ty),
FunctionArgCountMismatch =>
write!(f, "tried to call a function with incorrect number of arguments"),
BoundsCheck { ref len, ref index } =>

View File

@ -492,6 +492,10 @@ impl<'a, 'tcx, O: Lift<'tcx>> Lift<'tcx> for interpret::EvalErrorKind<'a, O> {
tcx.lift(&a)?,
tcx.lift(&b)?,
),
FunctionRetMismatch(a, b) => FunctionRetMismatch(
tcx.lift(&a)?,
tcx.lift(&b)?,
),
FunctionArgCountMismatch => FunctionArgCountMismatch,
NoMirFor(ref s) => NoMirFor(s.clone()),
UnterminatedCString(ptr) => UnterminatedCString(ptr),

View File

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

View File

@ -274,6 +274,7 @@ impl<'a, 'mir, 'tcx> interpret::Machine<'a, 'mir, 'tcx>
type MemoryKinds = !;
const MUT_STATIC_KIND: Option<!> = None; // no mutating of statics allowed
const ENFORCE_VALIDITY: bool = false; // for now, we don't
fn find_fn(
ecx: &mut EvalContext<'a, 'mir, 'tcx, Self>,

View File

@ -231,6 +231,7 @@ impl<'a, 'mir, 'tcx: 'mir, M: Machine<'a, 'mir, 'tcx>> EvalContext<'a, 'mir, 'tc
/// Mark a storage as live, killing the previous content and returning it.
/// Remember to deallocate that!
pub fn storage_live(&mut self, local: mir::Local) -> EvalResult<'tcx, LocalValue> {
assert!(local != mir::RETURN_PLACE, "Cannot make return place live");
trace!("{:?} is now live", local);
let layout = self.layout_of_local(self.cur_frame(), local)?;
@ -242,6 +243,7 @@ impl<'a, 'mir, 'tcx: 'mir, M: Machine<'a, 'mir, 'tcx>> EvalContext<'a, 'mir, 'tc
/// Returns the old value of the local.
/// Remember to deallocate that!
pub fn storage_dead(&mut self, local: mir::Local) -> LocalValue {
assert!(local != mir::RETURN_PLACE, "Cannot make return place dead");
trace!("{:?} is now dead", local);
mem::replace(&mut self.frame_mut().locals[local], LocalValue::Dead)
@ -446,6 +448,9 @@ impl<'a, 'mir, 'tcx: 'mir, M: Machine<'a, 'mir, 'tcx>> EvalContext<'a, 'mir, 'tc
let dummy =
LocalValue::Live(Operand::Immediate(Value::Scalar(ScalarMaybeUndef::Undef)));
let mut locals = IndexVec::from_elem(dummy, &mir.local_decls);
// Return place is handled specially by the `eval_place` functions, and the
// entry in `locals` should never be used. Make it dead, to be sure.
locals[mir::RETURN_PLACE] = LocalValue::Dead;
// Now mark those locals as dead that we do not want to initialize
match self.tcx.describe_def(instance.def_id()) {
// statics and constants don't have `Storage*` statements, no need to look for them

View File

@ -33,6 +33,9 @@ pub trait Machine<'a, 'mir, 'tcx>: Sized {
/// The memory kind to use for mutated statics -- or None if those are not supported.
const MUT_STATIC_KIND: Option<Self::MemoryKinds>;
/// Whether to enforce the validity invariant
const ENFORCE_VALIDITY: bool;
/// Called before a basic block terminator is executed.
/// You can use this to detect endlessly running programs.
fn before_terminator(ecx: &mut EvalContext<'a, 'mir, 'tcx, Self>) -> EvalResult<'tcx>;

View File

@ -19,7 +19,7 @@
use std::collections::VecDeque;
use std::ptr;
use rustc::ty::{self, Instance, query::TyCtxtAt};
use rustc::ty::{self, Instance, ParamEnv, query::TyCtxtAt};
use rustc::ty::layout::{self, Align, TargetDataLayout, Size, HasDataLayout};
use rustc::mir::interpret::{Pointer, AllocId, Allocation, ConstValue, GlobalId,
EvalResult, Scalar, EvalErrorKind, AllocType, PointerArithmetic,
@ -235,7 +235,7 @@ impl<'a, 'mir, 'tcx, M: Machine<'a, 'mir, 'tcx>> Memory<'a, 'mir, 'tcx, M> {
// Check non-NULL/Undef, extract offset
let (offset, alloc_align) = match ptr {
Scalar::Ptr(ptr) => {
let (size, align) = self.get_size_and_align(ptr.alloc_id)?;
let (size, align) = self.get_size_and_align(ptr.alloc_id);
// check this is not NULL -- which we can ensure only if this is in-bounds
// of some (potentially dead) allocation.
if ptr.offset > size {
@ -284,7 +284,7 @@ impl<'a, 'mir, 'tcx, M: Machine<'a, 'mir, 'tcx>> Memory<'a, 'mir, 'tcx, M> {
/// If you want to check bounds before doing a memory access, be sure to
/// check the pointer one past the end of your access, then everything will
/// work out exactly.
pub fn check_bounds(&self, ptr: Pointer, access: bool) -> EvalResult<'tcx> {
pub fn check_bounds_ptr(&self, ptr: Pointer, access: bool) -> EvalResult<'tcx> {
let alloc = self.get(ptr.alloc_id)?;
let allocation_size = alloc.bytes.len() as u64;
if ptr.offset.bytes() > allocation_size {
@ -296,6 +296,13 @@ impl<'a, 'mir, 'tcx, M: Machine<'a, 'mir, 'tcx>> Memory<'a, 'mir, 'tcx, M> {
}
Ok(())
}
/// Check if the memory range beginning at `ptr` and of size `Size` is "in-bounds".
#[inline(always)]
pub fn check_bounds(&self, ptr: Pointer, size: Size, access: bool) -> EvalResult<'tcx> {
// if ptr.offset is in bounds, then so is ptr (because offset checks for overflow)
self.check_bounds_ptr(ptr.offset(size, &*self)?, access)
}
}
/// Allocation accessors
@ -352,19 +359,28 @@ impl<'a, 'mir, 'tcx, M: Machine<'a, 'mir, 'tcx>> Memory<'a, 'mir, 'tcx, M> {
}
}
pub fn get_size_and_align(&self, id: AllocId) -> EvalResult<'tcx, (Size, Align)> {
Ok(match self.get(id) {
Ok(alloc) => (Size::from_bytes(alloc.bytes.len() as u64), alloc.align),
Err(err) => match err.kind {
EvalErrorKind::DanglingPointerDeref =>
// This should be in the dead allocation map
*self.dead_alloc_map.get(&id).expect(
"allocation missing in dead_alloc_map"
),
// E.g. a function ptr allocation
_ => return Err(err)
pub fn get_size_and_align(&self, id: AllocId) -> (Size, Align) {
if let Ok(alloc) = self.get(id) {
return (Size::from_bytes(alloc.bytes.len() as u64), alloc.align);
}
// Could also be a fn ptr or extern static
match self.tcx.alloc_map.lock().get(id) {
Some(AllocType::Function(..)) => (Size::ZERO, Align::from_bytes(1, 1).unwrap()),
Some(AllocType::Static(did)) => {
// The only way `get` couldnÄt have worked here is if this is an extern static
assert!(self.tcx.is_foreign_item(did));
// Use size and align of the type
let ty = self.tcx.type_of(did);
let layout = self.tcx.layout_of(ParamEnv::empty().and(ty)).unwrap();
(layout.size, layout.align)
}
})
_ => {
// Must be a deallocated pointer
*self.dead_alloc_map.get(&id).expect(
"allocation missing in dead_alloc_map"
)
}
}
}
pub fn get_mut(
@ -524,8 +540,7 @@ impl<'a, 'mir, 'tcx, M: Machine<'a, 'mir, 'tcx>> Memory<'a, 'mir, 'tcx, M> {
) -> EvalResult<'tcx, &[u8]> {
assert_ne!(size.bytes(), 0, "0-sized accesses should never even get a `Pointer`");
self.check_align(ptr.into(), align)?;
// if ptr.offset is in bounds, then so is ptr (because offset checks for overflow)
self.check_bounds(ptr.offset(size, &*self)?, true)?;
self.check_bounds(ptr, size, true)?;
if check_defined_and_ptr {
self.check_defined(ptr, size)?;
@ -569,8 +584,7 @@ impl<'a, 'mir, 'tcx, M: Machine<'a, 'mir, 'tcx>> Memory<'a, 'mir, 'tcx, M> {
) -> EvalResult<'tcx, &mut [u8]> {
assert_ne!(size.bytes(), 0, "0-sized accesses should never even get a `Pointer`");
self.check_align(ptr.into(), align)?;
// if ptr.offset is in bounds, then so is ptr (because offset checks for overflow)
self.check_bounds(ptr.offset(size, &self)?, true)?;
self.check_bounds(ptr, size, true)?;
self.mark_definedness(ptr, size, true)?;
self.clear_relocations(ptr, size)?;

View File

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

View File

@ -131,6 +131,18 @@ impl MemPlace {
}
impl<'tcx> MPlaceTy<'tcx> {
/// Produces a MemPlace that works for ZST but nothing else
#[inline]
pub fn dangling(layout: TyLayout<'tcx>, cx: impl HasDataLayout) -> Self {
MPlaceTy {
mplace: MemPlace::from_scalar_ptr(
Scalar::from_uint(layout.align.abi(), cx.pointer_size()),
layout.align
),
layout
}
}
#[inline]
fn from_aligned_ptr(ptr: Pointer, layout: TyLayout<'tcx>) -> Self {
MPlaceTy { mplace: MemPlace::from_ptr(ptr, layout.align), layout }
@ -555,6 +567,13 @@ impl<'a, 'mir, 'tcx, M: Machine<'a, 'mir, 'tcx>> EvalContext<'a, 'mir, 'tcx, M>
dest: PlaceTy<'tcx>,
) -> EvalResult<'tcx> {
trace!("write_value: {:?} <- {:?}", *dest, src_val);
// Check that the value actually is okay for that type
if M::ENFORCE_VALIDITY {
// Something changed somewhere, better make sure it matches the type!
let op = OpTy { op: Operand::Immediate(src_val), layout: dest.layout };
self.validate_operand(op, &mut vec![], None, /*const_mode*/false)?;
}
// See if we can avoid an allocation. This is the counterpart to `try_read_value`,
// but not factored as a separate function.
let mplace = match dest.place {
@ -576,7 +595,8 @@ impl<'a, 'mir, 'tcx, M: Machine<'a, 'mir, 'tcx>> EvalContext<'a, 'mir, 'tcx, M>
self.write_value_to_mplace(src_val, dest)
}
/// Write a value to memory
/// Write a value to memory. This does NOT do validation, so you better had already
/// done that before calling this!
fn write_value_to_mplace(
&mut self,
value: Value,
@ -640,12 +660,18 @@ impl<'a, 'mir, 'tcx, M: Machine<'a, 'mir, 'tcx>> EvalContext<'a, 'mir, 'tcx, M>
};
// Slow path, this does not fit into an immediate. Just memcpy.
trace!("copy_op: {:?} <- {:?}", *dest, *src);
let (dest_ptr, dest_align) = self.force_allocation(dest)?.to_scalar_ptr_align();
let dest = self.force_allocation(dest)?;
let (dest_ptr, dest_align) = dest.to_scalar_ptr_align();
self.memory.copy(
src_ptr, src_align,
dest_ptr, dest_align,
src.layout.size, false
)
)?;
if M::ENFORCE_VALIDITY {
// Something changed somewhere, better make sure it matches the type!
self.validate_operand(dest.into(), &mut vec![], None, /*const_mode*/false)?;
}
Ok(())
}
/// Make sure that a place is in memory, and return where it is.
@ -668,6 +694,8 @@ impl<'a, 'mir, 'tcx, M: Machine<'a, 'mir, 'tcx>> EvalContext<'a, 'mir, 'tcx, M>
// that has different alignment than the outer field.
let local_layout = self.layout_of_local(frame, local)?;
let ptr = self.allocate(local_layout, MemoryKind::Stack)?;
// We don't have to validate as we can assume the local
// was already valid for its type.
self.write_value_to_mplace(value, ptr)?;
let mplace = ptr.mplace;
// Update the local

View File

@ -287,7 +287,7 @@ impl<'a, 'mir, 'tcx, M: Machine<'a, 'mir, 'tcx>> EvalContext<'a, 'mir, 'tcx, M>
let return_place = match dest {
Some(place) => *place,
None => Place::null(&self),
None => Place::null(&self), // any access will error. good!
};
self.push_stack_frame(
instance,
@ -373,6 +373,20 @@ impl<'a, 'mir, 'tcx, M: Machine<'a, 'mir, 'tcx>> EvalContext<'a, 'mir, 'tcx, M>
trace!("Caller has too many args over");
return err!(FunctionArgCountMismatch);
}
// Don't forget to check the return type!
if let Some(caller_ret) = dest {
let callee_ret = self.eval_place(&mir::Place::Local(mir::RETURN_PLACE))?;
if !Self::check_argument_compat(caller_ret.layout, callee_ret.layout) {
return err!(FunctionRetMismatch(
caller_ret.layout.ty, callee_ret.layout.ty
));
}
} else {
// FIXME: The caller thinks this function cannot return. How do
// we verify that the callee agrees?
// On the plus side, the the callee ever writes to its return place,
// that will be detected as UB (because we set that to NULL above).
}
Ok(())
})();
match res {

View File

@ -11,18 +11,18 @@
use std::fmt::Write;
use syntax_pos::symbol::Symbol;
use rustc::ty::layout::{self, Size, Primitive};
use rustc::ty::{self, Ty};
use rustc::ty::layout::{self, Size, Align, TyLayout};
use rustc::ty;
use rustc_data_structures::fx::FxHashSet;
use rustc::mir::interpret::{
Scalar, AllocType, EvalResult, EvalErrorKind, PointerArithmetic
Scalar, AllocType, EvalResult, EvalErrorKind
};
use super::{
OpTy, Machine, EvalContext, ScalarMaybeUndef
ValTy, OpTy, MPlaceTy, Machine, EvalContext, ScalarMaybeUndef
};
macro_rules! validation_failure{
macro_rules! validation_failure {
($what:expr, $where:expr, $details:expr) => {{
let where_ = path_format($where);
let where_ = if where_.is_empty() {
@ -49,6 +49,22 @@ macro_rules! validation_failure{
}};
}
macro_rules! try_validation {
($e:expr, $what:expr, $where:expr, $details:expr) => {{
match $e {
Ok(x) => x,
Err(_) => return validation_failure!($what, $where, $details),
}
}};
($e:expr, $what:expr, $where:expr) => {{
match $e {
Ok(x) => x,
Err(_) => return validation_failure!($what, $where),
}
}}
}
/// We want to show a nice path to the invalid field for diagnotsics,
/// but avoid string operations in the happy case where no error happens.
/// So we track a `Vec<PathElem>` where `PathElem` contains all the data we
@ -63,6 +79,23 @@ pub enum PathElem {
Tag,
}
/// State for tracking recursive validation of references
pub struct RefTracking<'tcx> {
pub seen: FxHashSet<(OpTy<'tcx>)>,
pub todo: Vec<(OpTy<'tcx>, Vec<PathElem>)>,
}
impl<'tcx> RefTracking<'tcx> {
pub fn new(op: OpTy<'tcx>) -> Self {
let mut ref_tracking = RefTracking {
seen: FxHashSet(),
todo: vec![(op, Vec::new())],
};
ref_tracking.seen.insert(op);
ref_tracking
}
}
// Adding a Deref and making a copy of the path to be put into the queue
// always go together. This one does it with only new allocation.
fn path_clone_and_deref(path: &Vec<PathElem>) -> Vec<PathElem> {
@ -95,133 +128,251 @@ fn path_format(path: &Vec<PathElem>) -> String {
out
}
fn scalar_format(value: ScalarMaybeUndef) -> 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(),
}
}
impl<'a, 'mir, 'tcx, M: Machine<'a, 'mir, 'tcx>> EvalContext<'a, 'mir, 'tcx, M> {
fn validate_scalar(
/// Make sure that `value` is valid for `ty`, *assuming* `ty` is a primitive type.
fn validate_primitive_type(
&self,
value: ValTy<'tcx>,
path: &Vec<PathElem>,
ref_tracking: Option<&mut RefTracking<'tcx>>,
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(_) => {
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.
}
}
_ if ty.is_box() || ty.is_region_ptr() || ty.is_unsafe_ptr() => {
// Handle fat pointers. We also check fat raw pointers,
// their metadata must be valid!
// This also checks that the ptr itself is initialized, which
// seems reasonable even for raw pointers.
let place = try_validation!(self.ref_to_mplace(value),
"undefined data in pointer", path);
// Check metadata early, for better diagnostics
if place.layout.is_unsized() {
let tail = self.tcx.struct_tail(place.layout.ty);
match tail.sty {
ty::Dynamic(..) => {
let vtable = try_validation!(place.extra.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!(place.extra.unwrap().to_usize(self),
"non-integer slice length in fat pointer", path);
}
ty::Foreign(..) => {
// Unsized, but not fat.
}
_ =>
bug!("Unexpected unsized type tail: {:?}", tail),
}
}
// for safe ptrs, also check the ptr values itself
if !ty.is_unsafe_ptr() {
// Make sure this is non-NULL and aligned
let (size, align) = self.size_and_align_of(place.extra, place.layout)?;
match self.memory.check_align(place.ptr, align) {
Ok(_) => {},
Err(err) => match err.kind {
EvalErrorKind::InvalidNullPointerUsage =>
return validation_failure!("NULL reference", path),
EvalErrorKind::AlignmentCheckFailed { .. } =>
return validation_failure!("unaligned reference", path),
_ =>
return validation_failure!(
"dangling (deallocated) reference", path
),
}
}
// non-ZST also have to be dereferencable
if size != Size::ZERO {
let ptr = try_validation!(place.ptr.to_ptr(),
"integer pointer in non-ZST reference", path);
if const_mode {
// 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(());
}
}
}
try_validation!(self.memory.check_bounds(ptr, size, false),
"dangling (not entirely in bounds) reference", path);
}
if let Some(ref_tracking) = ref_tracking {
// 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 catched earlier"),
_ => bug!("Unexpected primitive type {}", value.layout.ty)
}
Ok(())
}
/// Make sure that `value` matches the
fn validate_scalar_layout(
&self,
value: ScalarMaybeUndef,
size: Size,
scalar: &layout::Scalar,
path: &Vec<PathElem>,
ty: Ty,
layout: &layout::Scalar,
) -> EvalResult<'tcx> {
trace!("validate scalar: {:#?}, {:#?}, {:#?}, {}", value, size, scalar, ty);
let (lo, hi) = scalar.valid_range.clone().into_inner();
let value = match value {
ScalarMaybeUndef::Scalar(scalar) => scalar,
ScalarMaybeUndef::Undef => return validation_failure!("undefined bytes", path),
};
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);
if lo == 0 && hi == max_hi {
// 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::Bits { bits, size: value_size } => {
assert_eq!(value_size as u64, size.bytes());
bits
},
Scalar::Ptr(_) => {
match ty.sty {
ty::Bool |
ty::Char |
ty::Float(_) |
ty::Int(_) |
ty::Uint(_) => {
return validation_failure!(
"a pointer",
path,
format!("the type {}", ty.sty)
);
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);
}
ty::RawPtr(_) |
ty::Ref(_, _, _) |
ty::FnPtr(_) => {}
_ => { unreachable!(); }
}
let ptr_size = self.pointer_size();
let ptr_max = u128::max_value() >> (128 - ptr_size.bits());
return if lo > hi {
if lo - hi == 1 {
// no gap, all values are ok
Ok(())
} else if hi < ptr_max || lo > 1 {
let max = u128::max_value() >> (128 - size.bits());
validation_failure!(
"pointer",
path,
format!("something in the range {:?} or {:?}", 0..=lo, hi..=max)
)
} else {
Ok(())
}
} else if hi < ptr_max || lo > 1 {
validation_failure!(
"pointer",
path,
format!("something in the range {:?}", scalar.valid_range)
)
return Ok(());
} else {
Ok(())
};
},
};
// char gets a special treatment, because its number space is not contiguous so `TyLayout`
// has no special checks for chars
match ty.sty {
ty::Char => {
debug_assert_eq!(size.bytes(), 4);
if ::std::char::from_u32(bits as u32).is_none() {
// Conservatively, we reject, because the pointer *could* have this
// value.
return validation_failure!(
"character",
"a pointer",
path,
"a valid unicode codepoint"
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 {
if in_range(0..=hi) || in_range(lo..=u128::max_value()) {
// wrapping around
if in_range(0..=hi) || in_range(lo..=max_hi) {
Ok(())
} else {
validation_failure!(
bits,
path,
format!("something in the range {:?} or {:?}", ..=hi, lo..)
format!("something in the range {:?} or {:?}", 0..=hi, lo..=max_hi)
)
}
} else {
if in_range(scalar.valid_range.clone()) {
if in_range(layout.valid_range.clone()) {
Ok(())
} else {
validation_failure!(
bits,
path,
format!("something in the range {:?}", scalar.valid_range)
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`.
/// 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>,
path: &mut Vec<PathElem>,
seen: &mut FxHashSet<(OpTy<'tcx>)>,
todo: &mut Vec<(OpTy<'tcx>, Vec<PathElem>)>,
mut ref_tracking: Option<&mut RefTracking<'tcx>>,
const_mode: bool,
) -> EvalResult<'tcx> {
trace!("validate_operand: {:?}, {:#?}", *dest, dest.layout);
trace!("validate_operand: {:?}, {:?}", *dest, dest.layout.ty);
// Find the right variant. We have to handle this as a prelude, not via
// proper recursion with the new inner layout, to be able to later nicely
// print the field names of the enum field that is being accessed.
let (variant, dest) = match dest.layout.variants {
// 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) {
@ -237,124 +388,104 @@ impl<'a, 'mir, 'tcx, M: Machine<'a, 'mir, 'tcx>> EvalContext<'a, 'mir, 'tcx, M>
),
}
};
let inner_dest = self.operand_downcast(dest, variant)?;
// 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);
(variant, inner_dest)
dest
},
layout::Variants::Single { index } => {
// Pre-processing for trait objects: Treat them at their real type.
// (We do not do this for slices and strings: For slices it is not needed,
// `mplace_array_fields` does the right thing, and for strings there is no
// real type that would show the actual length.)
let dest = match dest.layout.ty.sty {
ty::Dynamic(..) => {
let dest = dest.to_mem_place(); // immediate trait objects are not a thing
match self.unpack_dyn_trait(dest) {
Ok(res) => res.1.into(),
Err(_) =>
return validation_failure!(
"invalid vtable in fat pointer", path
),
}
}
_ => dest
};
(index, dest)
}
layout::Variants::Single { .. } => dest,
};
// Remember the length, in case we need to truncate
let path_len = path.len();
// 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
};
// Validate all fields
match dest.layout.fields {
// primitives are unions with zero fields
// We still check `layout.fields`, not `layout.abi`, because `layout.abi`
// is `Scalar` for newtypes around scalars, but we want to descend through the
// fields to get a proper `path`.
layout::FieldPlacement::Union(0) => {
match dest.layout.abi {
// nothing to do, whatever the pointer points to, it is never going to be read
layout::Abi::Uninhabited =>
return validation_failure!("a value of an uninhabited type", path),
// check that the scalar is a valid pointer or that its bit range matches the
// expectation.
layout::Abi::Scalar(ref scalar_layout) => {
let size = scalar_layout.value.size(self);
let value = match self.read_value(dest) {
Ok(val) => val,
Err(err) => match err.kind {
EvalErrorKind::PointerOutOfBounds { .. } |
EvalErrorKind::ReadUndefBytes(_) =>
return validation_failure!(
"uninitialized or out-of-bounds memory", path
),
_ =>
return validation_failure!(
"unrepresentable data", path
),
}
};
let scalar = value.to_scalar_or_undef();
self.validate_scalar(scalar, size, scalar_layout, &path, dest.layout.ty)?;
if scalar_layout.value == Primitive::Pointer {
// ignore integer pointers, we can't reason about the final hardware
if let Scalar::Ptr(ptr) = scalar.not_undef()? {
let alloc_kind = self.tcx.alloc_map.lock().get(ptr.alloc_id);
if let Some(AllocType::Static(did)) = alloc_kind {
// statics from other crates are already checked.
// extern statics cannot be validated as they have no body.
if !did.is_local() || self.tcx.is_foreign_item(did) {
return Ok(());
}
}
if value.layout.ty.builtin_deref(false).is_some() {
let ptr_op = self.ref_to_mplace(value)?.into();
// we have not encountered this pointer+layout combination
// before.
if seen.insert(ptr_op) {
trace!("Recursing below ptr {:#?}", *value);
todo.push((ptr_op, path_clone_and_deref(path)));
}
}
}
}
},
_ => bug!("bad abi for FieldPlacement::Union(0): {:#?}", dest.layout.abi),
}
// 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)?;
}
layout::FieldPlacement::Union(_) => {
// 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_value(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(..) => {
// 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::Array { stride, .. } if !dest.layout.is_zst() => {
let dest = dest.to_mem_place(); // non-ZST array/slice/str cannot be immediate
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 => {
match self.read_str(dest) {
Ok(_) => {},
Err(err) => match err.kind {
EvalErrorKind::PointerOutOfBounds { .. } |
EvalErrorKind::ReadUndefBytes(_) =>
// The error here looks slightly different than it does
// for slices, because we do not report the index into the
// str at which we are OOB.
return validation_failure!(
"uninitialized or out-of-bounds memory", path
),
_ =>
return validation_failure!(
"non-UTF-8 data in str", path
),
}
}
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 {
@ -390,18 +521,9 @@ impl<'a, 'mir, 'tcx, M: Machine<'a, 'mir, 'tcx>> EvalContext<'a, 'mir, 'tcx, M>
"undefined bytes", path
)
},
EvalErrorKind::PointerOutOfBounds { allocation_size, .. } => {
// If the array access is out-of-bounds, the first
// undefined access is the after the end of the array.
let i = (allocation_size.bytes() * ty_size) as usize;
path.push(PathElem::ArrayElem(i));
},
_ => (),
// Other errors shouldn't be possible
_ => return Err(err),
}
return validation_failure!(
"uninitialized or out-of-bounds memory", path
)
}
}
},
@ -411,83 +533,32 @@ impl<'a, 'mir, 'tcx, M: Machine<'a, 'mir, 'tcx>> EvalContext<'a, 'mir, 'tcx, M>
for (i, field) in self.mplace_array_fields(dest)?.enumerate() {
let field = field?;
path.push(PathElem::ArrayElem(i));
self.validate_operand(field.into(), path, seen, todo)?;
self.validate_operand(
field.into(),
path,
ref_tracking.as_mut().map(|r| &mut **r),
const_mode,
)?;
path.truncate(path_len);
}
}
}
},
layout::FieldPlacement::Array { .. } => {
// An empty array. Nothing to do.
}
layout::FieldPlacement::Arbitrary { ref offsets, .. } => {
// Fat pointers are treated like pointers, not aggregates.
if dest.layout.ty.builtin_deref(true).is_some() {
// This is a fat pointer.
let ptr = match self.read_value(dest.into())
.and_then(|val| self.ref_to_mplace(val))
{
Ok(ptr) => ptr,
Err(_) =>
return validation_failure!(
"undefined location or metadata in fat pointer", path
),
};
// check metadata early, for better diagnostics
match self.tcx.struct_tail(ptr.layout.ty).sty {
ty::Dynamic(..) => {
match ptr.extra.unwrap().to_ptr() {
Ok(_) => {},
Err(_) =>
return validation_failure!(
"non-pointer vtable in fat pointer", path
),
}
// FIXME: More checks for the vtable.
}
ty::Slice(..) | ty::Str => {
match ptr.extra.unwrap().to_usize(self) {
Ok(_) => {},
Err(_) =>
return validation_failure!(
"non-integer slice length in fat pointer", path
),
}
}
_ =>
bug!("Unexpected unsized type tail: {:?}",
self.tcx.struct_tail(ptr.layout.ty)
),
}
// for safe ptrs, recursively check it
if !dest.layout.ty.is_unsafe_ptr() {
let ptr = ptr.into();
if seen.insert(ptr) {
trace!("Recursing below fat ptr {:?}", ptr);
todo.push((ptr, path_clone_and_deref(path)));
}
}
} else {
// Not a pointer, perform regular aggregate handling below
for i in 0..offsets.len() {
let field = self.operand_field(dest, i as u64)?;
path.push(self.aggregate_field_path_elem(dest.layout.ty, variant, i));
self.validate_operand(field, path, seen, todo)?;
path.truncate(path_len);
}
}
}
}
Ok(())
}
fn aggregate_field_path_elem(&self, ty: Ty<'tcx>, variant: usize, field: usize) -> PathElem {
match ty.sty {
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, _, _) => {
let node_id = self.tcx.hir.as_local_node_id(def_id).unwrap();
let freevar = self.tcx.with_freevars(node_id, |fv| fv[field]);
PathElem::ClosureVar(self.tcx.hir.name(freevar.var_id()))
if let Some(node_id) = self.tcx.hir.as_local_node_id(def_id) {
let freevar = self.tcx.with_freevars(node_id, |fv| fv[field]);
PathElem::ClosureVar(self.tcx.hir.name(freevar.var_id()))
} else {
// The closure is not local, so we cannot get the name
PathElem::ClosureVar(Symbol::intern(&field.to_string()))
}
}
// tuples
@ -495,7 +566,10 @@ impl<'a, 'mir, 'tcx, M: Machine<'a, 'mir, 'tcx>> EvalContext<'a, 'mir, 'tcx, M>
// enums
ty::Adt(def, ..) if def.is_enum() => {
let variant = &def.variants[variant];
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)
}
@ -503,7 +577,7 @@ impl<'a, 'mir, 'tcx, M: Machine<'a, 'mir, 'tcx>> EvalContext<'a, 'mir, 'tcx, M>
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 {:?}", ty),
_ => bug!("aggregate_field_path_elem: got non-aggregate type {:?}", layout.ty),
}
}
}

View File

@ -154,6 +154,7 @@ impl<'a, 'mir, 'tcx> ConstPropagator<'a, 'mir, 'tcx> {
// FIXME: figure out the rules and start linting
| FunctionAbiMismatch(..)
| FunctionArgMismatch(..)
| FunctionRetMismatch(..)
| FunctionArgCountMismatch
// fine at runtime, might be a register address or sth
| ReadBytesAsPointer

View File

@ -2,7 +2,7 @@ error[E0080]: this constant likely exhibits undefined behavior
--> $DIR/const-pointer-values-in-various-types.rs:24:5
|
LL | const I32_REF_USIZE_UNION: usize = unsafe { Nonsense { int_32_ref: &3 }.u };
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ type validation failed: encountered a pointer, but expected the type usize
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ type validation failed: encountered a pointer, but expected initialized plain bits
|
= 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
@ -36,7 +36,7 @@ error[E0080]: this constant likely exhibits undefined behavior
--> $DIR/const-pointer-values-in-various-types.rs:36:5
|
LL | const I32_REF_U64_UNION: u64 = unsafe { Nonsense { int_32_ref: &3 }.uint_64 };
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ type validation failed: encountered a pointer, but expected the type u64
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ type validation failed: encountered a pointer, but expected initialized plain bits
|
= 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
@ -74,7 +74,7 @@ error[E0080]: this constant likely exhibits undefined behavior
--> $DIR/const-pointer-values-in-various-types.rs:51:5
|
LL | const I32_REF_I64_UNION: i64 = unsafe { Nonsense { int_32_ref: &3 }.int_64 };
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ type validation failed: encountered a pointer, but expected the type i64
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ type validation failed: encountered a pointer, but expected initialized plain bits
|
= 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
@ -96,7 +96,7 @@ error[E0080]: this constant likely exhibits undefined behavior
--> $DIR/const-pointer-values-in-various-types.rs:60:5
|
LL | const I32_REF_F64_UNION: f64 = unsafe { Nonsense { int_32_ref: &3 }.float_64 };
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ type validation failed: encountered a pointer, but expected the type f64
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ type validation failed: encountered a pointer, but expected initialized plain bits
|
= 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
@ -144,7 +144,7 @@ error[E0080]: this constant likely exhibits undefined behavior
--> $DIR/const-pointer-values-in-various-types.rs:78:5
|
LL | const STR_U64_UNION: u64 = unsafe { Nonsense { stringy: "3" }.uint_64 };
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ type validation failed: encountered a pointer, but expected the type u64
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ type validation failed: encountered a pointer, but expected initialized plain bits
|
= 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
@ -184,7 +184,7 @@ error[E0080]: this constant likely exhibits undefined behavior
--> $DIR/const-pointer-values-in-various-types.rs:93:5
|
LL | const STR_I64_UNION: i64 = unsafe { Nonsense { stringy: "3" }.int_64 };
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ type validation failed: encountered a pointer, but expected the type i64
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ type validation failed: encountered a pointer, but expected initialized plain bits
|
= 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
@ -208,7 +208,7 @@ error[E0080]: this constant likely exhibits undefined behavior
--> $DIR/const-pointer-values-in-various-types.rs:102:5
|
LL | const STR_F64_UNION: f64 = unsafe { Nonsense { stringy: "3" }.float_64 };
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ type validation failed: encountered a pointer, but expected the type f64
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ type validation failed: encountered a pointer, but expected initialized plain bits
|
= 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

@ -14,6 +14,5 @@ use std::mem;
static FOO: bool = unsafe { mem::transmute(3u8) };
//~^ ERROR this static likely exhibits undefined behavior
//~^^ type validation failed: encountered 3, but expected something in the range 0..=1
fn main() {}

View File

@ -18,7 +18,7 @@ error[E0080]: this constant likely exhibits undefined behavior
--> $DIR/ub-enum.rs:45:1
|
LL | const BAD_ENUM_CHAR : Option<(char, char)> = Some(('x', unsafe { TransmuteChar { a: !0 }.b }));
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ type validation failed: encountered character at .Some.0.1, but expected a valid unicode codepoint
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 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

View File

@ -0,0 +1,25 @@
// Copyright 2018 The Rust Project Developers. See the COPYRIGHT
// file at the top-level directory of this distribution and at
// http://rust-lang.org/COPYRIGHT.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.
#![feature(const_transmute)]
use std::mem;
use std::ptr::NonNull;
use std::num::{NonZeroU8, NonZeroUsize};
const NULL_PTR: NonNull<u8> = unsafe { mem::transmute(0usize) };
//~^ ERROR this constant likely exhibits undefined behavior
const NULL_U8: NonZeroU8 = unsafe { mem::transmute(0u8) };
//~^ ERROR this constant likely exhibits undefined behavior
const NULL_USIZE: NonZeroUsize = unsafe { mem::transmute(0usize) };
//~^ ERROR this constant likely exhibits undefined behavior
fn main() {}

View File

@ -0,0 +1,27 @@
error[E0080]: this constant likely exhibits undefined behavior
--> $DIR/ub-nonnull.rs:17:1
|
LL | const NULL_PTR: NonNull<u8> = unsafe { mem::transmute(0usize) };
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ type validation failed: encountered 0, 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
error[E0080]: this constant likely exhibits undefined behavior
--> $DIR/ub-nonnull.rs:20:1
|
LL | const NULL_U8: NonZeroU8 = unsafe { mem::transmute(0u8) };
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ type validation failed: encountered 0, 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
error[E0080]: this constant likely exhibits undefined behavior
--> $DIR/ub-nonnull.rs:22:1
|
LL | const NULL_USIZE: NonZeroUsize = unsafe { mem::transmute(0usize) };
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ type validation failed: encountered 0, 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
error: aborting due to 3 previous errors
For more information about this error, try `rustc --explain E0080`.

View File

@ -0,0 +1,27 @@
// Copyright 2018 The Rust Project Developers. See the COPYRIGHT
// file at the top-level directory of this distribution and at
// http://rust-lang.org/COPYRIGHT.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.
#![feature(const_transmute)]
use std::mem;
const UNALIGNED: &u16 = unsafe { mem::transmute(&[0u8; 4]) };
//~^ ERROR this constant likely exhibits undefined behavior
const NULL: &u16 = unsafe { mem::transmute(0usize) };
//~^ ERROR this constant likely exhibits undefined behavior
const REF_AS_USIZE: usize = unsafe { mem::transmute(&0) };
//~^ ERROR this constant likely exhibits undefined behavior
const USIZE_AS_REF: &'static u8 = unsafe { mem::transmute(1337usize) };
//~^ ERROR this constant likely exhibits undefined behavior
fn main() {}

View File

@ -0,0 +1,35 @@
error[E0080]: this constant likely exhibits undefined behavior
--> $DIR/ub-ref.rs:15:1
|
LL | const UNALIGNED: &u16 = unsafe { mem::transmute(&[0u8; 4]) };
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ type validation failed: encountered unaligned reference
|
= 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]: this constant likely exhibits undefined behavior
--> $DIR/ub-ref.rs:18:1
|
LL | const NULL: &u16 = unsafe { mem::transmute(0usize) };
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ type validation failed: encountered 0, 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
error[E0080]: this constant likely exhibits undefined behavior
--> $DIR/ub-ref.rs:21:1
|
LL | const REF_AS_USIZE: usize = unsafe { mem::transmute(&0) };
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ type validation failed: encountered a pointer, but expected initialized plain bits
|
= 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]: this constant likely exhibits undefined behavior
--> $DIR/ub-ref.rs:24:1
|
LL | const USIZE_AS_REF: &'static u8 = unsafe { mem::transmute(1337usize) };
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ type validation failed: encountered integer pointer in non-ZST reference
|
= 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 4 previous errors
For more information about this error, try `rustc --explain E0080`.

View File

@ -8,15 +8,20 @@
// option. This file may not be copied, modified, or distributed
// except according to those terms.
union Foo {
a: u8,
b: Bar,
}
#![feature(const_transmute)]
use std::mem;
#[derive(Copy, Clone)]
enum Bar {}
const BAD_BAD_BAD: Bar = unsafe { Foo { a: 1 }.b};
const BAD_BAD_BAD: Bar = unsafe { mem::transmute(()) };
//~^ ERROR this constant likely exhibits undefined behavior
const BAD_BAD_REF: &Bar = unsafe { mem::transmute(1usize) };
//~^ ERROR this constant likely exhibits undefined behavior
const BAD_BAD_ARRAY: [Bar; 1] = unsafe { mem::transmute(()) };
//~^ ERROR this constant likely exhibits undefined behavior
fn main() {

View File

@ -1,11 +1,27 @@
error[E0080]: this constant likely exhibits undefined behavior
--> $DIR/ub-uninhabit.rs:19:1
--> $DIR/ub-uninhabit.rs:18:1
|
LL | const BAD_BAD_BAD: Bar = unsafe { Foo { a: 1 }.b};
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ type validation failed: encountered a value of an uninhabited type
LL | const BAD_BAD_BAD: Bar = unsafe { mem::transmute(()) };
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ type validation failed: encountered a value of an uninhabited type
|
= 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 previous error
error[E0080]: this constant likely exhibits undefined behavior
--> $DIR/ub-uninhabit.rs:21:1
|
LL | const BAD_BAD_REF: &Bar = unsafe { mem::transmute(1usize) };
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ type validation failed: encountered a value of an uninhabited type at .<deref>
|
= 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]: this constant likely exhibits undefined behavior
--> $DIR/ub-uninhabit.rs:24:1
|
LL | const BAD_BAD_ARRAY: [Bar; 1] = unsafe { mem::transmute(()) };
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ type validation failed: encountered a value of an uninhabited type at [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: aborting due to 3 previous errors
For more information about this error, try `rustc --explain E0080`.

View File

@ -1,22 +0,0 @@
// Copyright 2018 The Rust Project Developers. See the COPYRIGHT
// file at the top-level directory of this distribution and at
// http://rust-lang.org/COPYRIGHT.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.
// compile-pass
union Foo {
a: &'static u8,
b: usize,
}
// This might point to an invalid address, but that's the user's problem
const USIZE_AS_STATIC_REF: &'static u8 = unsafe { Foo { b: 1337 }.a};
fn main() {
}

View File

@ -13,7 +13,7 @@ LL | / const FIELD_PATH: Struct = Struct { //~ ERROR this constant likely exhibi
LL | | a: 42,
LL | | b: unsafe { UNION.field3 },
LL | | };
| |__^ type validation failed: encountered undefined bytes at .b
| |__^ type validation failed: encountered uninitialized bytes at .b, but expected initialized plain bits
|
= 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

@ -2,7 +2,7 @@ error[E0080]: this constant likely exhibits undefined behavior
--> $DIR/union-ub-fat-ptr.rs:87:1
|
LL | const B: &str = unsafe { SliceTransmute { repr: SliceRepr { ptr: &42, len: 999 } }.str};
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ type validation failed: encountered uninitialized or out-of-bounds memory at .<deref>
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ type validation failed: encountered dangling (not entirely in bounds) reference
|
= 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
@ -26,7 +26,7 @@ error[E0080]: this constant likely exhibits undefined behavior
--> $DIR/union-ub-fat-ptr.rs:99:1
|
LL | const B2: &[u8] = unsafe { SliceTransmute { repr: SliceRepr { ptr: &42, len: 999 } }.slice};
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ type validation failed: encountered uninitialized or out-of-bounds memory at .<deref>[1]
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ type validation failed: encountered dangling (not entirely in bounds) reference
|
= 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
@ -42,7 +42,7 @@ error[E0080]: this constant likely exhibits undefined behavior
--> $DIR/union-ub-fat-ptr.rs:106:1
|
LL | const D: &Trait = unsafe { DynTransmute { repr: DynRepr { ptr: &92, vtable: &3 } }.rust};
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ type validation failed: encountered invalid vtable in fat pointer at .<deref>
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ type validation failed: encountered invalid drop fn in 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
@ -50,7 +50,7 @@ error[E0080]: this constant likely exhibits undefined behavior
--> $DIR/union-ub-fat-ptr.rs:109:1
|
LL | const E: &Trait = unsafe { DynTransmute { repr2: DynRepr2 { ptr: &92, vtable: &3 } }.rust};
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ type validation failed: encountered invalid vtable in fat pointer at .<deref>
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ type validation failed: encountered invalid drop fn in 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
@ -98,7 +98,7 @@ error[E0080]: this constant likely exhibits undefined behavior
--> $DIR/union-ub-fat-ptr.rs:132:1
|
LL | const J1: &str = unsafe { SliceTransmute { slice: &[0xFF] }.str };
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ type validation failed: encountered non-UTF-8 data in str at .<deref>
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ type validation failed: encountered uninitialized or non-UTF-8 data in str at .<deref>
|
= 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
@ -106,7 +106,7 @@ error[E0080]: this constant likely exhibits undefined behavior
--> $DIR/union-ub-fat-ptr.rs:135:1
|
LL | const J2: &MyStr = unsafe { SliceTransmute { slice: &[0xFF] }.my_str };
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ type validation failed: encountered non-UTF-8 data in str at .<deref>.0
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ type validation failed: encountered uninitialized or non-UTF-8 data in str at .<deref>.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

View File

@ -41,5 +41,4 @@ const BAD_BOOL: bool = unsafe { DummyUnion { u8: 42 }.bool};
const BAD_UNION: Foo = unsafe { Bar { u8: 42 }.foo };
fn main() {
}
fn main() {}

View File

@ -0,0 +1,18 @@
// compile-pass
// Some constants that *are* valid
#![feature(const_transmute)]
use std::mem;
use std::ptr::NonNull;
use std::num::{NonZeroU8, NonZeroUsize};
const NON_NULL_PTR1: NonNull<u8> = unsafe { mem::transmute(1usize) };
const NON_NULL_PTR2: NonNull<u8> = unsafe { mem::transmute(&0) };
const NON_NULL_U8: NonZeroU8 = unsafe { mem::transmute(1u8) };
const NON_NULL_USIZE: NonZeroUsize = unsafe { mem::transmute(1usize) };
const UNIT: () = ();
fn main() {}

View File

@ -11,9 +11,9 @@
#![allow(safe_extern_statics, warnings)]
extern {
pub static symbol: ();
pub static symbol: u32;
}
static CRASH: () = symbol;
static CRASH: u32 = symbol;
//~^ ERROR could not evaluate static initializer
//~| tried to read from foreign (extern) static

View File

@ -1,8 +1,8 @@
error[E0080]: could not evaluate static initializer
--> $DIR/issue-14227.rs:16:20
--> $DIR/issue-14227.rs:16:21
|
LL | static CRASH: () = symbol;
| ^^^^^^ tried to read from foreign (extern) static
LL | static CRASH: u32 = symbol;
| ^^^^^^ tried to read from foreign (extern) static
error: aborting due to previous error

@ -1 +1 @@
Subproject commit e8f6973e2d40ab39e30cdbe0cf8e77a72c867d4f
Subproject commit cc275c63a90d4bea394e76607b2e10611eb1be36