| //! Check the validity invariant of a given value, and tell the user | |
| //! where in the value it got violated. | |
| //! In const context, this goes even further and tries to approximate const safety. | |
| //! That's useful because it means other passes (e.g. promotion) can rely on `const`s | |
| //! to be const-safe. | |
| use std::borrow::Cow; | |
| use std::fmt::{self, Write}; | |
| use std::hash::Hash; | |
| use std::mem; | |
| use std::num::NonZero; | |
| use either::{Left, Right}; | |
| use hir::def::DefKind; | |
| use rustc_abi::{ | |
| BackendRepr, FieldIdx, FieldsShape, Scalar as ScalarAbi, Size, VariantIdx, Variants, | |
| WrappingRange, | |
| }; | |
| use rustc_ast::Mutability; | |
| use rustc_data_structures::fx::FxHashSet; | |
| use rustc_hir as hir; | |
| use rustc_middle::bug; | |
| use rustc_middle::mir::interpret::{ | |
| InterpErrorKind, InvalidMetaKind, Misalignment, Provenance, UnsupportedOpInfo, alloc_range, | |
| interp_ok, | |
| }; | |
| use rustc_middle::ty::layout::{LayoutCx, TyAndLayout}; | |
| use rustc_middle::ty::{self, Ty}; | |
| use rustc_span::{Symbol, sym}; | |
| use tracing::trace; | |
| use super::machine::AllocMap; | |
| use super::{ | |
| AllocId, CheckInAllocMsg, GlobalAlloc, ImmTy, Immediate, InterpCx, InterpResult, MPlaceTy, | |
| Machine, MemPlaceMeta, PlaceTy, Pointer, Projectable, Scalar, ValueVisitor, err_ub, | |
| format_interp_error, | |
| }; | |
| use crate::enter_trace_span; | |
| // for the validation errors | |
| #[rustfmt::skip] | |
| use super::InterpErrorKind::UndefinedBehavior as Ub; | |
| use super::InterpErrorKind::Unsupported as Unsup; | |
| use super::UndefinedBehaviorInfo::*; | |
| use super::UnsupportedOpInfo::*; | |
| macro_rules! err_validation_failure { | |
| ($where:expr, $msg:expr ) => {{ | |
| let where_ = &$where; | |
| let path = if !where_.projs.is_empty() { | |
| let mut path = String::new(); | |
| write_path(&mut path, &where_.projs); | |
| Some(path) | |
| } else { | |
| None | |
| }; | |
| #[allow(unused)] | |
| use ValidationErrorKind::*; | |
| let msg = ValidationErrorKind::from($msg); | |
| err_ub!(ValidationError { | |
| orig_ty: where_.orig_ty, | |
| path, | |
| ptr_bytes_warning: msg.ptr_bytes_warning(), | |
| msg: msg.to_string(), | |
| }) | |
| }}; | |
| } | |
| macro_rules! throw_validation_failure { | |
| ($where:expr, $msg:expr ) => { | |
| do yeet err_validation_failure!($where, $msg) | |
| }; | |
| } | |
| /// If $e throws an error matching the pattern, throw a validation failure. | |
| /// Other errors are passed back to the caller, unchanged -- and if they reach the root of | |
| /// the visitor, we make sure only validation errors and `InvalidProgram` errors are left. | |
| /// This lets you use the patterns as a kind of validation list, asserting which errors | |
| /// can possibly happen: | |
| /// | |
| /// ```ignore(illustrative) | |
| /// let v = try_validation!(some_fn(x), some_path, { | |
| /// Foo | Bar | Baz => format!("some failure involving {x}"), | |
| /// }); | |
| /// ``` | |
| /// | |
| /// The patterns must be of type `UndefinedBehaviorInfo`. | |
| macro_rules! try_validation { | |
| ($e:expr, $where:expr, | |
| $( $( $p:pat_param )|+ => $msg:expr ),+ $(,)? | |
| ) => {{ | |
| $e.map_err_kind(|e| { | |
| // We catch the error and turn it into a validation failure. We are okay with | |
| // allocation here as this can only slow down builds that fail anyway. | |
| match e { | |
| $( | |
| $($p)|+ => { | |
| err_validation_failure!( | |
| $where, | |
| $msg | |
| ) | |
| } | |
| ),+, | |
| e => e, | |
| } | |
| })? | |
| }}; | |
| } | |
| #[derive(Debug, Clone, Copy)] | |
| enum PointerKind { | |
| Ref(Mutability), | |
| Box, | |
| } | |
| impl fmt::Display for PointerKind { | |
| fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { | |
| let str = match self { | |
| PointerKind::Ref(_) => "reference", | |
| PointerKind::Box => "box", | |
| }; | |
| write!(f, "{str}") | |
| } | |
| } | |
| #[derive(Debug)] | |
| enum ExpectedKind { | |
| Reference, | |
| Box, | |
| RawPtr, | |
| Bool, | |
| Char, | |
| Float, | |
| Int, | |
| FnPtr, | |
| Str, | |
| } | |
| impl fmt::Display for ExpectedKind { | |
| fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { | |
| let str = match self { | |
| ExpectedKind::Reference => "expected a reference", | |
| ExpectedKind::Box => "expected a box", | |
| ExpectedKind::RawPtr => "expected a raw pointer", | |
| ExpectedKind::Bool => "expected a boolean", | |
| ExpectedKind::Char => "expected a unicode scalar value", | |
| ExpectedKind::Float => "expected a floating point number", | |
| ExpectedKind::Int => "expected an integer", | |
| ExpectedKind::FnPtr => "expected a function pointer", | |
| ExpectedKind::Str => "expected a string", | |
| }; | |
| write!(f, "{str}") | |
| } | |
| } | |
| impl From<PointerKind> for ExpectedKind { | |
| fn from(x: PointerKind) -> ExpectedKind { | |
| match x { | |
| PointerKind::Box => ExpectedKind::Box, | |
| PointerKind::Ref(_) => ExpectedKind::Reference, | |
| } | |
| } | |
| } | |
| /// Validation errors that can be emitted in one than one place get a variant here so that | |
| /// we format them consistently. Everything else uses the `String` fallback. | |
| #[derive(Debug)] | |
| enum ValidationErrorKind<'tcx> { | |
| Uninit { | |
| expected: ExpectedKind, | |
| }, | |
| PointerAsInt { | |
| expected: ExpectedKind, | |
| }, | |
| PartialPointer, | |
| InvalidMetaWrongTrait { | |
| /// The vtable that was actually referenced by the wide pointer metadata. | |
| vtable_dyn_type: &'tcx ty::List<ty::PolyExistentialPredicate<'tcx>>, | |
| /// The vtable that was expected at the point in MIR that it was accessed. | |
| expected_dyn_type: &'tcx ty::List<ty::PolyExistentialPredicate<'tcx>>, | |
| }, | |
| GeneralError { | |
| msg: String, | |
| }, | |
| } | |
| impl<'tcx> ValidationErrorKind<'tcx> { | |
| // We don't do this via `fmt::Display` to so that we can do a move in the `GeneralError` case. | |
| fn to_string(self) -> String { | |
| use ValidationErrorKind::*; | |
| match self { | |
| Uninit { expected } => format!("encountered uninitialized memory, but {expected}"), | |
| PointerAsInt { expected } => format!("encountered a pointer, but {expected}"), | |
| PartialPointer => format!("encountered a partial pointer or a mix of pointers"), | |
| InvalidMetaWrongTrait { vtable_dyn_type, expected_dyn_type } => format!( | |
| "wrong trait in wide pointer vtable: expected `{expected_dyn_type}`, but encountered `{vtable_dyn_type}`" | |
| ), | |
| GeneralError { msg } => msg, | |
| } | |
| } | |
| fn ptr_bytes_warning(&self) -> bool { | |
| use ValidationErrorKind::*; | |
| matches!(self, PointerAsInt { .. } | PartialPointer) | |
| } | |
| } | |
| impl<'tcx> From<String> for ValidationErrorKind<'tcx> { | |
| fn from(msg: String) -> Self { | |
| ValidationErrorKind::GeneralError { msg } | |
| } | |
| } | |
| fn fmt_range(r: WrappingRange, max_hi: u128) -> String { | |
| let WrappingRange { start: lo, end: hi } = r; | |
| assert!(hi <= max_hi); | |
| if lo > hi { | |
| format!("less or equal to {hi}, or greater or equal to {lo}") | |
| } else if lo == hi { | |
| format!("equal to {lo}") | |
| } else if lo == 0 { | |
| assert!(hi < max_hi, "should not be printing if the range covers everything"); | |
| format!("less or equal to {hi}") | |
| } else if hi == max_hi { | |
| assert!(lo > 0, "should not be printing if the range covers everything"); | |
| format!("greater or equal to {lo}") | |
| } else { | |
| format!("in the range {lo}..={hi}") | |
| } | |
| } | |
| /// We want to show a nice path to the invalid field for diagnostics, | |
| /// 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 | |
| /// need to later print something for the user. | |
| #[derive(Copy, Clone, Debug)] | |
| pub enum PathElem<'tcx> { | |
| Field(Symbol), | |
| Variant(Symbol), | |
| CoroutineState(VariantIdx), | |
| CapturedVar(Symbol), | |
| ArrayElem(usize), | |
| TupleElem(usize), | |
| Deref, | |
| EnumTag, | |
| CoroutineTag, | |
| DynDowncast(Ty<'tcx>), | |
| Vtable, | |
| } | |
| #[derive(Clone, Debug)] | |
| pub struct Path<'tcx> { | |
| orig_ty: Ty<'tcx>, | |
| projs: Vec<PathElem<'tcx>>, | |
| } | |
| impl<'tcx> Path<'tcx> { | |
| fn new(ty: Ty<'tcx>) -> Self { | |
| Self { orig_ty: ty, projs: vec![] } | |
| } | |
| } | |
| /// Extra things to check for during validation of CTFE results. | |
| #[derive(Copy, Clone)] | |
| pub enum CtfeValidationMode { | |
| /// Validation of a `static` | |
| Static { mutbl: Mutability }, | |
| /// Validation of a promoted. | |
| Promoted, | |
| /// Validation of a `const`. | |
| /// `allow_immutable_unsafe_cell` says whether we allow `UnsafeCell` in immutable memory (which is the | |
| /// case for the top-level allocation of a `const`, where this is fine because the allocation will be | |
| /// copied at each use site). | |
| Const { allow_immutable_unsafe_cell: bool }, | |
| } | |
| impl CtfeValidationMode { | |
| fn allow_immutable_unsafe_cell(self) -> bool { | |
| match self { | |
| CtfeValidationMode::Static { .. } => false, | |
| CtfeValidationMode::Promoted { .. } => false, | |
| CtfeValidationMode::Const { allow_immutable_unsafe_cell, .. } => { | |
| allow_immutable_unsafe_cell | |
| } | |
| } | |
| } | |
| } | |
| /// State for tracking recursive validation of references | |
| pub struct RefTracking<T, PATH = ()> { | |
| seen: FxHashSet<T>, | |
| todo: Vec<(T, PATH)>, | |
| } | |
| impl<T: Clone + Eq + Hash + std::fmt::Debug, PATH> RefTracking<T, PATH> { | |
| pub fn empty() -> Self { | |
| RefTracking { seen: FxHashSet::default(), todo: vec![] } | |
| } | |
| pub fn next(&mut self) -> Option<(T, PATH)> { | |
| self.todo.pop() | |
| } | |
| fn track(&mut self, val: T, path: impl FnOnce() -> PATH) { | |
| if self.seen.insert(val.clone()) { | |
| trace!("Recursing below ptr {:#?}", val); | |
| let path = path(); | |
| // Remember to come back to this later. | |
| self.todo.push((val, path)); | |
| } | |
| } | |
| } | |
| impl<'tcx, T: Clone + Eq + Hash + std::fmt::Debug> RefTracking<T, Path<'tcx>> { | |
| pub fn new(val: T, ty: Ty<'tcx>) -> Self { | |
| let mut ref_tracking_for_consts = | |
| RefTracking { seen: FxHashSet::default(), todo: vec![(val.clone(), Path::new(ty))] }; | |
| ref_tracking_for_consts.seen.insert(val); | |
| ref_tracking_for_consts | |
| } | |
| } | |
| /// Format a path | |
| fn write_path(out: &mut String, path: &[PathElem<'_>]) { | |
| use self::PathElem::*; | |
| for elem in path.iter() { | |
| match elem { | |
| Field(name) => write!(out, ".{name}"), | |
| EnumTag => write!(out, ".<enum-tag>"), | |
| Variant(name) => write!(out, ".<enum-variant({name})>"), | |
| CoroutineTag => write!(out, ".<coroutine-tag>"), | |
| CoroutineState(idx) => write!(out, ".<coroutine-state({})>", idx.index()), | |
| CapturedVar(name) => write!(out, ".<captured-var({name})>"), | |
| TupleElem(idx) => write!(out, ".{idx}"), | |
| ArrayElem(idx) => write!(out, "[{idx}]"), | |
| // `.<deref>` does not match Rust syntax, but it is more readable for long paths -- and | |
| // some of the other items here also are not Rust syntax. Actually we can't | |
| // even use the usual syntax because we are just showing the projections, | |
| // not the root. | |
| Deref => write!(out, ".<deref>"), | |
| DynDowncast(ty) => write!(out, ".<dyn-downcast({ty})>"), | |
| Vtable => write!(out, ".<vtable>"), | |
| } | |
| .unwrap() | |
| } | |
| } | |
| /// Represents a set of `Size` values as a sorted list of ranges. | |
| // These are (offset, length) pairs, and they are sorted and mutually disjoint, | |
| // and never adjacent (i.e. there's always a gap between two of them). | |
| #[derive(Debug, Clone)] | |
| pub struct RangeSet(Vec<(Size, Size)>); | |
| impl RangeSet { | |
| fn add_range(&mut self, offset: Size, size: Size) { | |
| if size.bytes() == 0 { | |
| // No need to track empty ranges. | |
| return; | |
| } | |
| let v = &mut self.0; | |
| // We scan for a partition point where the left partition is all the elements that end | |
| // strictly before we start. Those are elements that are too "low" to merge with us. | |
| let idx = | |
| v.partition_point(|&(other_offset, other_size)| other_offset + other_size < offset); | |
| // Now we want to either merge with the first element of the second partition, or insert ourselves before that. | |
| if let Some(&(other_offset, other_size)) = v.get(idx) | |
| && offset + size >= other_offset | |
| { | |
| // Their end is >= our start (otherwise it would not be in the 2nd partition) and | |
| // our end is >= their start. This means we can merge the ranges. | |
| let new_start = other_offset.min(offset); | |
| let mut new_end = (other_offset + other_size).max(offset + size); | |
| // We grew to the right, so merge with overlapping/adjacent elements. | |
| // (We also may have grown to the left, but that can never make us adjacent with | |
| // anything there since we selected the first such candidate via `partition_point`.) | |
| let mut scan_right = 1; | |
| while let Some(&(next_offset, next_size)) = v.get(idx + scan_right) | |
| && new_end >= next_offset | |
| { | |
| // Increase our size to absorb the next element. | |
| new_end = new_end.max(next_offset + next_size); | |
| // Look at the next element. | |
| scan_right += 1; | |
| } | |
| // Update the element we grew. | |
| v[idx] = (new_start, new_end - new_start); | |
| // Remove the elements we absorbed (if any). | |
| if scan_right > 1 { | |
| drop(v.drain((idx + 1)..(idx + scan_right))); | |
| } | |
| } else { | |
| // Insert new element. | |
| v.insert(idx, (offset, size)); | |
| } | |
| } | |
| } | |
| struct ValidityVisitor<'rt, 'tcx, M: Machine<'tcx>> { | |
| /// The `path` may be pushed to, but the part that is present when a function | |
| /// starts must not be changed! `with_elem` relies on this stack discipline. | |
| path: Path<'tcx>, | |
| ref_tracking: Option<&'rt mut RefTracking<MPlaceTy<'tcx, M::Provenance>, Path<'tcx>>>, | |
| /// `None` indicates this is not validating for CTFE (but for runtime). | |
| ctfe_mode: Option<CtfeValidationMode>, | |
| ecx: &'rt mut InterpCx<'tcx, M>, | |
| /// Whether provenance should be reset outside of pointers (emulating the effect of a typed | |
| /// copy). | |
| reset_provenance_and_padding: bool, | |
| /// This tracks which byte ranges in this value contain data; the remaining bytes are padding. | |
| /// The ideal representation here would be pointer-length pairs, but to keep things more compact | |
| /// we only store a (range) set of offsets -- the base pointer is the same throughout the entire | |
| /// visit, after all. | |
| /// If this is `Some`, then `reset_provenance_and_padding` must be true (but not vice versa: | |
| /// we might not track data vs padding bytes if the operand isn't stored in memory anyway). | |
| data_bytes: Option<RangeSet>, | |
| /// True if we are inside of `MaybeDangling`. This disables pointer access checks. | |
| may_dangle: bool, | |
| } | |
| impl<'rt, 'tcx, M: Machine<'tcx>> ValidityVisitor<'rt, 'tcx, M> { | |
| fn aggregate_field_path_elem( | |
| &mut self, | |
| layout: TyAndLayout<'tcx>, | |
| field: usize, | |
| field_ty: Ty<'tcx>, | |
| ) -> PathElem<'tcx> { | |
| // First, check if we are projecting to a variant. | |
| match layout.variants { | |
| Variants::Multiple { tag_field, .. } => { | |
| if tag_field.as_usize() == field { | |
| return match layout.ty.kind() { | |
| ty::Adt(def, ..) if def.is_enum() => PathElem::EnumTag, | |
| ty::Coroutine(..) => PathElem::CoroutineTag, | |
| _ => bug!("non-variant type {:?}", layout.ty), | |
| }; | |
| } | |
| } | |
| Variants::Single { .. } | Variants::Empty => {} | |
| } | |
| // Now we know we are projecting to a field, so figure out which one. | |
| match layout.ty.kind() { | |
| // coroutines, closures, and coroutine-closures all have upvars that may be named. | |
| ty::Closure(def_id, _) | ty::Coroutine(def_id, _) | ty::CoroutineClosure(def_id, _) => { | |
| let mut name = None; | |
| // FIXME this should be more descriptive i.e. CapturePlace instead of CapturedVar | |
| // https://github.com/rust-lang/project-rfc-2229/issues/46 | |
| if let Some(local_def_id) = def_id.as_local() { | |
| let captures = self.ecx.tcx.closure_captures(local_def_id); | |
| if let Some(captured_place) = captures.get(field) { | |
| // Sometimes the index is beyond the number of upvars (seen | |
| // for a coroutine). | |
| let var_hir_id = captured_place.get_root_variable(); | |
| let node = self.ecx.tcx.hir_node(var_hir_id); | |
| if let hir::Node::Pat(pat) = node | |
| && let hir::PatKind::Binding(_, _, ident, _) = pat.kind | |
| { | |
| name = Some(ident.name); | |
| } | |
| } | |
| } | |
| PathElem::CapturedVar(name.unwrap_or_else(|| { | |
| // Fall back to showing the field index. | |
| sym::integer(field) | |
| })) | |
| } | |
| // tuples | |
| ty::Tuple(_) => PathElem::TupleElem(field), | |
| // enums | |
| ty::Adt(def, ..) if def.is_enum() => { | |
| // we might be projecting *to* a variant, or to a field *in* a variant. | |
| match layout.variants { | |
| Variants::Single { index } => { | |
| // Inside a variant | |
| PathElem::Field(def.variant(index).fields[FieldIdx::from_usize(field)].name) | |
| } | |
| Variants::Empty => panic!("there is no field in Variants::Empty types"), | |
| Variants::Multiple { .. } => bug!("we handled variants above"), | |
| } | |
| } | |
| // other ADTs | |
| ty::Adt(def, _) => { | |
| PathElem::Field(def.non_enum_variant().fields[FieldIdx::from_usize(field)].name) | |
| } | |
| // arrays/slices | |
| ty::Array(..) | ty::Slice(..) => PathElem::ArrayElem(field), | |
| // dyn traits | |
| ty::Dynamic(..) => { | |
| assert_eq!(field, 0); | |
| PathElem::DynDowncast(field_ty) | |
| } | |
| // nothing else has an aggregate layout | |
| _ => bug!("aggregate_field_path_elem: got non-aggregate type {:?}", layout.ty), | |
| } | |
| } | |
| fn with_elem<R>( | |
| &mut self, | |
| elem: PathElem<'tcx>, | |
| f: impl FnOnce(&mut Self) -> InterpResult<'tcx, R>, | |
| ) -> InterpResult<'tcx, R> { | |
| // Remember the old state | |
| let path_len = self.path.projs.len(); | |
| // Record new element | |
| self.path.projs.push(elem); | |
| // Perform operation | |
| let r = f(self)?; | |
| // Undo changes | |
| self.path.projs.truncate(path_len); | |
| // Done | |
| interp_ok(r) | |
| } | |
| fn read_immediate( | |
| &self, | |
| val: &PlaceTy<'tcx, M::Provenance>, | |
| expected: ExpectedKind, | |
| ) -> InterpResult<'tcx, ImmTy<'tcx, M::Provenance>> { | |
| interp_ok(try_validation!( | |
| self.ecx.read_immediate(val), | |
| self.path, | |
| Ub(InvalidUninitBytes(_)) => | |
| Uninit { expected }, | |
| // The `Unsup` cases can only occur during CTFE | |
| Unsup(ReadPointerAsInt(_)) => | |
| PointerAsInt { expected }, | |
| Unsup(ReadPartialPointer(_)) => | |
| PartialPointer, | |
| )) | |
| } | |
| fn read_scalar( | |
| &self, | |
| val: &PlaceTy<'tcx, M::Provenance>, | |
| expected: ExpectedKind, | |
| ) -> InterpResult<'tcx, Scalar<M::Provenance>> { | |
| interp_ok(self.read_immediate(val, expected)?.to_scalar()) | |
| } | |
| fn deref_pointer( | |
| &mut self, | |
| val: &PlaceTy<'tcx, M::Provenance>, | |
| expected: ExpectedKind, | |
| ) -> InterpResult<'tcx, MPlaceTy<'tcx, M::Provenance>> { | |
| // Not using `ecx.deref_pointer` since we want to use our `read_immediate` wrapper. | |
| let imm = self.read_immediate(val, expected)?; | |
| // Reset provenance: ensure slice tail metadata does not preserve provenance, | |
| // and ensure all pointers do not preserve partial provenance. | |
| if self.reset_provenance_and_padding { | |
| if matches!(imm.layout.backend_repr, BackendRepr::Scalar(..)) { | |
| // A thin pointer. If it has provenance, we don't have to do anything. | |
| // If it does not, ensure we clear the provenance in memory. | |
| if matches!(imm.to_scalar(), Scalar::Int(..)) { | |
| self.ecx.clear_provenance(val)?; | |
| } | |
| } else { | |
| // A wide pointer. This means we have to worry both about the pointer itself and the | |
| // metadata. We do the lazy thing and just write back the value we got. Just | |
| // clearing provenance in a targeted manner would be more efficient, but unless this | |
| // is a perf hotspot it's just not worth the effort. | |
| self.ecx.write_immediate_no_validate(*imm, val)?; | |
| } | |
| // The entire thing is data, not padding. | |
| self.add_data_range_place(val); | |
| } | |
| // Now turn it into a place. | |
| self.ecx.imm_ptr_to_mplace(&imm) | |
| } | |
| fn check_wide_ptr_meta( | |
| &mut self, | |
| meta: MemPlaceMeta<M::Provenance>, | |
| pointee: TyAndLayout<'tcx>, | |
| ) -> InterpResult<'tcx> { | |
| let tail = self.ecx.tcx.struct_tail_for_codegen(pointee.ty, self.ecx.typing_env); | |
| match tail.kind() { | |
| ty::Dynamic(data, _) => { | |
| let vtable = meta.unwrap_meta().to_pointer(self.ecx)?; | |
| // Make sure it is a genuine vtable pointer for the right trait. | |
| try_validation!( | |
| self.ecx.get_ptr_vtable_ty(vtable, Some(data)), | |
| self.path, | |
| Ub(DanglingIntPointer{ .. } | InvalidVTablePointer(..)) => | |
| format!("encountered {vtable}, but expected a vtable pointer"), | |
| Ub(InvalidVTableTrait { vtable_dyn_type, expected_dyn_type }) => | |
| InvalidMetaWrongTrait { expected_dyn_type, vtable_dyn_type }, | |
| ); | |
| } | |
| ty::Slice(..) | ty::Str => { | |
| let _len = meta.unwrap_meta().to_target_usize(self.ecx)?; | |
| // We do not check that `len * elem_size <= isize::MAX`: | |
| // that is only required for references, and there it falls out of the | |
| // "dereferenceable" check performed by Stacked Borrows. | |
| } | |
| ty::Foreign(..) => { | |
| // Unsized, but not wide. | |
| } | |
| _ => bug!("Unexpected unsized type tail: {:?}", tail), | |
| } | |
| interp_ok(()) | |
| } | |
| /// Check a reference or `Box`. | |
| fn check_safe_pointer( | |
| &mut self, | |
| value: &PlaceTy<'tcx, M::Provenance>, | |
| ptr_kind: PointerKind, | |
| ) -> InterpResult<'tcx> { | |
| let place = self.deref_pointer(value, ptr_kind.into())?; | |
| // Handle wide pointers. | |
| // Check metadata early, for better diagnostics | |
| if place.layout.is_unsized() { | |
| self.check_wide_ptr_meta(place.meta(), place.layout)?; | |
| } | |
| // Determine size and alignment of pointee. | |
| let size_and_align = try_validation!( | |
| self.ecx.size_and_align_of_val(&place), | |
| self.path, | |
| Ub(InvalidMeta(msg)) => format!( | |
| "encountered invalid {ptr_kind} metadata: {}", | |
| match msg { | |
| InvalidMetaKind::SliceTooBig => "slice is bigger than largest supported object", | |
| InvalidMetaKind::TooBig => "total size is bigger than largest supported object", | |
| } | |
| ) | |
| ); | |
| let (size, align) = size_and_align | |
| // for the purpose of validity, consider foreign types to have | |
| // alignment and size determined by the layout (size will be 0, | |
| // alignment should take attributes into account). | |
| .unwrap_or_else(|| (place.layout.size, place.layout.align.abi)); | |
| // If we're not allow to dangle, make sure this is dereferenceable. | |
| if !self.may_dangle { | |
| try_validation!( | |
| self.ecx.check_ptr_access( | |
| place.ptr(), | |
| size, | |
| CheckInAllocMsg::Dereferenceable, // will anyway be replaced by validity message | |
| ), | |
| self.path, | |
| Ub(DanglingIntPointer { addr: 0, .. }) => | |
| format!("encountered a null {ptr_kind}"), | |
| Ub(DanglingIntPointer { addr: i, .. }) => | |
| format!( | |
| "encountered a dangling {ptr_kind} ({ptr} has no provenance)", | |
| ptr = Pointer::<Option<AllocId>>::without_provenance(i) | |
| ), | |
| Ub(PointerOutOfBounds { .. }) => | |
| format!("encountered a dangling {ptr_kind} (going beyond the bounds of its allocation)"), | |
| Ub(PointerUseAfterFree(..)) => | |
| format!("encountered a dangling {ptr_kind} (use-after-free)"), | |
| ); | |
| } | |
| // Check alignment after dereferenceable (if both are violated, trigger the error above). | |
| try_validation!( | |
| self.ecx.check_ptr_align( | |
| place.ptr(), | |
| align, | |
| ), | |
| self.path, | |
| Ub(AlignmentCheckFailed(Misalignment { required, has }, _msg)) => format!( | |
| "encountered an unaligned {ptr_kind} (required {required_bytes} byte alignment but found {found_bytes})", | |
| required_bytes = required.bytes(), | |
| found_bytes = has.bytes() | |
| ), | |
| ); | |
| // Make sure this is non-null. This is obviously needed when `may_dangle` is set, | |
| // but even if we did check dereferenceability above that would still allow null | |
| // pointers if `size` is zero. | |
| let scalar = Scalar::from_maybe_pointer(place.ptr(), self.ecx); | |
| if self.ecx.scalar_may_be_null(scalar)? { | |
| let maybe = !M::Provenance::OFFSET_IS_ADDR && matches!(scalar, Scalar::Ptr(..)); | |
| throw_validation_failure!( | |
| self.path, | |
| format!( | |
| "encountered a {maybe}null {ptr_kind}", | |
| maybe = if maybe { "maybe-" } else { "" } | |
| ) | |
| ) | |
| } | |
| // Do not allow references to uninhabited types. | |
| if place.layout.is_uninhabited() { | |
| let ty = place.layout.ty; | |
| throw_validation_failure!( | |
| self.path, | |
| format!("encountered a {ptr_kind} pointing to uninhabited type {ty}") | |
| ) | |
| } | |
| // Recursive checking (but not inside `MaybeDangling` of course). | |
| if let Some(ref_tracking) = self.ref_tracking.as_deref_mut() | |
| && !self.may_dangle | |
| { | |
| // Proceed recursively even for ZST, no reason to skip them! | |
| // `!` is a ZST and we want to validate it. | |
| if let Some(ctfe_mode) = self.ctfe_mode { | |
| let mut skip_recursive_check = false; | |
| // CTFE imposes restrictions on what references can point to. | |
| if let Ok((alloc_id, _offset, _prov)) = | |
| self.ecx.ptr_try_get_alloc_id(place.ptr(), 0) | |
| { | |
| // Everything should be already interned. | |
| let Some(global_alloc) = self.ecx.tcx.try_get_global_alloc(alloc_id) else { | |
| if self.ecx.memory.alloc_map.contains_key(&alloc_id) { | |
| // This can happen when interning didn't complete due to, e.g. | |
| // missing `make_global`. This must mean other errors are already | |
| // being reported. | |
| self.ecx.tcx.dcx().delayed_bug( | |
| "interning did not complete, there should be an error", | |
| ); | |
| return interp_ok(()); | |
| } | |
| // We can't have *any* references to non-existing allocations in const-eval | |
| // as the rest of rustc isn't happy with them... so we throw an error, even | |
| // though for zero-sized references this isn't really UB. | |
| // A potential future alternative would be to resurrect this as a zero-sized allocation | |
| // (which codegen will then compile to an aligned dummy pointer anyway). | |
| throw_validation_failure!( | |
| self.path, | |
| format!("encountered a dangling {ptr_kind} (use-after-free)") | |
| ); | |
| }; | |
| let (size, _align) = | |
| global_alloc.size_and_align(*self.ecx.tcx, self.ecx.typing_env); | |
| let alloc_actual_mutbl = | |
| global_alloc.mutability(*self.ecx.tcx, self.ecx.typing_env); | |
| match global_alloc { | |
| GlobalAlloc::Static(did) => { | |
| let DefKind::Static { nested, .. } = self.ecx.tcx.def_kind(did) else { | |
| bug!() | |
| }; | |
| assert!(!self.ecx.tcx.is_thread_local_static(did)); | |
| assert!(self.ecx.tcx.is_static(did)); | |
| match ctfe_mode { | |
| CtfeValidationMode::Static { .. } | |
| | CtfeValidationMode::Promoted { .. } => { | |
| // We skip recursively checking other statics. These statics must be sound by | |
| // themselves, and the only way to get broken statics here is by using | |
| // unsafe code. | |
| // The reasons we don't check other statics is twofold. For one, in all | |
| // sound cases, the static was already validated on its own, and second, we | |
| // trigger cycle errors if we try to compute the value of the other static | |
| // and that static refers back to us (potentially through a promoted). | |
| // This could miss some UB, but that's fine. | |
| // We still walk nested allocations, as they are fundamentally part of this validation run. | |
| // This means we will also recurse into nested statics of *other* | |
| // statics, even though we do not recurse into other statics directly. | |
| // That's somewhat inconsistent but harmless. | |
| skip_recursive_check = !nested; | |
| } | |
| CtfeValidationMode::Const { .. } => { | |
| // If this is mutable memory or an `extern static`, there's no point in checking it -- we'd | |
| // just get errors trying to read the value. | |
| if alloc_actual_mutbl.is_mut() | |
| || self.ecx.tcx.is_foreign_item(did) | |
| { | |
| skip_recursive_check = true; | |
| } | |
| } | |
| } | |
| } | |
| _ => (), | |
| } | |
| // If this allocation has size zero, there is no actual mutability here. | |
| if size != Size::ZERO { | |
| // Determine whether this pointer expects to be pointing to something mutable. | |
| let ptr_expected_mutbl = match ptr_kind { | |
| PointerKind::Box => Mutability::Mut, | |
| PointerKind::Ref(mutbl) => { | |
| // We do not take into account interior mutability here since we cannot know if | |
| // there really is an `UnsafeCell` inside `Option<UnsafeCell>` -- so we check | |
| // that in the recursive descent behind this reference (controlled by | |
| // `allow_immutable_unsafe_cell`). | |
| mutbl | |
| } | |
| }; | |
| // Mutable pointer to immutable memory is no good. | |
| if ptr_expected_mutbl == Mutability::Mut | |
| && alloc_actual_mutbl == Mutability::Not | |
| { | |
| // This can actually occur with transmutes. | |
| throw_validation_failure!( | |
| self.path, | |
| format!( | |
| "encountered mutable reference or box pointing to read-only memory" | |
| ) | |
| ); | |
| } | |
| } | |
| } | |
| // Potentially skip recursive check. | |
| if skip_recursive_check { | |
| return interp_ok(()); | |
| } | |
| } else { | |
| // This is not CTFE, so it's Miri with recursive checking. | |
| // FIXME: should we skip `UnsafeCell` behind shared references? Currently that is | |
| // not needed since validation reads bypass Stacked Borrows and data race checks, | |
| // but is that really coherent? | |
| } | |
| let path = &self.path; | |
| ref_tracking.track(place, || { | |
| // We need to clone the path anyway, make sure it gets created | |
| // with enough space for the additional `Deref`. | |
| let mut new_projs = Vec::with_capacity(path.projs.len() + 1); | |
| new_projs.extend(&path.projs); | |
| new_projs.push(PathElem::Deref); | |
| Path { projs: new_projs, orig_ty: path.orig_ty } | |
| }); | |
| } | |
| interp_ok(()) | |
| } | |
| /// Check if this is a value of primitive type, and if yes check the validity of the value | |
| /// at that type. Return `true` if the type is indeed primitive. | |
| /// | |
| /// Note that not all of these have `FieldsShape::Primitive`, e.g. wide references. | |
| fn try_visit_primitive( | |
| &mut self, | |
| value: &PlaceTy<'tcx, M::Provenance>, | |
| ) -> InterpResult<'tcx, bool> { | |
| // Go over all the primitive types | |
| let ty = value.layout.ty; | |
| match ty.kind() { | |
| ty::Bool => { | |
| let scalar = self.read_scalar(value, ExpectedKind::Bool)?; | |
| try_validation!( | |
| scalar.to_bool(), | |
| self.path, | |
| Ub(InvalidBool(..)) => | |
| format!("encountered {scalar:x}, but expected a boolean"), | |
| ); | |
| if self.reset_provenance_and_padding { | |
| self.ecx.clear_provenance(value)?; | |
| self.add_data_range_place(value); | |
| } | |
| interp_ok(true) | |
| } | |
| ty::Char => { | |
| let scalar = self.read_scalar(value, ExpectedKind::Char)?; | |
| try_validation!( | |
| scalar.to_char(), | |
| self.path, | |
| Ub(InvalidChar(..)) => | |
| format!("encountered {scalar:x}, but expected a valid unicode scalar value \ | |
| (in `0..=0x10FFFF` but not in `0xD800..=0xDFFF`)") | |
| ); | |
| if self.reset_provenance_and_padding { | |
| self.ecx.clear_provenance(value)?; | |
| self.add_data_range_place(value); | |
| } | |
| interp_ok(true) | |
| } | |
| ty::Float(_) | ty::Int(_) | ty::Uint(_) => { | |
| // NOTE: Keep this in sync with the array optimization for int/float | |
| // types below! | |
| self.read_scalar( | |
| value, | |
| if matches!(ty.kind(), ty::Float(..)) { | |
| ExpectedKind::Float | |
| } else { | |
| ExpectedKind::Int | |
| }, | |
| )?; | |
| if self.reset_provenance_and_padding { | |
| self.ecx.clear_provenance(value)?; | |
| self.add_data_range_place(value); | |
| } | |
| interp_ok(true) | |
| } | |
| ty::RawPtr(..) => { | |
| let place = self.deref_pointer(value, ExpectedKind::RawPtr)?; | |
| if place.layout.is_unsized() { | |
| self.check_wide_ptr_meta(place.meta(), place.layout)?; | |
| } | |
| interp_ok(true) | |
| } | |
| ty::Ref(_, _ty, mutbl) => { | |
| self.check_safe_pointer(value, PointerKind::Ref(*mutbl))?; | |
| interp_ok(true) | |
| } | |
| ty::FnPtr(..) => { | |
| let scalar = self.read_scalar(value, ExpectedKind::FnPtr)?; | |
| // If we check references recursively, also check that this points to a function. | |
| if let Some(_) = self.ref_tracking { | |
| let ptr = scalar.to_pointer(self.ecx)?; | |
| let _fn = try_validation!( | |
| self.ecx.get_ptr_fn(ptr), | |
| self.path, | |
| Ub(DanglingIntPointer{ .. } | InvalidFunctionPointer(..)) => | |
| format!("encountered {ptr}, but expected a function pointer"), | |
| ); | |
| // FIXME: Check if the signature matches | |
| } else { | |
| // Otherwise (for standalone Miri and for `-Zextra-const-ub-checks`), | |
| // we have to still check it to be non-null. | |
| if self.ecx.scalar_may_be_null(scalar)? { | |
| let maybe = | |
| !M::Provenance::OFFSET_IS_ADDR && matches!(scalar, Scalar::Ptr(..)); | |
| throw_validation_failure!( | |
| self.path, | |
| format!( | |
| "encountered a {maybe}null function pointer", | |
| maybe = if maybe { "maybe-" } else { "" } | |
| ) | |
| ); | |
| } | |
| } | |
| if self.reset_provenance_and_padding { | |
| // Make sure we do not preserve partial provenance. This matches the thin | |
| // pointer handling in `deref_pointer`. | |
| if matches!(scalar, Scalar::Int(..)) { | |
| self.ecx.clear_provenance(value)?; | |
| } | |
| self.add_data_range_place(value); | |
| } | |
| interp_ok(true) | |
| } | |
| ty::Never => { | |
| throw_validation_failure!( | |
| self.path, | |
| format!("encountered a value of the never type `!`") | |
| ) | |
| } | |
| ty::Foreign(..) | ty::FnDef(..) => { | |
| // Nothing to check. | |
| interp_ok(true) | |
| } | |
| ty::UnsafeBinder(_) => todo!("FIXME(unsafe_binder)"), | |
| // The above should be all the primitive types. The rest is compound, we | |
| // check them by visiting their fields/variants. | |
| ty::Adt(..) | |
| | ty::Tuple(..) | |
| | ty::Array(..) | |
| | ty::Slice(..) | |
| | ty::Str | |
| | ty::Dynamic(..) | |
| | ty::Closure(..) | |
| | ty::Pat(..) | |
| | ty::CoroutineClosure(..) | |
| | ty::Coroutine(..) => interp_ok(false), | |
| // Some types only occur during typechecking, they have no layout. | |
| // We should not see them here and we could not check them anyway. | |
| ty::Error(_) | |
| | ty::Infer(..) | |
| | ty::Placeholder(..) | |
| | ty::Bound(..) | |
| | ty::Param(..) | |
| | ty::Alias(..) | |
| | ty::CoroutineWitness(..) => bug!("Encountered invalid type {:?}", ty), | |
| } | |
| } | |
| fn visit_scalar( | |
| &mut self, | |
| scalar: Scalar<M::Provenance>, | |
| scalar_layout: ScalarAbi, | |
| ) -> InterpResult<'tcx> { | |
| let size = scalar_layout.size(self.ecx); | |
| let valid_range = scalar_layout.valid_range(self.ecx); | |
| let WrappingRange { start, end } = valid_range; | |
| let max_value = size.unsigned_int_max(); | |
| assert!(end <= max_value); | |
| let bits = match scalar.try_to_scalar_int() { | |
| Ok(int) => int.to_bits(size), | |
| Err(_) => { | |
| // So this is a pointer then, and casting to an int failed. | |
| // Can only happen during CTFE. | |
| // We support 2 kinds of ranges here: full range, and excluding zero. | |
| if start == 1 && end == max_value { | |
| // Only null is the niche. So make sure the ptr is NOT null. | |
| if self.ecx.scalar_may_be_null(scalar)? { | |
| throw_validation_failure!( | |
| self.path, | |
| format!( | |
| "encountered a maybe-null pointer, but expected something that is definitely non-zero" | |
| ) | |
| ) | |
| } else { | |
| return interp_ok(()); | |
| } | |
| } else if scalar_layout.is_always_valid(self.ecx) { | |
| // Easy. (This is reachable if `enforce_number_validity` is set.) | |
| return interp_ok(()); | |
| } else { | |
| // Conservatively, we reject, because the pointer *could* have a bad value. | |
| throw_validation_failure!( | |
| self.path, | |
| format!( | |
| "encountered a pointer with unknown absolute address, but expected something that is definitely {in_range}", | |
| in_range = fmt_range(valid_range, max_value) | |
| ) | |
| ) | |
| } | |
| } | |
| }; | |
| // Now compare. | |
| if valid_range.contains(bits) { | |
| interp_ok(()) | |
| } else { | |
| throw_validation_failure!( | |
| self.path, | |
| format!( | |
| "encountered {bits}, but expected something {in_range}", | |
| in_range = fmt_range(valid_range, max_value) | |
| ) | |
| ) | |
| } | |
| } | |
| fn in_mutable_memory(&self, val: &PlaceTy<'tcx, M::Provenance>) -> bool { | |
| debug_assert!(self.ctfe_mode.is_some()); | |
| if let Some(mplace) = val.as_mplace_or_local().left() { | |
| if let Some(alloc_id) = mplace.ptr().provenance.and_then(|p| p.get_alloc_id()) { | |
| let tcx = *self.ecx.tcx; | |
| // Everything must be already interned. | |
| let mutbl = tcx.global_alloc(alloc_id).mutability(tcx, self.ecx.typing_env); | |
| if let Some((_, alloc)) = self.ecx.memory.alloc_map.get(alloc_id) { | |
| assert_eq!(alloc.mutability, mutbl); | |
| } | |
| mutbl.is_mut() | |
| } else { | |
| // No memory at all. | |
| false | |
| } | |
| } else { | |
| // A local variable -- definitely mutable. | |
| true | |
| } | |
| } | |
| /// Add the given pointer-length pair to the "data" range of this visit. | |
| fn add_data_range(&mut self, ptr: Pointer<Option<M::Provenance>>, size: Size) { | |
| if let Some(data_bytes) = self.data_bytes.as_mut() { | |
| // We only have to store the offset, the rest is the same for all pointers here. | |
| // The logic is agnostic to whether the offset is relative or absolute as long as | |
| // it is consistent. | |
| let (_prov, offset) = ptr.into_raw_parts(); | |
| // Add this. | |
| data_bytes.add_range(offset, size); | |
| }; | |
| } | |
| /// Add the entire given place to the "data" range of this visit. | |
| fn add_data_range_place(&mut self, place: &PlaceTy<'tcx, M::Provenance>) { | |
| // Only sized places can be added this way. | |
| debug_assert!(place.layout.is_sized()); | |
| if let Some(data_bytes) = self.data_bytes.as_mut() { | |
| let offset = Self::data_range_offset(self.ecx, place); | |
| data_bytes.add_range(offset, place.layout.size); | |
| } | |
| } | |
| /// Convert a place into the offset it starts at, for the purpose of data_range tracking. | |
| /// Must only be called if `data_bytes` is `Some(_)`. | |
| fn data_range_offset(ecx: &InterpCx<'tcx, M>, place: &PlaceTy<'tcx, M::Provenance>) -> Size { | |
| // The presence of `data_bytes` implies that our place is in memory. | |
| let ptr = ecx | |
| .place_to_op(place) | |
| .expect("place must be in memory") | |
| .as_mplace_or_imm() | |
| .expect_left("place must be in memory") | |
| .ptr(); | |
| let (_prov, offset) = ptr.into_raw_parts(); | |
| offset | |
| } | |
| fn reset_padding(&mut self, place: &PlaceTy<'tcx, M::Provenance>) -> InterpResult<'tcx> { | |
| let Some(data_bytes) = self.data_bytes.as_mut() else { return interp_ok(()) }; | |
| // Our value must be in memory, otherwise we would not have set up `data_bytes`. | |
| let mplace = self.ecx.force_allocation(place)?; | |
| // Determine starting offset and size. | |
| let (_prov, start_offset) = mplace.ptr().into_raw_parts(); | |
| let (size, _align) = self | |
| .ecx | |
| .size_and_align_of_val(&mplace)? | |
| .unwrap_or((mplace.layout.size, mplace.layout.align.abi)); | |
| // If there is no padding at all, we can skip the rest: check for | |
| // a single data range covering the entire value. | |
| if data_bytes.0 == &[(start_offset, size)] { | |
| return interp_ok(()); | |
| } | |
| // Get a handle for the allocation. Do this only once, to avoid looking up the same | |
| // allocation over and over again. (Though to be fair, iterating the value already does | |
| // exactly that.) | |
| let Some(mut alloc) = self.ecx.get_ptr_alloc_mut(mplace.ptr(), size)? else { | |
| // A ZST, no padding to clear. | |
| return interp_ok(()); | |
| }; | |
| // Add a "finalizer" data range at the end, so that the iteration below finds all gaps | |
| // between ranges. | |
| data_bytes.0.push((start_offset + size, Size::ZERO)); | |
| // Iterate, and reset gaps. | |
| let mut padding_cleared_until = start_offset; | |
| for &(offset, size) in data_bytes.0.iter() { | |
| assert!( | |
| offset >= padding_cleared_until, | |
| "reset_padding on {}: previous field ended at offset {}, next field starts at {} (and has a size of {} bytes)", | |
| mplace.layout.ty, | |
| (padding_cleared_until - start_offset).bytes(), | |
| (offset - start_offset).bytes(), | |
| size.bytes(), | |
| ); | |
| if offset > padding_cleared_until { | |
| // We found padding. Adjust the range to be relative to `alloc`, and make it uninit. | |
| let padding_start = padding_cleared_until - start_offset; | |
| let padding_size = offset - padding_cleared_until; | |
| let range = alloc_range(padding_start, padding_size); | |
| trace!("reset_padding on {}: resetting padding range {range:?}", mplace.layout.ty); | |
| alloc.write_uninit(range); | |
| } | |
| padding_cleared_until = offset + size; | |
| } | |
| assert!(padding_cleared_until == start_offset + size); | |
| interp_ok(()) | |
| } | |
| /// Computes the data range of this union type: | |
| /// which bytes are inside a field (i.e., not padding.) | |
| fn union_data_range<'e>( | |
| ecx: &'e mut InterpCx<'tcx, M>, | |
| layout: TyAndLayout<'tcx>, | |
| ) -> Cow<'e, RangeSet> { | |
| assert!(layout.ty.is_union()); | |
| assert!(layout.is_sized(), "there are no unsized unions"); | |
| let layout_cx = LayoutCx::new(*ecx.tcx, ecx.typing_env); | |
| return M::cached_union_data_range(ecx, layout.ty, || { | |
| let mut out = RangeSet(Vec::new()); | |
| union_data_range_uncached(&layout_cx, layout, Size::ZERO, &mut out); | |
| out | |
| }); | |
| /// Helper for recursive traversal: add data ranges of the given type to `out`. | |
| fn union_data_range_uncached<'tcx>( | |
| cx: &LayoutCx<'tcx>, | |
| layout: TyAndLayout<'tcx>, | |
| base_offset: Size, | |
| out: &mut RangeSet, | |
| ) { | |
| // If this is a ZST, we don't contain any data. In particular, this helps us to quickly | |
| // skip over huge arrays of ZST. | |
| if layout.is_zst() { | |
| return; | |
| } | |
| // Just recursively add all the fields of everything to the output. | |
| match &layout.fields { | |
| FieldsShape::Primitive => { | |
| out.add_range(base_offset, layout.size); | |
| } | |
| &FieldsShape::Union(fields) => { | |
| // Currently, all fields start at offset 0 (relative to `base_offset`). | |
| for field in 0..fields.get() { | |
| let field = layout.field(cx, field); | |
| union_data_range_uncached(cx, field, base_offset, out); | |
| } | |
| } | |
| &FieldsShape::Array { stride, count } => { | |
| let elem = layout.field(cx, 0); | |
| // Fast-path for large arrays of simple types that do not contain any padding. | |
| if elem.backend_repr.is_scalar() { | |
| out.add_range(base_offset, elem.size * count); | |
| } else { | |
| for idx in 0..count { | |
| // This repeats the same computation for every array element... but the alternative | |
| // is to allocate temporary storage for a dedicated `out` set for the array element, | |
| // and replicating that N times. Is that better? | |
| union_data_range_uncached(cx, elem, base_offset + idx * stride, out); | |
| } | |
| } | |
| } | |
| FieldsShape::Arbitrary { offsets, .. } => { | |
| for (field, &offset) in offsets.iter_enumerated() { | |
| let field = layout.field(cx, field.as_usize()); | |
| union_data_range_uncached(cx, field, base_offset + offset, out); | |
| } | |
| } | |
| } | |
| // Don't forget potential other variants. | |
| match &layout.variants { | |
| Variants::Single { .. } | Variants::Empty => { | |
| // Fully handled above. | |
| } | |
| Variants::Multiple { variants, .. } => { | |
| for variant in variants.indices() { | |
| let variant = layout.for_variant(cx, variant); | |
| union_data_range_uncached(cx, variant, base_offset, out); | |
| } | |
| } | |
| } | |
| } | |
| } | |
| } | |
| impl<'rt, 'tcx, M: Machine<'tcx>> ValueVisitor<'tcx, M> for ValidityVisitor<'rt, 'tcx, M> { | |
| type V = PlaceTy<'tcx, M::Provenance>; | |
| #[inline(always)] | |
| fn ecx(&self) -> &InterpCx<'tcx, M> { | |
| self.ecx | |
| } | |
| fn read_discriminant( | |
| &mut self, | |
| val: &PlaceTy<'tcx, M::Provenance>, | |
| ) -> InterpResult<'tcx, VariantIdx> { | |
| self.with_elem(PathElem::EnumTag, move |this| { | |
| interp_ok(try_validation!( | |
| this.ecx.read_discriminant(val), | |
| this.path, | |
| Ub(InvalidTag(val)) => | |
| format!("encountered {val:x}, but expected a valid enum tag"), | |
| Ub(UninhabitedEnumVariantRead(_)) => | |
| format!("encountered an uninhabited enum variant"), | |
| // Uninit / bad provenance are not possible since the field was already previously | |
| // checked at its integer type. | |
| )) | |
| }) | |
| } | |
| #[inline] | |
| fn visit_field( | |
| &mut self, | |
| old_val: &PlaceTy<'tcx, M::Provenance>, | |
| field: usize, | |
| new_val: &PlaceTy<'tcx, M::Provenance>, | |
| ) -> InterpResult<'tcx> { | |
| let elem = self.aggregate_field_path_elem(old_val.layout, field, new_val.layout.ty); | |
| self.with_elem(elem, move |this| this.visit_value(new_val)) | |
| } | |
| #[inline] | |
| fn visit_variant( | |
| &mut self, | |
| old_val: &PlaceTy<'tcx, M::Provenance>, | |
| variant_id: VariantIdx, | |
| new_val: &PlaceTy<'tcx, M::Provenance>, | |
| ) -> InterpResult<'tcx> { | |
| let name = match old_val.layout.ty.kind() { | |
| ty::Adt(adt, _) => PathElem::Variant(adt.variant(variant_id).name), | |
| // Coroutines also have variants | |
| ty::Coroutine(..) => PathElem::CoroutineState(variant_id), | |
| _ => bug!("Unexpected type with variant: {:?}", old_val.layout.ty), | |
| }; | |
| self.with_elem(name, move |this| this.visit_value(new_val)) | |
| } | |
| #[inline(always)] | |
| fn visit_union( | |
| &mut self, | |
| val: &PlaceTy<'tcx, M::Provenance>, | |
| _fields: NonZero<usize>, | |
| ) -> InterpResult<'tcx> { | |
| // Special check for CTFE validation, preventing `UnsafeCell` inside unions in immutable memory. | |
| if self.ctfe_mode.is_some_and(|c| !c.allow_immutable_unsafe_cell()) { | |
| // Unsized unions are currently not a thing, but let's keep this code consistent with | |
| // the check in `visit_value`. | |
| let zst = self.ecx.size_and_align_of_val(val)?.is_some_and(|(s, _a)| s.bytes() == 0); | |
| if !zst && !val.layout.ty.is_freeze(*self.ecx.tcx, self.ecx.typing_env) { | |
| if !self.in_mutable_memory(val) { | |
| throw_validation_failure!( | |
| self.path, | |
| format!("encountered `UnsafeCell` in read-only memory") | |
| ); | |
| } | |
| } | |
| } | |
| if self.reset_provenance_and_padding | |
| && let Some(data_bytes) = self.data_bytes.as_mut() | |
| { | |
| let base_offset = Self::data_range_offset(self.ecx, val); | |
| // Determine and add data range for this union. | |
| let union_data_range = Self::union_data_range(self.ecx, val.layout); | |
| for &(offset, size) in union_data_range.0.iter() { | |
| data_bytes.add_range(base_offset + offset, size); | |
| } | |
| } | |
| interp_ok(()) | |
| } | |
| #[inline] | |
| fn visit_box( | |
| &mut self, | |
| _box_ty: Ty<'tcx>, | |
| val: &PlaceTy<'tcx, M::Provenance>, | |
| ) -> InterpResult<'tcx> { | |
| self.check_safe_pointer(val, PointerKind::Box)?; | |
| interp_ok(()) | |
| } | |
| #[inline] | |
| fn visit_value(&mut self, val: &PlaceTy<'tcx, M::Provenance>) -> InterpResult<'tcx> { | |
| trace!("visit_value: {:?}, {:?}", *val, val.layout); | |
| // Check primitive types -- the leaves of our recursive descent. | |
| // This is called even for enum discriminants (which are "fields" of their enum), | |
| // so for integer-typed discriminants the provenance reset will happen here. | |
| // We assume that the Scalar validity range does not restrict these values | |
| // any further than `try_visit_primitive` does! | |
| if self.try_visit_primitive(val)? { | |
| return interp_ok(()); | |
| } | |
| // Special check preventing `UnsafeCell` in the inner part of constants | |
| if self.ctfe_mode.is_some_and(|c| !c.allow_immutable_unsafe_cell()) { | |
| // Exclude ZST values. We need to compute the dynamic size/align to properly | |
| // handle slices and trait objects. | |
| let zst = self.ecx.size_and_align_of_val(val)?.is_some_and(|(s, _a)| s.bytes() == 0); | |
| if !zst | |
| && let Some(def) = val.layout.ty.ty_adt_def() | |
| && def.is_unsafe_cell() | |
| { | |
| if !self.in_mutable_memory(val) { | |
| throw_validation_failure!( | |
| self.path, | |
| format!("encountered `UnsafeCell` in read-only memory") | |
| ); | |
| } | |
| } | |
| } | |
| // Recursively walk the value at its type. Apply optimizations for some large types. | |
| match val.layout.ty.kind() { | |
| ty::Str => { | |
| let mplace = val.assert_mem_place(); // strings are unsized and hence never immediate | |
| let len = mplace.len(self.ecx)?; | |
| let expected = ExpectedKind::Str; | |
| try_validation!( | |
| self.ecx.read_bytes_ptr_strip_provenance(mplace.ptr(), Size::from_bytes(len)), | |
| self.path, | |
| Ub(InvalidUninitBytes(..)) => | |
| Uninit { expected }, | |
| Unsup(ReadPointerAsInt(_)) => | |
| PointerAsInt { expected }, | |
| ); | |
| } | |
| ty::Array(tys, ..) | ty::Slice(tys) | |
| // This optimization applies for types that can hold arbitrary non-provenance bytes (such as | |
| // integer and floating point types). | |
| // FIXME(wesleywiser) This logic could be extended further to arbitrary structs or | |
| // tuples made up of integer/floating point types or inhabited ZSTs with no padding. | |
| if matches!(tys.kind(), ty::Int(..) | ty::Uint(..) | ty::Float(..)) | |
| => | |
| { | |
| let expected = if tys.is_integral() { ExpectedKind::Int } else { ExpectedKind::Float }; | |
| // Optimized handling for arrays of integer/float type. | |
| // This is the length of the array/slice. | |
| let len = val.len(self.ecx)?; | |
| // This is the element type size. | |
| let layout = self.ecx.layout_of(*tys)?; | |
| // This is the size in bytes of the whole array. (This checks for overflow.) | |
| let size = layout.size * len; | |
| // If the size is 0, there is nothing to check. | |
| // (`size` can only be 0 if `len` is 0, and empty arrays are always valid.) | |
| if size == Size::ZERO { | |
| return interp_ok(()); | |
| } | |
| // Now that we definitely have a non-ZST array, we know it lives in memory -- except it may | |
| // be an uninitialized local variable, those are also "immediate". | |
| let mplace = match val.to_op(self.ecx)?.as_mplace_or_imm() { | |
| Left(mplace) => mplace, | |
| Right(imm) => match *imm { | |
| Immediate::Uninit => | |
| throw_validation_failure!( | |
| self.path, | |
| Uninit { expected } | |
| ), | |
| Immediate::Scalar(..) | Immediate::ScalarPair(..) => | |
| bug!("arrays/slices can never have Scalar/ScalarPair layout"), | |
| } | |
| }; | |
| // Optimization: we just check the entire range at once. | |
| // NOTE: Keep this in sync with the handling of integer and float | |
| // types above, in `visit_primitive`. | |
| // No need for an alignment check here, this is not an actual memory access. | |
| let alloc = self.ecx.get_ptr_alloc(mplace.ptr(), size)?.expect("we already excluded size 0"); | |
| alloc.get_bytes_strip_provenance().map_err_kind(|kind| { | |
| // Some error happened, try to provide a more detailed description. | |
| // For some errors we might be able to provide extra information. | |
| // (This custom logic does not fit the `try_validation!` macro.) | |
| match kind { | |
| Ub(InvalidUninitBytes(Some((_alloc_id, access)))) | Unsup(ReadPointerAsInt(Some((_alloc_id, access)))) => { | |
| // Some byte was uninitialized, determine which | |
| // element that byte belongs to so we can | |
| // provide an index. | |
| let i = usize::try_from( | |
| access.bad.start.bytes() / layout.size.bytes(), | |
| ) | |
| .unwrap(); | |
| self.path.projs.push(PathElem::ArrayElem(i)); | |
| if matches!(kind, Ub(InvalidUninitBytes(_))) { | |
| err_validation_failure!(self.path, Uninit { expected }) | |
| } else { | |
| err_validation_failure!(self.path, PointerAsInt {expected}) | |
| } | |
| } | |
| // Propagate upwards (that will also check for unexpected errors). | |
| err => err, | |
| } | |
| })?; | |
| // Don't forget that these are all non-pointer types, and thus do not preserve | |
| // provenance. | |
| if self.reset_provenance_and_padding { | |
| // We can't share this with above as above, we might be looking at read-only memory. | |
| let mut alloc = self.ecx.get_ptr_alloc_mut(mplace.ptr(), size)?.expect("we already excluded size 0"); | |
| alloc.clear_provenance(); | |
| // Also, mark this as containing data, not padding. | |
| self.add_data_range(mplace.ptr(), size); | |
| } | |
| } | |
| // Fast path for arrays and slices of ZSTs. We only need to check a single ZST element | |
| // of an array and not all of them, because there's only a single value of a specific | |
| // ZST type, so either validation fails for all elements or none. | |
| ty::Array(tys, ..) | ty::Slice(tys) if self.ecx.layout_of(*tys)?.is_zst() => { | |
| // Validate just the first element (if any). | |
| if val.len(self.ecx)? > 0 { | |
| self.visit_field(val, 0, &self.ecx.project_index(val, 0)?)?; | |
| } | |
| } | |
| ty::Pat(base, pat) => { | |
| // First check that the base type is valid | |
| self.visit_value(&val.transmute(self.ecx.layout_of(*base)?, self.ecx)?)?; | |
| // When you extend this match, make sure to also add tests to | |
| // tests/ui/type/pattern_types/validity.rs(( | |
| match **pat { | |
| // Range and non-null patterns are precisely reflected into `valid_range` and thus | |
| // handled fully by `visit_scalar` (called below). | |
| ty::PatternKind::Range { .. } => {}, | |
| ty::PatternKind::NotNull => {}, | |
| // FIXME(pattern_types): check that the value is covered by one of the variants. | |
| // For now, we rely on layout computation setting the scalar's `valid_range` to | |
| // match the pattern. However, this cannot always work; the layout may | |
| // pessimistically cover actually illegal ranges and Miri would miss that UB. | |
| // The consolation here is that codegen also will miss that UB, so at least | |
| // we won't see optimizations actually breaking such programs. | |
| ty::PatternKind::Or(_patterns) => {} | |
| } | |
| } | |
| ty::Adt(adt, _) if adt.is_maybe_dangling() => { | |
| let old_may_dangle = mem::replace(&mut self.may_dangle, true); | |
| let inner = self.ecx.project_field(val, FieldIdx::ZERO)?; | |
| self.visit_value(&inner)?; | |
| self.may_dangle = old_may_dangle; | |
| } | |
| _ => { | |
| // default handler | |
| try_validation!( | |
| self.walk_value(val), | |
| self.path, | |
| // It's not great to catch errors here, since we can't give a very good path, | |
| // but it's better than ICEing. | |
| Ub(InvalidVTableTrait { vtable_dyn_type, expected_dyn_type }) => | |
| InvalidMetaWrongTrait { expected_dyn_type, vtable_dyn_type }, | |
| ); | |
| } | |
| } | |
| // *After* all of this, check further information stored in the layout. We need to check | |
| // this to handle types like `NonNull` where the `Scalar` info is more restrictive than what | |
| // the fields say (`rustc_layout_scalar_valid_range_start`). But in most cases, this will | |
| // just propagate what the fields say, and then we want the error to point at the field -- | |
| // so, we first recurse, then we do this check. | |
| // | |
| // 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). | |
| if val.layout.is_uninhabited() { | |
| let ty = val.layout.ty; | |
| throw_validation_failure!( | |
| self.path, | |
| format!("encountered a value of uninhabited type `{ty}`") | |
| ); | |
| } | |
| match val.layout.backend_repr { | |
| BackendRepr::Scalar(scalar_layout) => { | |
| if !scalar_layout.is_uninit_valid() { | |
| // There is something to check here. | |
| // We read directly via `ecx` since the read cannot fail -- we already read | |
| // this field above when recursing into the field. | |
| let scalar = self.ecx.read_scalar(val)?; | |
| self.visit_scalar(scalar, scalar_layout)?; | |
| } | |
| } | |
| BackendRepr::ScalarPair(a_layout, b_layout) => { | |
| // We can only proceed if *both* scalars need to be initialized. | |
| // FIXME: find a way to also check ScalarPair when one side can be uninit but | |
| // the other must be init. | |
| if !a_layout.is_uninit_valid() && !b_layout.is_uninit_valid() { | |
| // We read directly via `ecx` since the read cannot fail -- we already read | |
| // this field above when recursing into the field. | |
| let (a, b) = self.ecx.read_immediate(val)?.to_scalar_pair(); | |
| self.visit_scalar(a, a_layout)?; | |
| self.visit_scalar(b, b_layout)?; | |
| } | |
| } | |
| BackendRepr::SimdVector { .. } | BackendRepr::SimdScalableVector { .. } => { | |
| // No checks here, we assume layout computation gets this right. | |
| // (This is harder to check since Miri does not represent these as `Immediate`. We | |
| // also cannot use field projections since this might be a newtype around a vector.) | |
| } | |
| BackendRepr::Memory { .. } => { | |
| // Nothing to do. | |
| } | |
| } | |
| interp_ok(()) | |
| } | |
| } | |
| impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> { | |
| /// The internal core entry point for all validation operations. | |
| fn validate_operand_internal( | |
| &mut self, | |
| val: &PlaceTy<'tcx, M::Provenance>, | |
| path: Path<'tcx>, | |
| ref_tracking: Option<&mut RefTracking<MPlaceTy<'tcx, M::Provenance>, Path<'tcx>>>, | |
| ctfe_mode: Option<CtfeValidationMode>, | |
| reset_provenance_and_padding: bool, | |
| start_in_may_dangle: bool, | |
| ) -> InterpResult<'tcx> { | |
| trace!("validate_operand_internal: {:?}, {:?}", *val, val.layout.ty); | |
| // Run the visitor. | |
| self.run_for_validation_mut(|ecx| { | |
| let reset_padding = reset_provenance_and_padding && { | |
| // Check if `val` is actually stored in memory. If not, padding is not even | |
| // represented and we need not reset it. | |
| ecx.place_to_op(val)?.as_mplace_or_imm().is_left() | |
| }; | |
| let mut v = ValidityVisitor { | |
| path, | |
| ref_tracking, | |
| ctfe_mode, | |
| ecx, | |
| reset_provenance_and_padding, | |
| data_bytes: reset_padding.then_some(RangeSet(Vec::new())), | |
| may_dangle: start_in_may_dangle, | |
| }; | |
| v.visit_value(val)?; | |
| v.reset_padding(val)?; | |
| interp_ok(()) | |
| }) | |
| .map_err_info(|err| { | |
| if !matches!( | |
| err.kind(), | |
| err_ub!(ValidationError { .. }) | |
| | InterpErrorKind::InvalidProgram(_) | |
| | InterpErrorKind::Unsupported(UnsupportedOpInfo::ExternTypeField) | |
| ) { | |
| bug!("Unexpected error during validation: {}", format_interp_error(err)); | |
| } | |
| err | |
| }) | |
| } | |
| /// This function checks the data at `val` to be const-valid. | |
| /// `val` is assumed to cover valid memory if it is an indirect operand. | |
| /// It will error if the bits at the destination do not match the ones described by the layout. | |
| /// | |
| /// `ref_tracking` is used to record references that we encounter so that they | |
| /// can be checked recursively by an outside driving loop. | |
| /// | |
| /// `constant` controls whether this must satisfy the rules for constants: | |
| /// - no pointers to statics. | |
| /// - no `UnsafeCell` or non-ZST `&mut`. | |
| #[inline(always)] | |
| pub(crate) fn const_validate_operand( | |
| &mut self, | |
| val: &PlaceTy<'tcx, M::Provenance>, | |
| path: Path<'tcx>, | |
| ref_tracking: &mut RefTracking<MPlaceTy<'tcx, M::Provenance>, Path<'tcx>>, | |
| ctfe_mode: CtfeValidationMode, | |
| ) -> InterpResult<'tcx> { | |
| self.validate_operand_internal( | |
| val, | |
| path, | |
| Some(ref_tracking), | |
| Some(ctfe_mode), | |
| /*reset_provenance*/ false, | |
| /*start_in_may_dangle*/ false, | |
| ) | |
| } | |
| /// This function checks the data at `val` to be runtime-valid. | |
| /// `val` 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. | |
| #[inline(always)] | |
| pub fn validate_operand( | |
| &mut self, | |
| val: &PlaceTy<'tcx, M::Provenance>, | |
| recursive: bool, | |
| reset_provenance_and_padding: bool, | |
| ) -> InterpResult<'tcx> { | |
| let _trace = enter_trace_span!( | |
| M, | |
| "validate_operand", | |
| recursive, | |
| reset_provenance_and_padding, | |
| ?val, | |
| ); | |
| // Note that we *could* actually be in CTFE here with `-Zextra-const-ub-checks`, but it's | |
| // still correct to not use `ctfe_mode`: that mode is for validation of the final constant | |
| // value, it rules out things like `UnsafeCell` in awkward places. | |
| if !recursive { | |
| return self.validate_operand_internal( | |
| val, | |
| Path::new(val.layout.ty), | |
| None, | |
| None, | |
| reset_provenance_and_padding, | |
| /*start_in_may_dangle*/ false, | |
| ); | |
| } | |
| // Do a recursive check. | |
| let mut ref_tracking = RefTracking::empty(); | |
| self.validate_operand_internal( | |
| val, | |
| Path::new(val.layout.ty), | |
| Some(&mut ref_tracking), | |
| None, | |
| reset_provenance_and_padding, | |
| /*start_in_may_dangle*/ false, | |
| )?; | |
| while let Some((mplace, path)) = ref_tracking.todo.pop() { | |
| // Things behind reference do *not* have the provenance reset. In fact | |
| // we treat the entire thing as being inside MaybeDangling, i.e., references | |
| // do not have to be dereferenceable. | |
| self.validate_operand_internal( | |
| &mplace.into(), | |
| path, | |
| None, // no further recursion | |
| None, | |
| /*reset_provenance_and_padding*/ false, | |
| /*start_in_may_dangle*/ true, | |
| )?; | |
| } | |
| interp_ok(()) | |
| } | |
| } |