support non-null pointer niches in CTFE
This commit is contained in:
parent
3c05276866
commit
76c49aead6
8 changed files with 100 additions and 65 deletions
|
@ -1006,6 +1006,43 @@ impl WrappingRange {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns `true` if `range` is contained in `self`.
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn contains_range<I: Into<u128> + Ord>(&self, range: RangeInclusive<I>) -> bool {
|
||||||
|
if range.is_empty() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
let (vmin, vmax) = range.into_inner();
|
||||||
|
let (vmin, vmax) = (vmin.into(), vmax.into());
|
||||||
|
|
||||||
|
if self.start <= self.end {
|
||||||
|
self.start <= vmin && vmax <= self.end
|
||||||
|
} else {
|
||||||
|
// The last check is needed to cover the following case:
|
||||||
|
// `vmin ... start, end ... vmax`. In this special case there is no gap
|
||||||
|
// between `start` and `end` so we must return true.
|
||||||
|
self.start <= vmin || vmax <= self.end || self.start == self.end + 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns `true` if `range` has an overlap with `self`.
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn overlaps_range<I: Into<u128> + Ord>(&self, range: RangeInclusive<I>) -> bool {
|
||||||
|
if range.is_empty() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
let (vmin, vmax) = range.into_inner();
|
||||||
|
let (vmin, vmax) = (vmin.into(), vmax.into());
|
||||||
|
|
||||||
|
if self.start <= self.end {
|
||||||
|
self.start <= vmax && vmin <= self.end
|
||||||
|
} else {
|
||||||
|
self.start <= vmax || vmin <= self.end
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Returns `self` with replaced `start`
|
/// Returns `self` with replaced `start`
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub fn with_start(mut self, start: u128) -> Self {
|
pub fn with_start(mut self, start: u128) -> Self {
|
||||||
|
|
|
@ -244,7 +244,6 @@ const_eval_not_enough_caller_args =
|
||||||
const_eval_null_box = {$front_matter}: encountered a null box
|
const_eval_null_box = {$front_matter}: encountered a null box
|
||||||
const_eval_null_fn_ptr = {$front_matter}: encountered a null function pointer
|
const_eval_null_fn_ptr = {$front_matter}: encountered a null function pointer
|
||||||
const_eval_null_ref = {$front_matter}: encountered a null reference
|
const_eval_null_ref = {$front_matter}: encountered a null reference
|
||||||
const_eval_nullable_ptr_out_of_range = {$front_matter}: encountered a potentially null pointer, but expected something that cannot possibly fail to be {$in_range}
|
|
||||||
const_eval_nullary_intrinsic_fail =
|
const_eval_nullary_intrinsic_fail =
|
||||||
could not evaluate nullary intrinsic
|
could not evaluate nullary intrinsic
|
||||||
|
|
||||||
|
|
|
@ -333,7 +333,7 @@ impl<'mir, 'tcx: 'mir> CompileTimeEvalContext<'mir, 'tcx> {
|
||||||
// Inequality with integers other than null can never be known for sure.
|
// Inequality with integers other than null can never be known for sure.
|
||||||
(Scalar::Int(int), ptr @ Scalar::Ptr(..))
|
(Scalar::Int(int), ptr @ Scalar::Ptr(..))
|
||||||
| (ptr @ Scalar::Ptr(..), Scalar::Int(int))
|
| (ptr @ Scalar::Ptr(..), Scalar::Int(int))
|
||||||
if int.is_null() && !self.scalar_may_be_null(ptr)? =>
|
if int.is_null() && !self.ptr_scalar_range(ptr)?.contains(&0) =>
|
||||||
{
|
{
|
||||||
0
|
0
|
||||||
}
|
}
|
||||||
|
|
|
@ -617,7 +617,6 @@ impl<'tcx> ReportErrorExt for ValidationErrorInfo<'tcx> {
|
||||||
MutableRefInConst => const_eval_mutable_ref_in_const,
|
MutableRefInConst => const_eval_mutable_ref_in_const,
|
||||||
NullFnPtr => const_eval_null_fn_ptr,
|
NullFnPtr => const_eval_null_fn_ptr,
|
||||||
NeverVal => const_eval_never_val,
|
NeverVal => const_eval_never_val,
|
||||||
NullablePtrOutOfRange { .. } => const_eval_nullable_ptr_out_of_range,
|
|
||||||
PtrOutOfRange { .. } => const_eval_ptr_out_of_range,
|
PtrOutOfRange { .. } => const_eval_ptr_out_of_range,
|
||||||
OutOfRange { .. } => const_eval_out_of_range,
|
OutOfRange { .. } => const_eval_out_of_range,
|
||||||
UnsafeCell => const_eval_unsafe_cell,
|
UnsafeCell => const_eval_unsafe_cell,
|
||||||
|
@ -732,9 +731,7 @@ impl<'tcx> ReportErrorExt for ValidationErrorInfo<'tcx> {
|
||||||
| InvalidFnPtr { value } => {
|
| InvalidFnPtr { value } => {
|
||||||
err.set_arg("value", value);
|
err.set_arg("value", value);
|
||||||
}
|
}
|
||||||
NullablePtrOutOfRange { range, max_value } | PtrOutOfRange { range, max_value } => {
|
PtrOutOfRange { range, max_value } => add_range_arg(range, max_value, handler, err),
|
||||||
add_range_arg(range, max_value, handler, err)
|
|
||||||
}
|
|
||||||
OutOfRange { range, max_value, value } => {
|
OutOfRange { range, max_value, value } => {
|
||||||
err.set_arg("value", value);
|
err.set_arg("value", value);
|
||||||
add_range_arg(range, max_value, handler, err);
|
add_range_arg(range, max_value, handler, err);
|
||||||
|
|
|
@ -2,8 +2,7 @@
|
||||||
|
|
||||||
use rustc_middle::ty::layout::{LayoutOf, PrimitiveExt};
|
use rustc_middle::ty::layout::{LayoutOf, PrimitiveExt};
|
||||||
use rustc_middle::{mir, ty};
|
use rustc_middle::{mir, ty};
|
||||||
use rustc_target::abi::{self, TagEncoding};
|
use rustc_target::abi::{self, TagEncoding, VariantIdx, Variants, WrappingRange};
|
||||||
use rustc_target::abi::{VariantIdx, Variants};
|
|
||||||
|
|
||||||
use super::{ImmTy, InterpCx, InterpResult, Machine, OpTy, PlaceTy, Scalar};
|
use super::{ImmTy, InterpCx, InterpResult, Machine, OpTy, PlaceTy, Scalar};
|
||||||
|
|
||||||
|
@ -180,19 +179,24 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
|
||||||
// discriminant (encoded in niche/tag) and variant index are the same.
|
// discriminant (encoded in niche/tag) and variant index are the same.
|
||||||
let variants_start = niche_variants.start().as_u32();
|
let variants_start = niche_variants.start().as_u32();
|
||||||
let variants_end = niche_variants.end().as_u32();
|
let variants_end = niche_variants.end().as_u32();
|
||||||
|
let variants_len = u128::from(variants_end - variants_start);
|
||||||
let variant = match tag_val.try_to_int() {
|
let variant = match tag_val.try_to_int() {
|
||||||
Err(dbg_val) => {
|
Err(dbg_val) => {
|
||||||
// So this is a pointer then, and casting to an int failed.
|
// So this is a pointer then, and casting to an int failed.
|
||||||
// Can only happen during CTFE.
|
// Can only happen during CTFE.
|
||||||
// The niche must be just 0, and the ptr not null, then we know this is
|
// The pointer and niches ranges must be disjoint, then we know
|
||||||
// okay. Everything else, we conservatively reject.
|
// this is the untagged variant (as the value is not in the niche).
|
||||||
let ptr_valid = niche_start == 0
|
// Everything else, we conservatively reject.
|
||||||
&& variants_start == variants_end
|
let range = self.ptr_scalar_range(tag_val)?;
|
||||||
&& !self.scalar_may_be_null(tag_val)?;
|
let niches = WrappingRange {
|
||||||
if !ptr_valid {
|
start: niche_start,
|
||||||
|
end: niche_start.wrapping_add(variants_len),
|
||||||
|
};
|
||||||
|
if niches.overlaps_range(range) {
|
||||||
throw_ub!(InvalidTag(dbg_val))
|
throw_ub!(InvalidTag(dbg_val))
|
||||||
|
} else {
|
||||||
|
untagged_variant
|
||||||
}
|
}
|
||||||
untagged_variant
|
|
||||||
}
|
}
|
||||||
Ok(tag_bits) => {
|
Ok(tag_bits) => {
|
||||||
let tag_bits = tag_bits.assert_bits(tag_layout.size);
|
let tag_bits = tag_bits.assert_bits(tag_layout.size);
|
||||||
|
@ -205,7 +209,7 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
|
||||||
let variant_index_relative =
|
let variant_index_relative =
|
||||||
variant_index_relative_val.to_scalar().assert_bits(tag_val.layout.size);
|
variant_index_relative_val.to_scalar().assert_bits(tag_val.layout.size);
|
||||||
// Check if this is in the range that indicates an actual discriminant.
|
// Check if this is in the range that indicates an actual discriminant.
|
||||||
if variant_index_relative <= u128::from(variants_end - variants_start) {
|
if variant_index_relative <= variants_len {
|
||||||
let variant_index_relative = u32::try_from(variant_index_relative)
|
let variant_index_relative = u32::try_from(variant_index_relative)
|
||||||
.expect("we checked that this fits into a u32");
|
.expect("we checked that this fits into a u32");
|
||||||
// Then computing the absolute variant idx should not overflow any more.
|
// Then computing the absolute variant idx should not overflow any more.
|
||||||
|
|
|
@ -10,6 +10,7 @@ use std::assert_matches::assert_matches;
|
||||||
use std::borrow::Cow;
|
use std::borrow::Cow;
|
||||||
use std::collections::VecDeque;
|
use std::collections::VecDeque;
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
|
use std::ops::RangeInclusive;
|
||||||
use std::ptr;
|
use std::ptr;
|
||||||
|
|
||||||
use rustc_ast::Mutability;
|
use rustc_ast::Mutability;
|
||||||
|
@ -1222,24 +1223,34 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
|
||||||
|
|
||||||
/// Machine pointer introspection.
|
/// Machine pointer introspection.
|
||||||
impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
|
impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
|
||||||
/// Test if this value might be null.
|
/// Turn a pointer-sized scalar into a (non-empty) range of possible values.
|
||||||
/// If the machine does not support ptr-to-int casts, this is conservative.
|
/// If the machine does not support ptr-to-int casts, this is conservative.
|
||||||
pub fn scalar_may_be_null(&self, scalar: Scalar<M::Provenance>) -> InterpResult<'tcx, bool> {
|
pub fn ptr_scalar_range(
|
||||||
Ok(match scalar.try_to_int() {
|
&self,
|
||||||
Ok(int) => int.is_null(),
|
scalar: Scalar<M::Provenance>,
|
||||||
Err(_) => {
|
) -> InterpResult<'tcx, RangeInclusive<u64>> {
|
||||||
// Can only happen during CTFE.
|
if let Ok(int) = scalar.to_target_usize(self) {
|
||||||
let ptr = scalar.to_pointer(self)?;
|
return Ok(int..=int);
|
||||||
match self.ptr_try_get_alloc_id(ptr) {
|
}
|
||||||
Ok((alloc_id, offset, _)) => {
|
|
||||||
let (size, _align, _kind) = self.get_alloc_info(alloc_id);
|
let ptr = scalar.to_pointer(self)?;
|
||||||
// If the pointer is out-of-bounds, it may be null.
|
|
||||||
// Note that one-past-the-end (offset == size) is still inbounds, and never null.
|
// Can only happen during CTFE.
|
||||||
offset > size
|
Ok(match self.ptr_try_get_alloc_id(ptr) {
|
||||||
}
|
Ok((alloc_id, offset, _)) => {
|
||||||
Err(_offset) => bug!("a non-int scalar is always a pointer"),
|
let offset = offset.bytes();
|
||||||
|
let (size, align, _) = self.get_alloc_info(alloc_id);
|
||||||
|
let dl = self.data_layout();
|
||||||
|
if offset > size.bytes() {
|
||||||
|
// If the pointer is out-of-bounds, we do not have a
|
||||||
|
// meaningful range to return.
|
||||||
|
0..=dl.max_address()
|
||||||
|
} else {
|
||||||
|
let (min, max) = dl.address_range_for(size, align);
|
||||||
|
(min + offset)..=(max + offset)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Err(_offset) => bug!("a non-int scalar is always a pointer"),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -19,9 +19,7 @@ use rustc_middle::mir::interpret::{
|
||||||
use rustc_middle::ty;
|
use rustc_middle::ty;
|
||||||
use rustc_middle::ty::layout::{LayoutOf, TyAndLayout};
|
use rustc_middle::ty::layout::{LayoutOf, TyAndLayout};
|
||||||
use rustc_span::symbol::{sym, Symbol};
|
use rustc_span::symbol::{sym, Symbol};
|
||||||
use rustc_target::abi::{
|
use rustc_target::abi::{Abi, FieldIdx, Scalar as ScalarAbi, Size, VariantIdx, Variants};
|
||||||
Abi, FieldIdx, Scalar as ScalarAbi, Size, VariantIdx, Variants, WrappingRange,
|
|
||||||
};
|
|
||||||
|
|
||||||
use std::hash::Hash;
|
use std::hash::Hash;
|
||||||
|
|
||||||
|
@ -554,7 +552,7 @@ impl<'rt, 'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> ValidityVisitor<'rt, 'mir, '
|
||||||
// FIXME: Check if the signature matches
|
// FIXME: Check if the signature matches
|
||||||
} else {
|
} else {
|
||||||
// Otherwise (for standalone Miri), we have to still check it to be non-null.
|
// Otherwise (for standalone Miri), we have to still check it to be non-null.
|
||||||
if self.ecx.scalar_may_be_null(value)? {
|
if self.ecx.ptr_scalar_range(value)?.contains(&0) {
|
||||||
throw_validation_failure!(self.path, NullFnPtr);
|
throw_validation_failure!(self.path, NullFnPtr);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -595,46 +593,36 @@ impl<'rt, 'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> ValidityVisitor<'rt, 'mir, '
|
||||||
) -> InterpResult<'tcx> {
|
) -> InterpResult<'tcx> {
|
||||||
let size = scalar_layout.size(self.ecx);
|
let size = scalar_layout.size(self.ecx);
|
||||||
let valid_range = scalar_layout.valid_range(self.ecx);
|
let valid_range = scalar_layout.valid_range(self.ecx);
|
||||||
let WrappingRange { start, end } = valid_range;
|
|
||||||
let max_value = size.unsigned_int_max();
|
let max_value = size.unsigned_int_max();
|
||||||
assert!(end <= max_value);
|
assert!(valid_range.end <= max_value);
|
||||||
let bits = match scalar.try_to_int() {
|
match scalar.try_to_int() {
|
||||||
Ok(int) => int.assert_bits(size),
|
Ok(int) => {
|
||||||
|
// We have an explicit int: check it against the valid range.
|
||||||
|
let bits = int.assert_bits(size);
|
||||||
|
if valid_range.contains(bits) {
|
||||||
|
Ok(())
|
||||||
|
} else {
|
||||||
|
throw_validation_failure!(
|
||||||
|
self.path,
|
||||||
|
OutOfRange { value: format!("{bits}"), range: valid_range, max_value }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
Err(_) => {
|
Err(_) => {
|
||||||
// So this is a pointer then, and casting to an int failed.
|
// So this is a pointer then, and casting to an int failed.
|
||||||
// Can only happen during CTFE.
|
// Can only happen during CTFE.
|
||||||
// We support 2 kinds of ranges here: full range, and excluding zero.
|
// We check if the possible addresses are compatible with the valid range.
|
||||||
if start == 1 && end == max_value {
|
let range = self.ecx.ptr_scalar_range(scalar)?;
|
||||||
// Only null is the niche. So make sure the ptr is NOT null.
|
if valid_range.contains_range(range) {
|
||||||
if self.ecx.scalar_may_be_null(scalar)? {
|
Ok(())
|
||||||
throw_validation_failure!(
|
|
||||||
self.path,
|
|
||||||
NullablePtrOutOfRange { range: valid_range, max_value }
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
} else if scalar_layout.is_always_valid(self.ecx) {
|
|
||||||
// Easy. (This is reachable if `enforce_number_validity` is set.)
|
|
||||||
return Ok(());
|
|
||||||
} else {
|
} else {
|
||||||
// Conservatively, we reject, because the pointer *could* have a bad
|
// Reject conservatively, because the pointer *could* have a bad value.
|
||||||
// value.
|
|
||||||
throw_validation_failure!(
|
throw_validation_failure!(
|
||||||
self.path,
|
self.path,
|
||||||
PtrOutOfRange { range: valid_range, max_value }
|
PtrOutOfRange { range: valid_range, max_value }
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
|
||||||
// Now compare.
|
|
||||||
if valid_range.contains(bits) {
|
|
||||||
Ok(())
|
|
||||||
} else {
|
|
||||||
throw_validation_failure!(
|
|
||||||
self.path,
|
|
||||||
OutOfRange { value: format!("{bits}"), range: valid_range, max_value }
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -388,7 +388,6 @@ pub enum ValidationErrorKind<'tcx> {
|
||||||
MutableRefInConst,
|
MutableRefInConst,
|
||||||
NullFnPtr,
|
NullFnPtr,
|
||||||
NeverVal,
|
NeverVal,
|
||||||
NullablePtrOutOfRange { range: WrappingRange, max_value: u128 },
|
|
||||||
PtrOutOfRange { range: WrappingRange, max_value: u128 },
|
PtrOutOfRange { range: WrappingRange, max_value: u128 },
|
||||||
OutOfRange { value: String, range: WrappingRange, max_value: u128 },
|
OutOfRange { value: String, range: WrappingRange, max_value: u128 },
|
||||||
UnsafeCell,
|
UnsafeCell,
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue