make read_immediate error immediately on uninit, so ImmTy can carry initialized Scalar
This commit is contained in:
parent
2e52fe01cf
commit
30fa931f92
51 changed files with 491 additions and 747 deletions
|
@ -20,8 +20,8 @@ use rustc_target::abi::{Abi, Scalar as ScalarAbi, Size, VariantIdx, Variants, Wr
|
|||
use std::hash::Hash;
|
||||
|
||||
use super::{
|
||||
alloc_range, CheckInAllocMsg, GlobalAlloc, Immediate, InterpCx, InterpResult, MPlaceTy,
|
||||
Machine, MemPlaceMeta, OpTy, Scalar, ScalarMaybeUninit, ValueVisitor,
|
||||
alloc_range, CheckInAllocMsg, GlobalAlloc, ImmTy, Immediate, InterpCx, InterpResult, MPlaceTy,
|
||||
Machine, MemPlaceMeta, OpTy, Scalar, ValueVisitor,
|
||||
};
|
||||
|
||||
macro_rules! throw_validation_failure {
|
||||
|
@ -304,6 +304,27 @@ impl<'rt, 'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> ValidityVisitor<'rt, 'mir, '
|
|||
Ok(r)
|
||||
}
|
||||
|
||||
fn read_immediate(
|
||||
&self,
|
||||
op: &OpTy<'tcx, M::Provenance>,
|
||||
expected: &str,
|
||||
) -> InterpResult<'tcx, ImmTy<'tcx, M::Provenance>> {
|
||||
Ok(try_validation!(
|
||||
self.ecx.read_immediate(op),
|
||||
self.path,
|
||||
err_unsup!(ReadPointerAsBytes) => { "(potentially part of) a pointer" } expected { "{expected}" },
|
||||
err_ub!(InvalidUninitBytes(None)) => { "uninitialized memory" } expected { "{expected}" }
|
||||
))
|
||||
}
|
||||
|
||||
fn read_scalar(
|
||||
&self,
|
||||
op: &OpTy<'tcx, M::Provenance>,
|
||||
expected: &str,
|
||||
) -> InterpResult<'tcx, Scalar<M::Provenance>> {
|
||||
Ok(self.read_immediate(op, expected)?.to_scalar())
|
||||
}
|
||||
|
||||
fn check_wide_ptr_meta(
|
||||
&mut self,
|
||||
meta: MemPlaceMeta<M::Provenance>,
|
||||
|
@ -348,18 +369,9 @@ impl<'rt, 'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> ValidityVisitor<'rt, 'mir, '
|
|||
value: &OpTy<'tcx, M::Provenance>,
|
||||
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" },
|
||||
);
|
||||
let place = self.ecx.ref_to_mplace(&self.read_immediate(value, &format!("a {kind}"))?)?;
|
||||
// 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)?;
|
||||
}
|
||||
|
@ -454,28 +466,6 @@ impl<'rt, 'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> ValidityVisitor<'rt, 'mir, '
|
|||
Ok(())
|
||||
}
|
||||
|
||||
fn read_scalar(
|
||||
&self,
|
||||
op: &OpTy<'tcx, M::Provenance>,
|
||||
) -> InterpResult<'tcx, ScalarMaybeUninit<M::Provenance>> {
|
||||
Ok(try_validation!(
|
||||
self.ecx.read_scalar(op),
|
||||
self.path,
|
||||
err_unsup!(ReadPointerAsBytes) => { "(potentially part of) a pointer" } expected { "plain (non-pointer) bytes" },
|
||||
))
|
||||
}
|
||||
|
||||
fn read_immediate_forced(
|
||||
&self,
|
||||
op: &OpTy<'tcx, M::Provenance>,
|
||||
) -> InterpResult<'tcx, Immediate<M::Provenance>> {
|
||||
Ok(*try_validation!(
|
||||
self.ecx.read_immediate_raw(op, /*force*/ true),
|
||||
self.path,
|
||||
err_unsup!(ReadPointerAsBytes) => { "(potentially part of) a pointer" } expected { "plain (non-pointer) bytes" },
|
||||
).unwrap())
|
||||
}
|
||||
|
||||
/// 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(
|
||||
|
@ -486,39 +476,39 @@ impl<'rt, 'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> ValidityVisitor<'rt, 'mir, '
|
|||
let ty = value.layout.ty;
|
||||
match ty.kind() {
|
||||
ty::Bool => {
|
||||
let value = self.read_scalar(value)?;
|
||||
let value = self.read_scalar(value, "a boolean")?;
|
||||
try_validation!(
|
||||
value.to_bool(),
|
||||
self.path,
|
||||
err_ub!(InvalidBool(..)) | err_ub!(InvalidUninitBytes(None)) =>
|
||||
err_ub!(InvalidBool(..)) =>
|
||||
{ "{:x}", value } expected { "a boolean" },
|
||||
);
|
||||
Ok(true)
|
||||
}
|
||||
ty::Char => {
|
||||
let value = self.read_scalar(value)?;
|
||||
let value = self.read_scalar(value, "a unicode scalar value")?;
|
||||
try_validation!(
|
||||
value.to_char(),
|
||||
self.path,
|
||||
err_ub!(InvalidChar(..)) | err_ub!(InvalidUninitBytes(None)) =>
|
||||
err_ub!(InvalidChar(..)) =>
|
||||
{ "{:x}", 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!
|
||||
try_validation!(
|
||||
value.check_init(),
|
||||
self.path,
|
||||
err_ub!(InvalidUninitBytes(..)) =>
|
||||
{ "{:x}", value } expected { "initialized bytes" }
|
||||
);
|
||||
let value = self.read_scalar(
|
||||
value,
|
||||
if matches!(ty.kind(), ty::Float(..)) {
|
||||
"a floating point number"
|
||||
} else {
|
||||
"an integer"
|
||||
},
|
||||
)?;
|
||||
// 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).
|
||||
let is_ptr = value.check_init().map_or(false, |v| matches!(v, Scalar::Ptr(..)));
|
||||
if is_ptr {
|
||||
if matches!(value, Scalar::Ptr(..)) {
|
||||
throw_validation_failure!(self.path,
|
||||
{ "{:x}", value } expected { "plain (non-pointer) bytes" }
|
||||
)
|
||||
|
@ -529,12 +519,8 @@ impl<'rt, 'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> ValidityVisitor<'rt, 'mir, '
|
|||
// 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" },
|
||||
);
|
||||
let place =
|
||||
self.ecx.ref_to_mplace(&self.read_immediate(value, "a raw pointer")?)?;
|
||||
if place.layout.is_unsized() {
|
||||
self.check_wide_ptr_meta(place.meta, place.layout)?;
|
||||
}
|
||||
|
@ -555,12 +541,7 @@ impl<'rt, 'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> ValidityVisitor<'rt, 'mir, '
|
|||
Ok(true)
|
||||
}
|
||||
ty::FnPtr(_sig) => {
|
||||
let value = try_validation!(
|
||||
self.ecx.read_scalar(value).and_then(|v| v.check_init()),
|
||||
self.path,
|
||||
err_unsup!(ReadPointerAsBytes) => { "part of a pointer" } expected { "a proper pointer or integer value" },
|
||||
err_ub!(InvalidUninitBytes(None)) => { "uninitialized bytes" } expected { "a proper pointer or integer value" },
|
||||
);
|
||||
let value = self.read_scalar(value, "a function pointer")?;
|
||||
|
||||
// If we check references recursively, also check that this points to a function.
|
||||
if let Some(_) = self.ref_tracking {
|
||||
|
@ -611,34 +592,15 @@ impl<'rt, 'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> ValidityVisitor<'rt, 'mir, '
|
|||
|
||||
fn visit_scalar(
|
||||
&mut self,
|
||||
scalar: ScalarMaybeUninit<M::Provenance>,
|
||||
scalar: Scalar<M::Provenance>,
|
||||
scalar_layout: ScalarAbi,
|
||||
) -> InterpResult<'tcx> {
|
||||
// We check `is_full_range` in a slightly complicated way because *if* we are checking
|
||||
// number validity, then we want to ensure that `Scalar::Initialized` is indeed initialized,
|
||||
// i.e. that we go over the `check_init` below.
|
||||
let size = scalar_layout.size(self.ecx);
|
||||
let is_full_range = match scalar_layout {
|
||||
ScalarAbi::Initialized { .. } => false, // not "full" since uninit is not valid
|
||||
ScalarAbi::Union { .. } => true,
|
||||
};
|
||||
if is_full_range {
|
||||
// Nothing to check. Cruciall we don't even `read_scalar` until here, since that would
|
||||
// fail for `Union` scalars!
|
||||
return Ok(());
|
||||
}
|
||||
// We have something to check: it must at least be initialized.
|
||||
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 value = try_validation!(
|
||||
scalar.check_init(),
|
||||
self.path,
|
||||
err_ub!(InvalidUninitBytes(None)) => { "{:x}", scalar }
|
||||
expected { "something {}", wrapping_range_format(valid_range, max_value) },
|
||||
);
|
||||
let bits = match value.try_to_int() {
|
||||
let bits = match scalar.try_to_int() {
|
||||
Ok(int) => int.assert_bits(size),
|
||||
Err(_) => {
|
||||
// So this is a pointer then, and casting to an int failed.
|
||||
|
@ -646,7 +608,7 @@ impl<'rt, 'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> ValidityVisitor<'rt, 'mir, '
|
|||
// 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(value)? {
|
||||
if self.ecx.scalar_may_be_null(scalar)? {
|
||||
throw_validation_failure!(self.path,
|
||||
{ "a potentially null pointer" }
|
||||
expected {
|
||||
|
@ -800,10 +762,11 @@ impl<'rt, 'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> ValueVisitor<'mir, 'tcx, M>
|
|||
);
|
||||
}
|
||||
Abi::Scalar(scalar_layout) => {
|
||||
// We use a 'forced' read because we always need a `Immediate` here
|
||||
// and treating "partially uninit" as "fully uninit" is fine for us.
|
||||
let scalar = self.read_immediate_forced(op)?.to_scalar_or_uninit();
|
||||
self.visit_scalar(scalar, scalar_layout)?;
|
||||
if !scalar_layout.is_uninit_valid() {
|
||||
// There is something to check here.
|
||||
let scalar = self.read_scalar(op, "initiailized scalar value")?;
|
||||
self.visit_scalar(scalar, scalar_layout)?;
|
||||
}
|
||||
}
|
||||
Abi::ScalarPair(a_layout, b_layout) => {
|
||||
// There is no `rustc_layout_scalar_valid_range_start` for pairs, so
|
||||
|
@ -811,10 +774,15 @@ impl<'rt, 'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> ValueVisitor<'mir, 'tcx, M>
|
|||
// but that can miss bugs in layout computation. Layout computation
|
||||
// is subtle due to enums having ScalarPair layout, where one field
|
||||
// is the discriminant.
|
||||
if cfg!(debug_assertions) {
|
||||
// We use a 'forced' read because we always need a `Immediate` here
|
||||
// and treating "partially uninit" as "fully uninit" is fine for us.
|
||||
let (a, b) = self.read_immediate_forced(op)?.to_scalar_or_uninit_pair();
|
||||
if cfg!(debug_assertions)
|
||||
&& !a_layout.is_uninit_valid()
|
||||
&& !b_layout.is_uninit_valid()
|
||||
{
|
||||
// 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.
|
||||
let (a, b) =
|
||||
self.read_immediate(op, "initiailized scalar value")?.to_scalar_pair();
|
||||
self.visit_scalar(a, a_layout)?;
|
||||
self.visit_scalar(b, b_layout)?;
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue