Change the Miri engine to allow configuring whether to check initialization of integers and floats. This allows the Miri tool to optionally check for initialization if requested by the user.
961 lines
41 KiB
Rust
961 lines
41 KiB
Rust
//! 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::convert::TryFrom;
|
|
use std::fmt::Write;
|
|
use std::num::NonZeroUsize;
|
|
|
|
use rustc_data_structures::fx::FxHashSet;
|
|
use rustc_hir as hir;
|
|
use rustc_middle::mir::interpret::InterpError;
|
|
use rustc_middle::ty;
|
|
use rustc_middle::ty::layout::{LayoutOf, TyAndLayout};
|
|
use rustc_span::symbol::{sym, Symbol};
|
|
use rustc_target::abi::{Abi, Scalar as ScalarAbi, Size, VariantIdx, Variants, WrappingRange};
|
|
|
|
use std::hash::Hash;
|
|
|
|
use super::{
|
|
alloc_range, CheckInAllocMsg, GlobalAlloc, InterpCx, InterpResult, MPlaceTy, Machine,
|
|
MemPlaceMeta, OpTy, ScalarMaybeUninit, ValueVisitor,
|
|
};
|
|
|
|
macro_rules! throw_validation_failure {
|
|
($where:expr, { $( $what_fmt:expr ),+ } $( expected { $( $expected_fmt:expr ),+ } )?) => {{
|
|
let mut msg = String::new();
|
|
msg.push_str("encountered ");
|
|
write!(&mut msg, $($what_fmt),+).unwrap();
|
|
$(
|
|
msg.push_str(", but expected ");
|
|
write!(&mut msg, $($expected_fmt),+).unwrap();
|
|
)?
|
|
let path = rustc_middle::ty::print::with_no_trimmed_paths(|| {
|
|
let where_ = &$where;
|
|
if !where_.is_empty() {
|
|
let mut path = String::new();
|
|
write_path(&mut path, where_);
|
|
Some(path)
|
|
} else {
|
|
None
|
|
}
|
|
});
|
|
throw_ub!(ValidationFailure { path, 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:
|
|
///
|
|
/// ```
|
|
/// let v = try_validation!(some_fn(), some_path, {
|
|
/// Foo | Bar | Baz => { "some failure" },
|
|
/// });
|
|
/// ```
|
|
///
|
|
/// An additional expected parameter can also be added to the failure message:
|
|
///
|
|
/// ```
|
|
/// let v = try_validation!(some_fn(), some_path, {
|
|
/// Foo | Bar | Baz => { "some failure" } expected { "something that wasn't a failure" },
|
|
/// });
|
|
/// ```
|
|
///
|
|
/// An additional nicety is that both parameters actually take format args, so you can just write
|
|
/// the format string in directly:
|
|
///
|
|
/// ```
|
|
/// let v = try_validation!(some_fn(), some_path, {
|
|
/// Foo | Bar | Baz => { "{:?}", some_failure } expected { "{}", expected_value },
|
|
/// });
|
|
/// ```
|
|
///
|
|
macro_rules! try_validation {
|
|
($e:expr, $where:expr,
|
|
$( $( $p:pat_param )|+ => { $( $what_fmt:expr ),+ } $( expected { $( $expected_fmt:expr ),+ } )? ),+ $(,)?
|
|
) => {{
|
|
match $e {
|
|
Ok(x) => x,
|
|
// 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.
|
|
Err(e) => match e.kind() {
|
|
$(
|
|
$($p)|+ =>
|
|
throw_validation_failure!(
|
|
$where,
|
|
{ $( $what_fmt ),+ } $( expected { $( $expected_fmt ),+ } )?
|
|
)
|
|
),+,
|
|
#[allow(unreachable_patterns)]
|
|
_ => Err::<!, _>(e)?,
|
|
}
|
|
}
|
|
}};
|
|
}
|
|
|
|
/// 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 {
|
|
Field(Symbol),
|
|
Variant(Symbol),
|
|
GeneratorState(VariantIdx),
|
|
CapturedVar(Symbol),
|
|
ArrayElem(usize),
|
|
TupleElem(usize),
|
|
Deref,
|
|
EnumTag,
|
|
GeneratorTag,
|
|
DynDowncast,
|
|
}
|
|
|
|
/// Extra things to check for during validation of CTFE results.
|
|
pub enum CtfeValidationMode {
|
|
/// Regular validation, nothing special happening.
|
|
Regular,
|
|
/// Validation of a `const`.
|
|
/// `inner` says if this is an inner, indirect allocation (as opposed to the top-level const
|
|
/// allocation). Being an inner allocation makes a difference because the top-level allocation
|
|
/// of a `const` is copied for each use, but the inner allocations are implicitly shared.
|
|
/// `allow_static_ptrs` says if pointers to statics are permitted (which is the case for promoteds in statics).
|
|
Const { inner: bool, allow_static_ptrs: bool },
|
|
}
|
|
|
|
/// State for tracking recursive validation of references
|
|
pub struct RefTracking<T, PATH = ()> {
|
|
pub seen: FxHashSet<T>,
|
|
pub todo: Vec<(T, PATH)>,
|
|
}
|
|
|
|
impl<T: Copy + Eq + Hash + std::fmt::Debug, PATH: Default> RefTracking<T, PATH> {
|
|
pub fn empty() -> Self {
|
|
RefTracking { seen: FxHashSet::default(), todo: vec![] }
|
|
}
|
|
pub fn new(op: T) -> Self {
|
|
let mut ref_tracking_for_consts =
|
|
RefTracking { seen: FxHashSet::default(), todo: vec![(op, PATH::default())] };
|
|
ref_tracking_for_consts.seen.insert(op);
|
|
ref_tracking_for_consts
|
|
}
|
|
|
|
pub fn track(&mut self, op: T, path: impl FnOnce() -> PATH) {
|
|
if self.seen.insert(op) {
|
|
trace!("Recursing below ptr {:#?}", op);
|
|
let path = path();
|
|
// Remember to come back to this later.
|
|
self.todo.push((op, path));
|
|
}
|
|
}
|
|
}
|
|
|
|
/// 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),
|
|
GeneratorTag => write!(out, ".<generator-tag>"),
|
|
GeneratorState(idx) => write!(out, ".<generator-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 => write!(out, ".<dyn-downcast>"),
|
|
}
|
|
.unwrap()
|
|
}
|
|
}
|
|
|
|
// Formats such that a sentence like "expected something {}" to mean
|
|
// "expected something <in the given range>" makes sense.
|
|
fn wrapping_range_format(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 {}, or greater or equal to {}", hi, 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 {:?}", r)
|
|
}
|
|
}
|
|
|
|
struct ValidityVisitor<'rt, 'mir, 'tcx, M: Machine<'mir, 'tcx>> {
|
|
/// The `path` may be pushed to, but the part that is present when a function
|
|
/// starts must not be changed! `visit_fields` and `visit_array` rely on
|
|
/// this stack discipline.
|
|
path: Vec<PathElem>,
|
|
ref_tracking: Option<&'rt mut RefTracking<MPlaceTy<'tcx, M::PointerTag>, Vec<PathElem>>>,
|
|
/// `None` indicates this is not validating for CTFE (but for runtime).
|
|
ctfe_mode: Option<CtfeValidationMode>,
|
|
ecx: &'rt InterpCx<'mir, 'tcx, M>,
|
|
}
|
|
|
|
impl<'rt, 'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> ValidityVisitor<'rt, 'mir, 'tcx, M> {
|
|
fn aggregate_field_path_elem(&mut self, layout: TyAndLayout<'tcx>, field: usize) -> PathElem {
|
|
// First, check if we are projecting to a variant.
|
|
match layout.variants {
|
|
Variants::Multiple { tag_field, .. } => {
|
|
if tag_field == field {
|
|
return match layout.ty.kind() {
|
|
ty::Adt(def, ..) if def.is_enum() => PathElem::EnumTag,
|
|
ty::Generator(..) => PathElem::GeneratorTag,
|
|
_ => bug!("non-variant type {:?}", layout.ty),
|
|
};
|
|
}
|
|
}
|
|
Variants::Single { .. } => {}
|
|
}
|
|
|
|
// Now we know we are projecting to a field, so figure out which one.
|
|
match layout.ty.kind() {
|
|
// generators and closures.
|
|
ty::Closure(def_id, _) | ty::Generator(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 tables = self.ecx.tcx.typeck(local_def_id);
|
|
if let Some(captured_place) =
|
|
tables.closure_min_captures_flattened(*def_id).nth(field)
|
|
{
|
|
// Sometimes the index is beyond the number of upvars (seen
|
|
// for a generator).
|
|
let var_hir_id = captured_place.get_root_variable();
|
|
let node = self.ecx.tcx.hir().get(var_hir_id);
|
|
if let hir::Node::Binding(pat) = node {
|
|
if 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.variants[index].fields[field].ident.name)
|
|
}
|
|
Variants::Multiple { .. } => bug!("we handled variants above"),
|
|
}
|
|
}
|
|
|
|
// other ADTs
|
|
ty::Adt(def, _) => PathElem::Field(def.non_enum_variant().fields[field].ident.name),
|
|
|
|
// arrays/slices
|
|
ty::Array(..) | ty::Slice(..) => PathElem::ArrayElem(field),
|
|
|
|
// dyn traits
|
|
ty::Dynamic(..) => PathElem::DynDowncast,
|
|
|
|
// nothing else has an aggregate layout
|
|
_ => bug!("aggregate_field_path_elem: got non-aggregate type {:?}", layout.ty),
|
|
}
|
|
}
|
|
|
|
fn with_elem<R>(
|
|
&mut self,
|
|
elem: PathElem,
|
|
f: impl FnOnce(&mut Self) -> InterpResult<'tcx, R>,
|
|
) -> InterpResult<'tcx, R> {
|
|
// Remember the old state
|
|
let path_len = self.path.len();
|
|
// Record new element
|
|
self.path.push(elem);
|
|
// Perform operation
|
|
let r = f(self)?;
|
|
// Undo changes
|
|
self.path.truncate(path_len);
|
|
// Done
|
|
Ok(r)
|
|
}
|
|
|
|
fn check_wide_ptr_meta(
|
|
&mut self,
|
|
meta: MemPlaceMeta<M::PointerTag>,
|
|
pointee: TyAndLayout<'tcx>,
|
|
) -> InterpResult<'tcx> {
|
|
let tail = self.ecx.tcx.struct_tail_erasing_lifetimes(pointee.ty, self.ecx.param_env);
|
|
match tail.kind() {
|
|
ty::Dynamic(..) => {
|
|
let vtable = self.ecx.scalar_to_ptr(meta.unwrap_meta());
|
|
// Direct call to `check_ptr_access_align` checks alignment even on CTFE machines.
|
|
try_validation!(
|
|
self.ecx.memory.check_ptr_access_align(
|
|
vtable,
|
|
3 * self.ecx.tcx.data_layout.pointer_size, // drop, size, align
|
|
self.ecx.tcx.data_layout.pointer_align.abi,
|
|
CheckInAllocMsg::InboundsTest, // will anyway be replaced by validity message
|
|
),
|
|
self.path,
|
|
err_ub!(DanglingIntPointer(..)) |
|
|
err_ub!(PointerUseAfterFree(..)) =>
|
|
{ "dangling vtable pointer in wide pointer" },
|
|
err_ub!(AlignmentCheckFailed { .. }) =>
|
|
{ "unaligned vtable pointer in wide pointer" },
|
|
err_ub!(PointerOutOfBounds { .. }) =>
|
|
{ "too small vtable" },
|
|
);
|
|
try_validation!(
|
|
self.ecx.read_drop_type_from_vtable(vtable),
|
|
self.path,
|
|
err_ub!(DanglingIntPointer(..)) |
|
|
err_ub!(InvalidFunctionPointer(..)) =>
|
|
{ "invalid drop function pointer in vtable (not pointing to a function)" },
|
|
err_ub!(InvalidVtableDropFn(..)) =>
|
|
{ "invalid drop function pointer in vtable (function has incompatible signature)" },
|
|
);
|
|
try_validation!(
|
|
self.ecx.read_size_and_align_from_vtable(vtable),
|
|
self.path,
|
|
err_ub!(InvalidVtableSize) =>
|
|
{ "invalid vtable: size is bigger than largest supported object" },
|
|
err_ub!(InvalidVtableAlignment(msg)) =>
|
|
{ "invalid vtable: alignment {}", msg },
|
|
err_unsup!(ReadPointerAsBytes) => { "invalid size or align in vtable" },
|
|
);
|
|
// FIXME: More checks for the vtable.
|
|
}
|
|
ty::Slice(..) | ty::Str => {
|
|
let _len = try_validation!(
|
|
meta.unwrap_meta().to_machine_usize(self.ecx),
|
|
self.path,
|
|
err_unsup!(ReadPointerAsBytes) => { "non-integer slice length in wide pointer" },
|
|
);
|
|
// 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),
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
/// Check a reference or `Box`.
|
|
fn check_safe_pointer(
|
|
&mut self,
|
|
value: &OpTy<'tcx, M::PointerTag>,
|
|
kind: &str,
|
|
) -> InterpResult<'tcx> {
|
|
let value = try_validation!(
|
|
self.ecx.read_immediate(value),
|
|
self.path,
|
|
err_unsup!(ReadPointerAsBytes) => { "part of a pointer" } expected { "a proper pointer or integer value" },
|
|
);
|
|
// Handle wide pointers.
|
|
// Check metadata early, for better diagnostics
|
|
let place = try_validation!(
|
|
self.ecx.ref_to_mplace(&value),
|
|
self.path,
|
|
err_ub!(InvalidUninitBytes(None)) => { "uninitialized {}", kind },
|
|
);
|
|
if place.layout.is_unsized() {
|
|
self.check_wide_ptr_meta(place.meta, place.layout)?;
|
|
}
|
|
// Make sure this is dereferenceable and all.
|
|
let size_and_align = try_validation!(
|
|
self.ecx.size_and_align_of_mplace(&place),
|
|
self.path,
|
|
err_ub!(InvalidMeta(msg)) => { "invalid {} metadata: {}", kind, msg },
|
|
);
|
|
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));
|
|
// Direct call to `check_ptr_access_align` checks alignment even on CTFE machines.
|
|
try_validation!(
|
|
self.ecx.memory.check_ptr_access_align(
|
|
place.ptr,
|
|
size,
|
|
align,
|
|
CheckInAllocMsg::InboundsTest, // will anyway be replaced by validity message
|
|
),
|
|
self.path,
|
|
err_ub!(AlignmentCheckFailed { required, has }) =>
|
|
{
|
|
"an unaligned {} (required {} byte alignment but found {})",
|
|
kind,
|
|
required.bytes(),
|
|
has.bytes()
|
|
},
|
|
err_ub!(DanglingIntPointer(0, _)) =>
|
|
{ "a null {}", kind },
|
|
err_ub!(DanglingIntPointer(i, _)) =>
|
|
{ "a dangling {} (address 0x{:x} is unallocated)", kind, i },
|
|
err_ub!(PointerOutOfBounds { .. }) =>
|
|
{ "a dangling {} (going beyond the bounds of its allocation)", kind },
|
|
// This cannot happen during const-eval (because interning already detects
|
|
// dangling pointers), but it can happen in Miri.
|
|
err_ub!(PointerUseAfterFree(..)) =>
|
|
{ "a dangling {} (use-after-free)", kind },
|
|
);
|
|
// Recursive checking
|
|
if let Some(ref mut ref_tracking) = self.ref_tracking {
|
|
// Proceed recursively even for ZST, no reason to skip them!
|
|
// `!` is a ZST and we want to validate it.
|
|
// Skip validation entirely for some external statics
|
|
if let Ok((alloc_id, _offset, _ptr)) = self.ecx.memory.ptr_try_get_alloc(place.ptr) {
|
|
// not a ZST
|
|
let alloc_kind = self.ecx.tcx.get_global_alloc(alloc_id);
|
|
if let Some(GlobalAlloc::Static(did)) = alloc_kind {
|
|
assert!(!self.ecx.tcx.is_thread_local_static(did));
|
|
assert!(self.ecx.tcx.is_static(did));
|
|
if matches!(
|
|
self.ctfe_mode,
|
|
Some(CtfeValidationMode::Const { allow_static_ptrs: false, .. })
|
|
) {
|
|
// See const_eval::machine::MemoryExtra::can_access_statics for why
|
|
// this check is so important.
|
|
// This check is reachable when the const just referenced the static,
|
|
// but never read it (so we never entered `before_access_global`).
|
|
throw_validation_failure!(self.path,
|
|
{ "a {} pointing to a static variable", kind }
|
|
);
|
|
}
|
|
// We skip 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.
|
|
// We might miss const-invalid data,
|
|
// but things are still sound otherwise (in particular re: consts
|
|
// referring to statics).
|
|
return Ok(());
|
|
}
|
|
}
|
|
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_path = Vec::with_capacity(path.len() + 1);
|
|
new_path.clone_from(path);
|
|
new_path.push(PathElem::Deref);
|
|
new_path
|
|
});
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
fn read_scalar(
|
|
&self,
|
|
op: &OpTy<'tcx, M::PointerTag>,
|
|
) -> InterpResult<'tcx, ScalarMaybeUninit<M::PointerTag>> {
|
|
Ok(try_validation!(
|
|
self.ecx.read_scalar(op),
|
|
self.path,
|
|
err_unsup!(ReadPointerAsBytes) => { "(potentially part of) a pointer" } expected { "plain (non-pointer) bytes" },
|
|
))
|
|
}
|
|
|
|
/// 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.
|
|
fn try_visit_primitive(
|
|
&mut self,
|
|
value: &OpTy<'tcx, M::PointerTag>,
|
|
) -> InterpResult<'tcx, bool> {
|
|
// Go over all the primitive types
|
|
let ty = value.layout.ty;
|
|
match ty.kind() {
|
|
ty::Bool => {
|
|
let value = self.read_scalar(value)?;
|
|
try_validation!(
|
|
value.to_bool(),
|
|
self.path,
|
|
err_ub!(InvalidBool(..)) | err_ub!(InvalidUninitBytes(None)) =>
|
|
{ "{}", value } expected { "a boolean" },
|
|
);
|
|
Ok(true)
|
|
}
|
|
ty::Char => {
|
|
let value = self.read_scalar(value)?;
|
|
try_validation!(
|
|
value.to_char(),
|
|
self.path,
|
|
err_ub!(InvalidChar(..)) | err_ub!(InvalidUninitBytes(None)) =>
|
|
{ "{}", value } expected { "a valid unicode scalar value (in `0..=0x10FFFF` but not in `0xD800..=0xDFFF`)" },
|
|
);
|
|
Ok(true)
|
|
}
|
|
ty::Float(_) | ty::Int(_) | ty::Uint(_) => {
|
|
let value = self.read_scalar(value)?;
|
|
// NOTE: Keep this in sync with the array optimization for int/float
|
|
// types below!
|
|
if M::enforce_number_validity(self.ecx) {
|
|
// Integers/floats in CTFE: Must be scalar bits, pointers are dangerous
|
|
let is_bits = value.check_init().map_or(false, |v| v.try_to_int().is_ok());
|
|
if !is_bits {
|
|
throw_validation_failure!(self.path,
|
|
{ "{}", value } expected { "initialized plain (non-pointer) bytes" }
|
|
)
|
|
}
|
|
}
|
|
Ok(true)
|
|
}
|
|
ty::RawPtr(..) => {
|
|
// We are conservative with uninit for integers, but try to
|
|
// actually enforce the strict rules for raw pointers (mostly because
|
|
// that lets us re-use `ref_to_mplace`).
|
|
let place = try_validation!(
|
|
self.ecx.read_immediate(value).and_then(|ref i| self.ecx.ref_to_mplace(i)),
|
|
self.path,
|
|
err_ub!(InvalidUninitBytes(None)) => { "uninitialized raw pointer" },
|
|
err_unsup!(ReadPointerAsBytes) => { "part of a pointer" } expected { "a proper pointer or integer value" },
|
|
);
|
|
if place.layout.is_unsized() {
|
|
self.check_wide_ptr_meta(place.meta, place.layout)?;
|
|
}
|
|
Ok(true)
|
|
}
|
|
ty::Ref(_, ty, mutbl) => {
|
|
if matches!(self.ctfe_mode, Some(CtfeValidationMode::Const { .. }))
|
|
&& *mutbl == hir::Mutability::Mut
|
|
{
|
|
// A mutable reference inside a const? That does not seem right (except if it is
|
|
// a ZST).
|
|
let layout = self.ecx.layout_of(ty)?;
|
|
if !layout.is_zst() {
|
|
throw_validation_failure!(self.path, { "mutable reference in a `const`" });
|
|
}
|
|
}
|
|
self.check_safe_pointer(value, "reference")?;
|
|
Ok(true)
|
|
}
|
|
ty::Adt(def, ..) if def.is_box() => {
|
|
self.check_safe_pointer(value, "box")?;
|
|
Ok(true)
|
|
}
|
|
ty::FnPtr(_sig) => {
|
|
let value = try_validation!(
|
|
self.ecx.read_immediate(value),
|
|
self.path,
|
|
err_unsup!(ReadPointerAsBytes) => { "part of a pointer" } expected { "a proper pointer or integer value" },
|
|
);
|
|
// Make sure we print a `ScalarMaybeUninit` (and not an `ImmTy`) in the error
|
|
// message below.
|
|
let value = value.to_scalar_or_uninit();
|
|
let _fn = try_validation!(
|
|
value.check_init().and_then(|ptr| self.ecx.memory.get_fn(self.ecx.scalar_to_ptr(ptr))),
|
|
self.path,
|
|
err_ub!(DanglingIntPointer(..)) |
|
|
err_ub!(InvalidFunctionPointer(..)) |
|
|
err_ub!(InvalidUninitBytes(None)) =>
|
|
{ "{}", value } expected { "a function pointer" },
|
|
);
|
|
// FIXME: Check if the signature matches
|
|
Ok(true)
|
|
}
|
|
ty::Never => throw_validation_failure!(self.path, { "a value of the never type `!`" }),
|
|
ty::Foreign(..) | ty::FnDef(..) => {
|
|
// Nothing to check.
|
|
Ok(true)
|
|
}
|
|
// 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::Generator(..) => 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::Opaque(..)
|
|
| ty::Projection(..)
|
|
| ty::GeneratorWitness(..) => bug!("Encountered invalid type {:?}", ty),
|
|
}
|
|
}
|
|
|
|
fn visit_scalar(
|
|
&mut self,
|
|
op: &OpTy<'tcx, M::PointerTag>,
|
|
scalar_layout: ScalarAbi,
|
|
) -> InterpResult<'tcx> {
|
|
if scalar_layout.valid_range.is_full_for(op.layout.size) {
|
|
// Nothing to check
|
|
return Ok(());
|
|
}
|
|
// At least one value is excluded.
|
|
let valid_range = scalar_layout.valid_range;
|
|
let WrappingRange { start, end } = valid_range;
|
|
let max_value = op.layout.size.unsigned_int_max();
|
|
assert!(end <= max_value);
|
|
// Determine the allowed range
|
|
let value = self.read_scalar(op)?;
|
|
let value = try_validation!(
|
|
value.check_init(),
|
|
self.path,
|
|
err_ub!(InvalidUninitBytes(None)) => { "{}", value }
|
|
expected { "something {}", wrapping_range_format(valid_range, max_value) },
|
|
);
|
|
let bits = match value.try_to_int() {
|
|
Err(_) => {
|
|
// So this is a pointer then, and casting to an int failed.
|
|
// Can only happen during CTFE.
|
|
let ptr = self.ecx.scalar_to_ptr(value);
|
|
if start == 1 && end == max_value {
|
|
// Only null is the niche. So make sure the ptr is NOT null.
|
|
if self.ecx.memory.ptr_may_be_null(ptr) {
|
|
throw_validation_failure!(self.path,
|
|
{ "a potentially null pointer" }
|
|
expected {
|
|
"something that cannot possibly fail to be {}",
|
|
wrapping_range_format(valid_range, max_value)
|
|
}
|
|
)
|
|
}
|
|
return Ok(());
|
|
} else {
|
|
// Conservatively, we reject, because the pointer *could* have a bad
|
|
// value.
|
|
throw_validation_failure!(self.path,
|
|
{ "a pointer" }
|
|
expected {
|
|
"something that cannot possibly fail to be {}",
|
|
wrapping_range_format(valid_range, max_value)
|
|
}
|
|
)
|
|
}
|
|
}
|
|
Ok(int) => int.assert_bits(op.layout.size),
|
|
};
|
|
// Now compare. This is slightly subtle because this is a special "wrap-around" range.
|
|
if valid_range.contains(bits) {
|
|
Ok(())
|
|
} else {
|
|
throw_validation_failure!(self.path,
|
|
{ "{}", bits }
|
|
expected { "something {}", wrapping_range_format(valid_range, max_value) }
|
|
)
|
|
}
|
|
}
|
|
}
|
|
|
|
impl<'rt, 'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> ValueVisitor<'mir, 'tcx, M>
|
|
for ValidityVisitor<'rt, 'mir, 'tcx, M>
|
|
{
|
|
type V = OpTy<'tcx, M::PointerTag>;
|
|
|
|
#[inline(always)]
|
|
fn ecx(&self) -> &InterpCx<'mir, 'tcx, M> {
|
|
&self.ecx
|
|
}
|
|
|
|
fn read_discriminant(
|
|
&mut self,
|
|
op: &OpTy<'tcx, M::PointerTag>,
|
|
) -> InterpResult<'tcx, VariantIdx> {
|
|
self.with_elem(PathElem::EnumTag, move |this| {
|
|
Ok(try_validation!(
|
|
this.ecx.read_discriminant(op),
|
|
this.path,
|
|
err_ub!(InvalidTag(val)) =>
|
|
{ "{}", val } expected { "a valid enum tag" },
|
|
err_ub!(InvalidUninitBytes(None)) =>
|
|
{ "uninitialized bytes" } expected { "a valid enum tag" },
|
|
err_unsup!(ReadPointerAsBytes) =>
|
|
{ "a pointer" } expected { "a valid enum tag" },
|
|
)
|
|
.1)
|
|
})
|
|
}
|
|
|
|
#[inline]
|
|
fn visit_field(
|
|
&mut self,
|
|
old_op: &OpTy<'tcx, M::PointerTag>,
|
|
field: usize,
|
|
new_op: &OpTy<'tcx, M::PointerTag>,
|
|
) -> InterpResult<'tcx> {
|
|
let elem = self.aggregate_field_path_elem(old_op.layout, field);
|
|
self.with_elem(elem, move |this| this.visit_value(new_op))
|
|
}
|
|
|
|
#[inline]
|
|
fn visit_variant(
|
|
&mut self,
|
|
old_op: &OpTy<'tcx, M::PointerTag>,
|
|
variant_id: VariantIdx,
|
|
new_op: &OpTy<'tcx, M::PointerTag>,
|
|
) -> InterpResult<'tcx> {
|
|
let name = match old_op.layout.ty.kind() {
|
|
ty::Adt(adt, _) => PathElem::Variant(adt.variants[variant_id].ident.name),
|
|
// Generators also have variants
|
|
ty::Generator(..) => PathElem::GeneratorState(variant_id),
|
|
_ => bug!("Unexpected type with variant: {:?}", old_op.layout.ty),
|
|
};
|
|
self.with_elem(name, move |this| this.visit_value(new_op))
|
|
}
|
|
|
|
#[inline(always)]
|
|
fn visit_union(
|
|
&mut self,
|
|
_op: &OpTy<'tcx, M::PointerTag>,
|
|
_fields: NonZeroUsize,
|
|
) -> InterpResult<'tcx> {
|
|
Ok(())
|
|
}
|
|
|
|
#[inline]
|
|
fn visit_value(&mut self, op: &OpTy<'tcx, M::PointerTag>) -> InterpResult<'tcx> {
|
|
trace!("visit_value: {:?}, {:?}", *op, op.layout);
|
|
|
|
// Check primitive types -- the leafs of our recursive descend.
|
|
if self.try_visit_primitive(op)? {
|
|
return Ok(());
|
|
}
|
|
// Sanity check: `builtin_deref` does not know any pointers that are not primitive.
|
|
assert!(op.layout.ty.builtin_deref(true).is_none());
|
|
|
|
// Special check preventing `UnsafeCell` in the inner part of constants
|
|
if let Some(def) = op.layout.ty.ty_adt_def() {
|
|
if matches!(self.ctfe_mode, Some(CtfeValidationMode::Const { inner: true, .. }))
|
|
&& Some(def.did) == self.ecx.tcx.lang_items().unsafe_cell_type()
|
|
{
|
|
throw_validation_failure!(self.path, { "`UnsafeCell` in a `const`" });
|
|
}
|
|
}
|
|
|
|
// Recursively walk the value at its type.
|
|
self.walk_value(op)?;
|
|
|
|
// *After* all of this, check the ABI. We need to check the ABI 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, first recurse,
|
|
// then check ABI.
|
|
//
|
|
// FIXME: We could avoid some redundant checks here. For newtypes wrapping
|
|
// scalars, we do the same check on every "level" (e.g., first we check
|
|
// MyNewtype and then the scalar in there).
|
|
match op.layout.abi {
|
|
Abi::Uninhabited => {
|
|
throw_validation_failure!(self.path,
|
|
{ "a value of uninhabited type {:?}", op.layout.ty }
|
|
);
|
|
}
|
|
Abi::Scalar(scalar_layout) => {
|
|
self.visit_scalar(op, scalar_layout)?;
|
|
}
|
|
Abi::ScalarPair { .. } | Abi::Vector { .. } => {
|
|
// These have fields that we already visited above, so we already checked
|
|
// all their scalar-level restrictions.
|
|
// There is also no equivalent to `rustc_layout_scalar_valid_range_start`
|
|
// that would make skipping them here an issue.
|
|
}
|
|
Abi::Aggregate { .. } => {
|
|
// Nothing to do.
|
|
}
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
fn visit_aggregate(
|
|
&mut self,
|
|
op: &OpTy<'tcx, M::PointerTag>,
|
|
fields: impl Iterator<Item = InterpResult<'tcx, Self::V>>,
|
|
) -> InterpResult<'tcx> {
|
|
match op.layout.ty.kind() {
|
|
ty::Str => {
|
|
let mplace = op.assert_mem_place(); // strings are never immediate
|
|
let len = mplace.len(self.ecx)?;
|
|
try_validation!(
|
|
self.ecx.memory.read_bytes(mplace.ptr, Size::from_bytes(len)),
|
|
self.path,
|
|
err_ub!(InvalidUninitBytes(..)) => { "uninitialized data in `str`" },
|
|
err_unsup!(ReadPointerAsBytes) => { "a pointer in `str`" },
|
|
);
|
|
}
|
|
ty::Array(tys, ..) | ty::Slice(tys)
|
|
// This optimization applies for types that can hold arbitrary bytes (such as
|
|
// integer and floating point types) or for structs or tuples with no fields.
|
|
// 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(..))
|
|
=>
|
|
{
|
|
// Optimized handling for arrays of integer/float type.
|
|
|
|
// Arrays cannot be immediate, slices are never immediate.
|
|
let mplace = op.assert_mem_place();
|
|
// This is the length of the array/slice.
|
|
let len = mplace.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;
|
|
|
|
// 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`.
|
|
// In run-time mode, we accept pointers in here. This is actually more
|
|
// permissive than a per-element check would be, e.g., we accept
|
|
// a &[u8] that contains a pointer even though bytewise checking would
|
|
// reject it. However, that's good: We don't inherently want
|
|
// to reject those pointers, we just do not have the machinery to
|
|
// talk about parts of a pointer.
|
|
// We also accept uninit, for consistency with the slow path.
|
|
let alloc = match self.ecx.memory.get(mplace.ptr, size, mplace.align)? {
|
|
Some(a) => a,
|
|
None => {
|
|
// Size 0, nothing more to check.
|
|
return Ok(());
|
|
}
|
|
};
|
|
|
|
let allow_uninit_and_ptr = !M::enforce_number_validity(self.ecx);
|
|
match alloc.check_bytes(
|
|
alloc_range(Size::ZERO, size),
|
|
allow_uninit_and_ptr,
|
|
) {
|
|
// In the happy case, we needn't check anything else.
|
|
Ok(()) => {}
|
|
// Some error happened, try to provide a more detailed description.
|
|
Err(err) => {
|
|
// For some errors we might be able to provide extra information.
|
|
// (This custom logic does not fit the `try_validation!` macro.)
|
|
match err.kind() {
|
|
err_ub!(InvalidUninitBytes(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.uninit_offset.bytes() / layout.size.bytes(),
|
|
)
|
|
.unwrap();
|
|
self.path.push(PathElem::ArrayElem(i));
|
|
|
|
throw_validation_failure!(self.path, { "uninitialized bytes" })
|
|
}
|
|
err_unsup!(ReadPointerAsBytes) => {
|
|
throw_validation_failure!(self.path, { "a pointer" } expected { "plain (non-pointer) bytes" })
|
|
}
|
|
|
|
// Propagate upwards (that will also check for unexpected errors).
|
|
_ => return Err(err),
|
|
}
|
|
}
|
|
}
|
|
}
|
|
// 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).
|
|
self.walk_aggregate(op, fields.take(1))?
|
|
}
|
|
_ => {
|
|
self.walk_aggregate(op, fields)? // default handler
|
|
}
|
|
}
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
|
|
fn validate_operand_internal(
|
|
&self,
|
|
op: &OpTy<'tcx, M::PointerTag>,
|
|
path: Vec<PathElem>,
|
|
ref_tracking: Option<&mut RefTracking<MPlaceTy<'tcx, M::PointerTag>, Vec<PathElem>>>,
|
|
ctfe_mode: Option<CtfeValidationMode>,
|
|
) -> InterpResult<'tcx> {
|
|
trace!("validate_operand_internal: {:?}, {:?}", *op, op.layout.ty);
|
|
|
|
// Construct a visitor
|
|
let mut visitor = ValidityVisitor { path, ref_tracking, ctfe_mode, ecx: self };
|
|
|
|
// Run it.
|
|
match visitor.visit_value(&op) {
|
|
Ok(()) => Ok(()),
|
|
// Pass through validation failures.
|
|
Err(err) if matches!(err.kind(), err_ub!(ValidationFailure { .. })) => Err(err),
|
|
// Also pass through InvalidProgram, those just indicate that we could not
|
|
// validate and each caller will know best what to do with them.
|
|
Err(err) if matches!(err.kind(), InterpError::InvalidProgram(_)) => Err(err),
|
|
// Avoid other errors as those do not show *where* in the value the issue lies.
|
|
Err(err) => {
|
|
err.print_backtrace();
|
|
bug!("Unexpected error during validation: {}", err);
|
|
}
|
|
}
|
|
}
|
|
|
|
/// This function checks the data at `op` to be const-valid.
|
|
/// `op` is assumed to cover valid memory if it is an indirect operand.
|
|
/// It will error if the bits at the destination do not match the ones described by the layout.
|
|
///
|
|
/// `ref_tracking` 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 fn const_validate_operand(
|
|
&self,
|
|
op: &OpTy<'tcx, M::PointerTag>,
|
|
path: Vec<PathElem>,
|
|
ref_tracking: &mut RefTracking<MPlaceTy<'tcx, M::PointerTag>, Vec<PathElem>>,
|
|
ctfe_mode: CtfeValidationMode,
|
|
) -> InterpResult<'tcx> {
|
|
self.validate_operand_internal(op, path, Some(ref_tracking), Some(ctfe_mode))
|
|
}
|
|
|
|
/// This function checks the data at `op` to be runtime-valid.
|
|
/// `op` is assumed to cover valid memory if it is an indirect operand.
|
|
/// It will error if the bits at the destination do not match the ones described by the layout.
|
|
#[inline(always)]
|
|
pub fn validate_operand(&self, op: &OpTy<'tcx, M::PointerTag>) -> InterpResult<'tcx> {
|
|
self.validate_operand_internal(op, vec![], None, None)
|
|
}
|
|
}
|