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:
commit
0e07c4281c
@ -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 {
|
||||
|
@ -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 } =>
|
||||
|
@ -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),
|
||||
|
@ -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(())
|
||||
|
@ -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>,
|
||||
|
@ -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
|
||||
|
@ -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>;
|
||||
|
@ -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)?;
|
||||
|
@ -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;
|
||||
|
@ -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
|
||||
|
@ -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 {
|
||||
|
@ -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),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
||||
|
@ -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() {}
|
||||
|
@ -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
|
||||
|
||||
|
25
src/test/ui/consts/const-eval/ub-nonnull.rs
Normal file
25
src/test/ui/consts/const-eval/ub-nonnull.rs
Normal 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() {}
|
27
src/test/ui/consts/const-eval/ub-nonnull.stderr
Normal file
27
src/test/ui/consts/const-eval/ub-nonnull.stderr
Normal 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`.
|
27
src/test/ui/consts/const-eval/ub-ref.rs
Normal file
27
src/test/ui/consts/const-eval/ub-ref.rs
Normal 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() {}
|
35
src/test/ui/consts/const-eval/ub-ref.stderr
Normal file
35
src/test/ui/consts/const-eval/ub-ref.stderr
Normal 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`.
|
@ -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() {
|
||||
|
@ -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`.
|
||||
|
@ -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() {
|
||||
}
|
@ -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
|
||||
|
||||
|
@ -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
|
||||
|
@ -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() {}
|
||||
|
18
src/test/ui/consts/const-eval/valid-const.rs
Normal file
18
src/test/ui/consts/const-eval/valid-const.rs
Normal 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() {}
|
@ -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
|
||||
|
||||
|
@ -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
|
Loading…
Reference in New Issue
Block a user