//! 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(())
    }
}
