support non-null pointer niches in CTFE

This commit is contained in:
Moulins 2023-06-25 19:32:40 +02:00
parent 3c05276866
commit 76c49aead6
8 changed files with 100 additions and 65 deletions

View File

@ -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`
#[inline(always)]
pub fn with_start(mut self, start: u128) -> Self {

View File

@ -244,7 +244,6 @@ const_eval_not_enough_caller_args =
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_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 =
could not evaluate nullary intrinsic

View File

@ -333,7 +333,7 @@ impl<'mir, 'tcx: 'mir> CompileTimeEvalContext<'mir, 'tcx> {
// Inequality with integers other than null can never be known for sure.
(Scalar::Int(int), ptr @ Scalar::Ptr(..))
| (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
}

View File

@ -617,7 +617,6 @@ impl<'tcx> ReportErrorExt for ValidationErrorInfo<'tcx> {
MutableRefInConst => const_eval_mutable_ref_in_const,
NullFnPtr => const_eval_null_fn_ptr,
NeverVal => const_eval_never_val,
NullablePtrOutOfRange { .. } => const_eval_nullable_ptr_out_of_range,
PtrOutOfRange { .. } => const_eval_ptr_out_of_range,
OutOfRange { .. } => const_eval_out_of_range,
UnsafeCell => const_eval_unsafe_cell,
@ -732,9 +731,7 @@ impl<'tcx> ReportErrorExt for ValidationErrorInfo<'tcx> {
| InvalidFnPtr { value } => {
err.set_arg("value", value);
}
NullablePtrOutOfRange { range, max_value } | PtrOutOfRange { range, max_value } => {
add_range_arg(range, max_value, handler, err)
}
PtrOutOfRange { range, max_value } => add_range_arg(range, max_value, handler, err),
OutOfRange { range, max_value, value } => {
err.set_arg("value", value);
add_range_arg(range, max_value, handler, err);

View File

@ -2,8 +2,7 @@
use rustc_middle::ty::layout::{LayoutOf, PrimitiveExt};
use rustc_middle::{mir, ty};
use rustc_target::abi::{self, TagEncoding};
use rustc_target::abi::{VariantIdx, Variants};
use rustc_target::abi::{self, TagEncoding, VariantIdx, Variants, WrappingRange};
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.
let variants_start = niche_variants.start().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() {
Err(dbg_val) => {
// So this is a pointer then, and casting to an int failed.
// Can only happen during CTFE.
// The niche must be just 0, and the ptr not null, then we know this is
// okay. Everything else, we conservatively reject.
let ptr_valid = niche_start == 0
&& variants_start == variants_end
&& !self.scalar_may_be_null(tag_val)?;
if !ptr_valid {
// The pointer and niches ranges must be disjoint, then we know
// this is the untagged variant (as the value is not in the niche).
// Everything else, we conservatively reject.
let range = self.ptr_scalar_range(tag_val)?;
let niches = WrappingRange {
start: niche_start,
end: niche_start.wrapping_add(variants_len),
};
if niches.overlaps_range(range) {
throw_ub!(InvalidTag(dbg_val))
} else {
untagged_variant
}
untagged_variant
}
Ok(tag_bits) => {
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 =
variant_index_relative_val.to_scalar().assert_bits(tag_val.layout.size);
// 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)
.expect("we checked that this fits into a u32");
// Then computing the absolute variant idx should not overflow any more.

View File

@ -10,6 +10,7 @@ use std::assert_matches::assert_matches;
use std::borrow::Cow;
use std::collections::VecDeque;
use std::fmt;
use std::ops::RangeInclusive;
use std::ptr;
use rustc_ast::Mutability;
@ -1222,24 +1223,34 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
/// Machine pointer introspection.
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.
pub fn scalar_may_be_null(&self, scalar: Scalar<M::Provenance>) -> InterpResult<'tcx, bool> {
Ok(match scalar.try_to_int() {
Ok(int) => int.is_null(),
Err(_) => {
// Can only happen during CTFE.
let ptr = scalar.to_pointer(self)?;
match self.ptr_try_get_alloc_id(ptr) {
Ok((alloc_id, offset, _)) => {
let (size, _align, _kind) = self.get_alloc_info(alloc_id);
// 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.
offset > size
}
Err(_offset) => bug!("a non-int scalar is always a pointer"),
pub fn ptr_scalar_range(
&self,
scalar: Scalar<M::Provenance>,
) -> InterpResult<'tcx, RangeInclusive<u64>> {
if let Ok(int) = scalar.to_target_usize(self) {
return Ok(int..=int);
}
let ptr = scalar.to_pointer(self)?;
// Can only happen during CTFE.
Ok(match self.ptr_try_get_alloc_id(ptr) {
Ok((alloc_id, offset, _)) => {
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"),
})
}

View File

@ -19,9 +19,7 @@ use rustc_middle::mir::interpret::{
use rustc_middle::ty;
use rustc_middle::ty::layout::{LayoutOf, TyAndLayout};
use rustc_span::symbol::{sym, Symbol};
use rustc_target::abi::{
Abi, FieldIdx, Scalar as ScalarAbi, Size, VariantIdx, Variants, WrappingRange,
};
use rustc_target::abi::{Abi, FieldIdx, Scalar as ScalarAbi, Size, VariantIdx, Variants};
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
} else {
// 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);
}
}
@ -595,46 +593,36 @@ impl<'rt, 'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> ValidityVisitor<'rt, 'mir, '
) -> InterpResult<'tcx> {
let size = scalar_layout.size(self.ecx);
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 bits = match scalar.try_to_int() {
Ok(int) => int.assert_bits(size),
assert!(valid_range.end <= max_value);
match scalar.try_to_int() {
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(_) => {
// So this is a pointer then, and casting to an int failed.
// Can only happen during CTFE.
// 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(scalar)? {
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(());
// We check if the possible addresses are compatible with the valid range.
let range = self.ecx.ptr_scalar_range(scalar)?;
if valid_range.contains_range(range) {
Ok(())
} else {
// Conservatively, we reject, because the pointer *could* have a bad
// value.
// Reject conservatively, because the pointer *could* have a bad value.
throw_validation_failure!(
self.path,
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 }
)
}
}
}

View File

@ -388,7 +388,6 @@ pub enum ValidationErrorKind<'tcx> {
MutableRefInConst,
NullFnPtr,
NeverVal,
NullablePtrOutOfRange { range: WrappingRange, max_value: u128 },
PtrOutOfRange { range: WrappingRange, max_value: u128 },
OutOfRange { value: String, range: WrappingRange, max_value: u128 },
UnsafeCell,