use std::convert::TryFrom;
use std::fmt::Write;
use std::num::NonZeroUsize;
use std::ops::RangeInclusive;
use rustc_data_structures::fx::FxHashSet;
use rustc_hir as hir;
use rustc_middle::mir::interpret::{InterpError, InterpErrorInfo};
use rustc_middle::ty;
use rustc_middle::ty::layout::TyAndLayout;
use rustc_span::symbol::{sym, Symbol};
use rustc_target::abi::{Abi, LayoutOf, Scalar, Size, VariantIdx, Variants};
use std::hash::Hash;
use super::{
CheckInAllocMsg, GlobalAlloc, InterpCx, InterpResult, MPlaceTy, Machine, MemPlaceMeta, OpTy,
ValueVisitor,
};
macro_rules! throw_validation_failure {
($where:expr, { $( $what_fmt:expr ),+ } $( expected { $( $expected_fmt:expr ),+ } )?) => {{
let msg = rustc_middle::ty::print::with_no_trimmed_paths(|| {
let mut msg = String::new();
msg.push_str("encountered ");
write!(&mut msg, $($what_fmt),+).unwrap();
let where_ = &$where;
if !where_.is_empty() {
msg.push_str(" at ");
write_path(&mut msg, where_);
}
$(
msg.push_str(", but expected ");
write!(&mut msg, $($expected_fmt),+).unwrap();
)?
msg
});
throw_ub!(ValidationFailure(msg))
}};
}
macro_rules! try_validation {
($e:expr, $where:expr,
$( $( $p:pat )|+ => { $( $what_fmt:expr ),+ } $( expected { $( $expected_fmt:expr ),+ } )? ),+ $(,)?
) => {{
match $e {
Ok(x) => x,
$( $( Err(InterpErrorInfo { kind: $p, .. }) )|+ =>
throw_validation_failure!(
$where,
{ $( $what_fmt ),+ } $( expected { $( $expected_fmt ),+ } )?
),
)+
#[allow(unreachable_patterns)]
Err(e) => Err::<!, _>(e)?,
}
}};
}
#[derive(Copy, Clone, Debug)]
pub enum PathElem {
Field(Symbol),
Variant(Symbol),
GeneratorState(VariantIdx),
CapturedVar(Symbol),
ArrayElem(usize),
TupleElem(usize),
Deref,
EnumTag,
GeneratorTag,
DynDowncast,
}
pub enum CtfeValidationMode {
Regular,
Const { inner: bool },
}
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();
self.todo.push((op, path));
}
}
}
fn write_path(out: &mut String, path: &Vec<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 => write!(out, ".<deref>"),
DynDowncast => write!(out, ".<dyn-downcast>"),
}
.unwrap()
}
}
fn wrapping_range_contains(r: &RangeInclusive<u128>, test: u128) -> bool {
let (lo, hi) = r.clone().into_inner();
if lo > hi {
(..=hi).contains(&test) || (lo..).contains(&test)
} else {
r.contains(&test)
}
}
fn wrapping_range_format(r: &RangeInclusive<u128>, max_hi: u128) -> String {
let (lo, hi) = r.clone().into_inner();
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>> {
path: Vec<PathElem>,
ref_tracking: Option<&'rt mut RefTracking<MPlaceTy<'tcx, M::PointerTag>, Vec<PathElem>>>,
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 {
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 { .. } => {}
}
match layout.ty.kind() {
ty::Closure(def_id, _) | ty::Generator(def_id, _, _) => {
let mut name = None;
if let Some(def_id) = def_id.as_local() {
let tables = self.ecx.tcx.typeck(def_id);
if let Some(upvars) = tables.closure_captures.get(&def_id.to_def_id()) {
if let Some((&var_hir_id, _)) = upvars.get_index(field) {
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(|| {
sym::integer(field)
}))
}
ty::Tuple(_) => PathElem::TupleElem(field),
ty::Adt(def, ..) if def.is_enum() => {
match layout.variants {
Variants::Single { index } => {
PathElem::Field(def.variants[index].fields[field].ident.name)
}
Variants::Multiple { .. } => bug!("we handled variants above"),
}
}
ty::Adt(def, _) => PathElem::Field(def.non_enum_variant().fields[field].ident.name),
ty::Array(..) | ty::Slice(..) => PathElem::ArrayElem(field),
ty::Dynamic(..) => PathElem::DynDowncast,
_ => 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> {
let path_len = self.path.len();
self.path.push(elem);
let r = f(self)?;
self.path.truncate(path_len);
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 = meta.unwrap_meta();
try_validation!(
self.ecx.memory.check_ptr_access_align(
vtable,
3 * self.ecx.tcx.data_layout.pointer_size,
Some(self.ecx.tcx.data_layout.pointer_align.abi),
CheckInAllocMsg::InboundsTest,
),
self.path,
err_ub!(DanglingIntPointer(..)) |
err_ub!(PointerUseAfterFree(..)) |
err_unsup!(ReadBytesAsPointer) =>
{ "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(..)) |
err_unsup!(ReadBytesAsPointer) =>
{ "invalid drop function pointer in vtable (not pointing to a function)" },
err_ub!(InvalidDropFn(..)) =>
{ "invalid drop function pointer in vtable (function has incompatible signature)" },
);
try_validation!(
self.ecx.read_size_and_align_from_vtable(vtable),
self.path,
err_unsup!(ReadPointerAsBytes) => { "invalid size or align in 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" },
);
}
ty::Foreign(..) => {
}
_ => bug!("Unexpected unsized type tail: {:?}", tail),
}
Ok(())
}
fn check_safe_pointer(
&mut self,
value: OpTy<'tcx, M::PointerTag>,
kind: &str,
) -> InterpResult<'tcx> {
let value = self.ecx.read_immediate(value)?;
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)?;
}
let size_and_align = try_validation!(
self.ecx.size_and_align_of(place.meta, place.layout),
self.path,
err_ub!(InvalidMeta(msg)) => { "invalid {} metadata: {}", kind, msg },
);
let (size, align) = size_and_align
.unwrap_or_else(|| (place.layout.size, place.layout.align.abi));
let ptr: Option<_> = try_validation!(
self.ecx.memory.check_ptr_access_align(
place.ptr,
size,
Some(align),
CheckInAllocMsg::InboundsTest,
),
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 },
err_unsup!(ReadBytesAsPointer) =>
{ "a dangling {} (created from integer)", kind },
err_ub!(PointerUseAfterFree(..)) =>
{ "a dangling {} (use-after-free)", kind },
);
if let Some(ref mut ref_tracking) = self.ref_tracking {
if let Some(ptr) = ptr {
let alloc_kind = self.ecx.tcx.get_global_alloc(ptr.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 { .. })) {
throw_validation_failure!(self.path,
{ "a {} pointing to a static variable", kind }
);
}
return Ok(());
}
}
let place = if size.bytes() > 0 {
self.ecx.force_mplace_ptr(place).expect("we already bounds-checked")
} else {
place
};
let path = &self.path;
ref_tracking.track(place, || {
let mut new_path = Vec::with_capacity(path.len() + 1);
new_path.clone_from(path);
new_path.push(PathElem::Deref);
new_path
});
}
Ok(())
}
fn try_visit_primitive(
&mut self,
value: OpTy<'tcx, M::PointerTag>,
) -> InterpResult<'tcx, bool> {
let ty = value.layout.ty;
match ty.kind() {
ty::Bool => {
let value = self.ecx.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.ecx.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.ecx.read_scalar(value)?;
if self.ctfe_mode.is_some() {
let is_bits = value.check_init().map_or(false, |v| v.is_bits());
if !is_bits {
throw_validation_failure!(self.path,
{ "{}", value } expected { "initialized plain (non-pointer) bytes" }
)
}
} else {
}
Ok(true)
}
ty::RawPtr(..) => {
let place = try_validation!(
self.ecx.ref_to_mplace(self.ecx.read_immediate(value)?),
self.path,
err_ub!(InvalidUninitBytes(None)) => { "uninitialized raw pointer" },
);
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
{
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 = self.ecx.read_scalar(value)?;
let _fn = try_validation!(
value.check_init().and_then(|ptr| self.ecx.memory.get_fn(ptr)),
self.path,
err_ub!(DanglingIntPointer(..)) |
err_ub!(InvalidFunctionPointer(..)) |
err_ub!(InvalidUninitBytes(None)) |
err_unsup!(ReadBytesAsPointer) =>
{ "{}", value } expected { "a function pointer" },
);
Ok(true)
}
ty::Never => throw_validation_failure!(self.path, { "a value of the never type `!`" }),
ty::Foreign(..) | ty::FnDef(..) => {
Ok(true)
}
ty::Adt(..)
| ty::Tuple(..)
| ty::Array(..)
| ty::Slice(..)
| ty::Str
| ty::Dynamic(..)
| ty::Closure(..)
| ty::Generator(..) => Ok(false),
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: &Scalar,
) -> InterpResult<'tcx> {
let value = self.ecx.read_scalar(op)?;
let valid_range = &scalar_layout.valid_range;
let (lo, hi) = valid_range.clone().into_inner();
let max_hi = u128::MAX >> (128 - op.layout.size.bits());
assert!(hi <= max_hi);
if (lo == 0 && hi == max_hi) || (hi + 1 == lo) {
return Ok(());
}
let value = try_validation!(
value.check_init(),
self.path,
err_ub!(InvalidUninitBytes(None)) => { "{}", value }
expected { "something {}", wrapping_range_format(valid_range, max_hi) },
);
let bits = match value.to_bits_or_ptr(op.layout.size, self.ecx) {
Err(ptr) => {
if lo == 1 && hi == max_hi {
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_hi)
}
)
}
return Ok(());
} else {
throw_validation_failure!(self.path,
{ "a pointer" }
expected {
"something that cannot possibly fail to be {}",
wrapping_range_format(valid_range, max_hi)
}
)
}
}
Ok(data) => data,
};
if wrapping_range_contains(&valid_range, bits) {
Ok(())
} else {
throw_validation_failure!(self.path,
{ "{}", bits }
expected { "something {}", wrapping_range_format(valid_range, max_hi) }
)
}
}
}
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),
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);
if self.try_visit_primitive(op)? {
return Ok(());
}
assert!(op.layout.ty.builtin_deref(true).is_none());
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`" });
}
}
self.walk_value(op)?;
match op.layout.abi {
Abi::Uninhabited => {
throw_validation_failure!(self.path,
{ "a value of uninhabited type {:?}", op.layout.ty }
);
}
Abi::Scalar(ref scalar_layout) => {
self.visit_scalar(op, scalar_layout)?;
}
Abi::ScalarPair { .. } | Abi::Vector { .. } => {
}
Abi::Aggregate { .. } => {
}
}
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(self.ecx);
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`" },
);
}
ty::Array(tys, ..) | ty::Slice(tys)
if matches!(tys.kind(), ty::Int(..) | ty::Uint(..) | ty::Float(..))
=>
{
let mplace = op.assert_mem_place(self.ecx);
let len = mplace.len(self.ecx)?;
if len == 0 {
return Ok(());
}
let layout = self.ecx.layout_of(tys)?;
let size = layout.size * len;
let ptr = self.ecx.force_ptr(mplace.ptr)?;
match self.ecx.memory.get_raw(ptr.alloc_id)?.check_bytes(
self.ecx,
ptr,
size,
self.ctfe_mode.is_none(),
) {
Ok(()) => {}
Err(err) => {
match err.kind {
err_ub!(InvalidUninitBytes(Some(access))) => {
let i = usize::try_from(
access.uninit_ptr.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" })
}
_ => return Err(err),
}
}
}
}
ty::Array(tys, ..) | ty::Slice(tys) if self.ecx.layout_of(tys)?.is_zst() => {
self.walk_aggregate(op, fields.take(1))?
}
_ => {
self.walk_aggregate(op, fields)?
}
}
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);
let mut visitor = ValidityVisitor { path, ref_tracking, ctfe_mode, ecx: self };
let op = self.force_op_ptr(op).unwrap_or(op);
match visitor.visit_value(op) {
Ok(()) => Ok(()),
Err(err) if matches!(err.kind, err_ub!(ValidationFailure { .. })) => Err(err),
Err(err) if matches!(err.kind, InterpError::InvalidProgram(_)) => Err(err),
Err(err) => {
err.print_backtrace();
bug!("Unexpected error during validation: {}", err);
}
}
}
#[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))
}
#[inline(always)]
pub fn validate_operand(&self, op: OpTy<'tcx, M::PointerTag>) -> InterpResult<'tcx> {
self.validate_operand_internal(op, vec![], None, None)
}
}