summaryrefslogtreecommitdiffstats
path: root/compiler/rustc_const_eval/src/interpret/validity.rs
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-05-30 03:59:35 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-05-30 03:59:35 +0000
commitd1b2d29528b7794b41e66fc2136e395a02f8529b (patch)
treea4a17504b260206dec3cf55b2dca82929a348ac2 /compiler/rustc_const_eval/src/interpret/validity.rs
parentReleasing progress-linux version 1.72.1+dfsg1-1~progress7.99u1. (diff)
downloadrustc-d1b2d29528b7794b41e66fc2136e395a02f8529b.tar.xz
rustc-d1b2d29528b7794b41e66fc2136e395a02f8529b.zip
Merging upstream version 1.73.0+dfsg1.
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'compiler/rustc_const_eval/src/interpret/validity.rs')
-rw-r--r--compiler/rustc_const_eval/src/interpret/validity.rs234
1 files changed, 119 insertions, 115 deletions
diff --git a/compiler/rustc_const_eval/src/interpret/validity.rs b/compiler/rustc_const_eval/src/interpret/validity.rs
index 21c655988..d3f05af1c 100644
--- a/compiler/rustc_const_eval/src/interpret/validity.rs
+++ b/compiler/rustc_const_eval/src/interpret/validity.rs
@@ -25,13 +25,17 @@ use rustc_target::abi::{
use std::hash::Hash;
-// for the validation errors
-use super::UndefinedBehaviorInfo::*;
use super::{
AllocId, CheckInAllocMsg, GlobalAlloc, ImmTy, Immediate, InterpCx, InterpResult, MPlaceTy,
- Machine, MemPlaceMeta, OpTy, Pointer, Scalar, ValueVisitor,
+ Machine, MemPlaceMeta, OpTy, Pointer, Projectable, Scalar, ValueVisitor,
};
+// for the validation errors
+use super::InterpError::UndefinedBehavior as Ub;
+use super::InterpError::Unsupported as Unsup;
+use super::UndefinedBehaviorInfo::*;
+use super::UnsupportedOpInfo::*;
+
macro_rules! throw_validation_failure {
($where:expr, $kind: expr) => {{
let where_ = &$where;
@@ -43,7 +47,7 @@ macro_rules! throw_validation_failure {
None
};
- throw_ub!(Validation(ValidationErrorInfo { path, kind: $kind }))
+ throw_ub!(ValidationError(ValidationErrorInfo { path, kind: $kind }))
}};
}
@@ -85,16 +89,16 @@ macro_rules! try_validation {
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.into_parts() {
+ Err(e) => match e.kind() {
$(
- (InterpError::UndefinedBehavior($($p)|+), _) =>
+ $($p)|+ =>
throw_validation_failure!(
$where,
$kind
)
),+,
#[allow(unreachable_patterns)]
- (e, rest) => Err::<!, _>($crate::interpret::InterpErrorInfo::from_parts(e, rest))?,
+ _ => Err::<!, _>(e)?,
}
}
}};
@@ -136,19 +140,19 @@ pub struct RefTracking<T, PATH = ()> {
pub todo: Vec<(T, PATH)>,
}
-impl<T: Copy + Eq + Hash + std::fmt::Debug, PATH: Default> RefTracking<T, PATH> {
+impl<T: Clone + 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())] };
+ RefTracking { seen: FxHashSet::default(), todo: vec![(op.clone(), 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) {
+ if self.seen.insert(op.clone()) {
trace!("Recursing below ptr {:#?}", op);
let path = path();
// Remember to come back to this later.
@@ -164,14 +168,14 @@ fn write_path(out: &mut String, path: &[PathElem]) {
for elem in path.iter() {
match elem {
- Field(name) => write!(out, ".{}", name),
+ Field(name) => write!(out, ".{name}"),
EnumTag => write!(out, ".<enum-tag>"),
- Variant(name) => write!(out, ".<enum-variant({})>", name),
+ 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),
+ 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,
@@ -294,7 +298,13 @@ impl<'rt, 'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> ValidityVisitor<'rt, 'mir, '
Ok(try_validation!(
self.ecx.read_immediate(op),
self.path,
- InvalidUninitBytes(None) => Uninit { expected }
+ Ub(InvalidUninitBytes(None)) =>
+ Uninit { expected },
+ // The `Unsup` cases can only occur during CTFE
+ Unsup(ReadPointerAsInt(_)) =>
+ PointerAsInt { expected },
+ Unsup(ReadPartialPointer(_)) =>
+ PartialPointer,
))
}
@@ -319,8 +329,8 @@ impl<'rt, 'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> ValidityVisitor<'rt, 'mir, '
let (_ty, _trait) = try_validation!(
self.ecx.get_ptr_vtable(vtable),
self.path,
- DanglingIntPointer(..) |
- InvalidVTablePointer(..) => InvalidVTablePtr { value: format!("{vtable}") }
+ Ub(DanglingIntPointer(..) | InvalidVTablePointer(..)) =>
+ InvalidVTablePtr { value: format!("{vtable}") }
);
// FIXME: check if the type/trait match what ty::Dynamic says?
}
@@ -345,6 +355,7 @@ impl<'rt, 'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> ValidityVisitor<'rt, 'mir, '
value: &OpTy<'tcx, M::Provenance>,
ptr_kind: PointerKind,
) -> InterpResult<'tcx> {
+ // Not using `deref_pointer` since we do the dereferenceable check ourselves below.
let place = self.ecx.ref_to_mplace(&self.read_immediate(value, ptr_kind.into())?)?;
// Handle wide pointers.
// Check metadata early, for better diagnostics
@@ -355,7 +366,7 @@ impl<'rt, 'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> ValidityVisitor<'rt, 'mir, '
let size_and_align = try_validation!(
self.ecx.size_and_align_of_mplace(&place),
self.path,
- InvalidMeta(msg) => match msg {
+ Ub(InvalidMeta(msg)) => match msg {
InvalidMetaKind::SliceTooBig => InvalidMetaSliceTooLarge { ptr_kind },
InvalidMetaKind::TooBig => InvalidMetaTooLarge { ptr_kind },
}
@@ -374,23 +385,23 @@ impl<'rt, 'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> ValidityVisitor<'rt, 'mir, '
CheckInAllocMsg::InboundsTest, // will anyway be replaced by validity message
),
self.path,
- AlignmentCheckFailed { required, has } => UnalignedPtr {
+ Ub(AlignmentCheckFailed { required, has }) => UnalignedPtr {
ptr_kind,
required_bytes: required.bytes(),
found_bytes: has.bytes()
},
- DanglingIntPointer(0, _) => NullPtr { ptr_kind },
- DanglingIntPointer(i, _) => DanglingPtrNoProvenance {
+ Ub(DanglingIntPointer(0, _)) => NullPtr { ptr_kind },
+ Ub(DanglingIntPointer(i, _)) => DanglingPtrNoProvenance {
ptr_kind,
// FIXME this says "null pointer" when null but we need translate
- pointer: format!("{}", Pointer::<Option<AllocId>>::from_addr_invalid(i))
+ pointer: format!("{}", Pointer::<Option<AllocId>>::from_addr_invalid(*i))
},
- PointerOutOfBounds { .. } => DanglingPtrOutOfBounds {
+ Ub(PointerOutOfBounds { .. }) => DanglingPtrOutOfBounds {
ptr_kind
},
// This cannot happen during const-eval (because interning already detects
// dangling pointers), but it can happen in Miri.
- PointerUseAfterFree(..) => DanglingPtrUseAfterFree {
+ Ub(PointerUseAfterFree(..)) => DanglingPtrUseAfterFree {
ptr_kind,
},
);
@@ -462,6 +473,8 @@ impl<'rt, 'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> ValidityVisitor<'rt, 'mir, '
/// 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: &OpTy<'tcx, M::Provenance>,
@@ -474,7 +487,7 @@ impl<'rt, 'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> ValidityVisitor<'rt, 'mir, '
try_validation!(
value.to_bool(),
self.path,
- InvalidBool(..) => ValidationErrorKind::InvalidBool {
+ Ub(InvalidBool(..)) => ValidationErrorKind::InvalidBool {
value: format!("{value:x}"),
}
);
@@ -485,7 +498,7 @@ impl<'rt, 'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> ValidityVisitor<'rt, 'mir, '
try_validation!(
value.to_char(),
self.path,
- InvalidChar(..) => ValidationErrorKind::InvalidChar {
+ Ub(InvalidChar(..)) => ValidationErrorKind::InvalidChar {
value: format!("{value:x}"),
}
);
@@ -494,7 +507,7 @@ impl<'rt, 'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> ValidityVisitor<'rt, 'mir, '
ty::Float(_) | ty::Int(_) | ty::Uint(_) => {
// NOTE: Keep this in sync with the array optimization for int/float
// types below!
- let value = self.read_scalar(
+ self.read_scalar(
value,
if matches!(ty.kind(), ty::Float(..)) {
ExpectedKind::Float
@@ -502,20 +515,9 @@ impl<'rt, 'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> ValidityVisitor<'rt, 'mir, '
ExpectedKind::Int
},
)?;
- // As a special exception we *do* match on a `Scalar` here, since we truly want
- // to know its underlying representation (and *not* cast it to an integer).
- if matches!(value, Scalar::Ptr(..)) {
- throw_validation_failure!(
- self.path,
- ExpectedNonPtr { value: format!("{value:x}") }
- )
- }
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 =
self.ecx.ref_to_mplace(&self.read_immediate(value, ExpectedKind::RawPtr)?)?;
if place.layout.is_unsized() {
@@ -546,10 +548,8 @@ impl<'rt, 'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> ValidityVisitor<'rt, 'mir, '
let _fn = try_validation!(
self.ecx.get_ptr_fn(ptr),
self.path,
- DanglingIntPointer(..) |
- InvalidFunctionPointer(..) => InvalidFnPtr {
- value: format!("{ptr}"),
- },
+ Ub(DanglingIntPointer(..) | InvalidFunctionPointer(..)) =>
+ InvalidFnPtr { value: format!("{ptr}") },
);
// FIXME: Check if the signature matches
} else {
@@ -657,13 +657,13 @@ impl<'rt, 'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> ValueVisitor<'mir, 'tcx, M>
Ok(try_validation!(
this.ecx.read_discriminant(op),
this.path,
- InvalidTag(val) => InvalidEnumTag {
+ Ub(InvalidTag(val)) => InvalidEnumTag {
value: format!("{val:x}"),
},
-
- InvalidUninitBytes(None) => UninitEnumTag,
- )
- .1)
+ Ub(UninhabitedEnumVariantRead(_)) => UninhabitedEnumVariant,
+ // Uninit / bad provenance are not possible since the field was already previously
+ // checked at its integer type.
+ ))
})
}
@@ -733,60 +733,7 @@ impl<'rt, 'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> ValueVisitor<'mir, 'tcx, M>
}
}
- // 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 => {
- let ty = op.layout.ty;
- throw_validation_failure!(self.path, UninhabitedVal { ty });
- }
- Abi::Scalar(scalar_layout) => {
- if !scalar_layout.is_uninit_valid() {
- // There is something to check here.
- let scalar = self.read_scalar(op, ExpectedKind::InitScalar)?;
- self.visit_scalar(scalar, scalar_layout)?;
- }
- }
- Abi::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() {
- let (a, b) =
- self.read_immediate(op, ExpectedKind::InitScalar)?.to_scalar_pair();
- self.visit_scalar(a, a_layout)?;
- self.visit_scalar(b, b_layout)?;
- }
- }
- Abi::Vector { .. } => {
- // 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.)
- }
- Abi::Aggregate { .. } => {
- // Nothing to do.
- }
- }
-
- Ok(())
- }
-
- fn visit_aggregate(
- &mut self,
- op: &OpTy<'tcx, M::Provenance>,
- fields: impl Iterator<Item = InterpResult<'tcx, Self::V>>,
- ) -> InterpResult<'tcx> {
+ // Recursively walk the value at its type. Apply optimizations for some large types.
match op.layout.ty.kind() {
ty::Str => {
let mplace = op.assert_mem_place(); // strings are unsized and hence never immediate
@@ -794,7 +741,8 @@ impl<'rt, 'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> ValueVisitor<'mir, 'tcx, M>
try_validation!(
self.ecx.read_bytes_ptr_strip_provenance(mplace.ptr, Size::from_bytes(len)),
self.path,
- InvalidUninitBytes(..) => { UninitStr },
+ Ub(InvalidUninitBytes(..)) => Uninit { expected: ExpectedKind::Str },
+ Unsup(ReadPointerAsInt(_)) => PointerAsInt { expected: ExpectedKind::Str }
);
}
ty::Array(tys, ..) | ty::Slice(tys)
@@ -806,6 +754,7 @@ impl<'rt, 'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> ValueVisitor<'mir, 'tcx, M>
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.
@@ -824,7 +773,7 @@ impl<'rt, 'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> ValueVisitor<'mir, 'tcx, M>
Left(mplace) => mplace,
Right(imm) => match *imm {
Immediate::Uninit =>
- throw_validation_failure!(self.path, UninitVal),
+ throw_validation_failure!(self.path, Uninit { expected }),
Immediate::Scalar(..) | Immediate::ScalarPair(..) =>
bug!("arrays/slices can never have Scalar/ScalarPair layout"),
}
@@ -850,17 +799,21 @@ impl<'rt, 'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> ValueVisitor<'mir, 'tcx, M>
// 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)))) => {
+ 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.uninit.start.bytes() / layout.size.bytes(),
+ access.bad.start.bytes() / layout.size.bytes(),
)
.unwrap();
self.path.push(PathElem::ArrayElem(i));
- throw_validation_failure!(self.path, UninitVal)
+ if matches!(err.kind(), Ub(InvalidUninitBytes(_))) {
+ throw_validation_failure!(self.path, Uninit { expected })
+ } else {
+ throw_validation_failure!(self.path, PointerAsInt { expected })
+ }
}
// Propagate upwards (that will also check for unexpected errors).
@@ -874,12 +827,58 @@ impl<'rt, 'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> ValueVisitor<'mir, 'tcx, M>
// 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))?
+ if op.len(self.ecx)? > 0 {
+ self.visit_field(op, 0, &self.ecx.project_index(op, 0)?)?;
+ }
}
_ => {
- self.walk_aggregate(op, fields)? // default handler
+ self.walk_value(op)?; // default handler
+ }
+ }
+
+ // *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 => {
+ let ty = op.layout.ty;
+ throw_validation_failure!(self.path, UninhabitedVal { ty });
+ }
+ Abi::Scalar(scalar_layout) => {
+ if !scalar_layout.is_uninit_valid() {
+ // There is something to check here.
+ let scalar = self.read_scalar(op, ExpectedKind::InitScalar)?;
+ self.visit_scalar(scalar, scalar_layout)?;
+ }
+ }
+ Abi::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() {
+ let (a, b) =
+ self.read_immediate(op, ExpectedKind::InitScalar)?.to_scalar_pair();
+ self.visit_scalar(a, a_layout)?;
+ self.visit_scalar(b, b_layout)?;
+ }
+ }
+ Abi::Vector { .. } => {
+ // 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.)
+ }
+ Abi::Aggregate { .. } => {
+ // Nothing to do.
}
}
+
Ok(())
}
}
@@ -900,17 +899,22 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
// Run it.
match visitor.visit_value(&op) {
Ok(()) => Ok(()),
- // Pass through validation failures.
- Err(err) if matches!(err.kind(), err_ub!(Validation { .. })) => Err(err),
- // Complain about any other kind of UB error -- those are bad because we'd like to
+ // Pass through validation failures and "invalid program" issues.
+ Err(err)
+ if matches!(
+ err.kind(),
+ err_ub!(ValidationError { .. }) | InterpError::InvalidProgram(_)
+ ) =>
+ {
+ Err(err)
+ }
+ // Complain about any other kind of error -- those are bad because we'd like to
// report them in a way that shows *where* in the value the issue lies.
- Err(err) if matches!(err.kind(), InterpError::UndefinedBehavior(_)) => {
+ Err(err) => {
let (err, backtrace) = err.into_parts();
backtrace.print_backtrace();
bug!("Unexpected Undefined Behavior error during validation: {err:?}");
}
- // Pass through everything else.
- Err(err) => Err(err),
}
}