Make <*const T>::is_null
const fn
This commit is contained in:
parent
441fd22557
commit
34c3c0dae5
@ -13,6 +13,15 @@ impl<T: ?Sized> *const T {
|
|||||||
/// Therefore, two pointers that are null may still not compare equal to
|
/// Therefore, two pointers that are null may still not compare equal to
|
||||||
/// each other.
|
/// each other.
|
||||||
///
|
///
|
||||||
|
/// ## Behavior during const evaluation
|
||||||
|
///
|
||||||
|
/// When this function is used during const evaluation, it may return `false` for pointers
|
||||||
|
/// that turn out to be null at runtime. Specifically, when a pointer to some memory
|
||||||
|
/// is offset beyond its bounds in such a way that the resulting pointer is null,
|
||||||
|
/// the function will still return `false`. There is no way for CTFE to know
|
||||||
|
/// the absolute position of that memory, so we cannot tell if the pointer is
|
||||||
|
/// null or not.
|
||||||
|
///
|
||||||
/// # Examples
|
/// # Examples
|
||||||
///
|
///
|
||||||
/// Basic usage:
|
/// Basic usage:
|
||||||
@ -23,11 +32,12 @@ impl<T: ?Sized> *const T {
|
|||||||
/// assert!(!ptr.is_null());
|
/// assert!(!ptr.is_null());
|
||||||
/// ```
|
/// ```
|
||||||
#[stable(feature = "rust1", since = "1.0.0")]
|
#[stable(feature = "rust1", since = "1.0.0")]
|
||||||
|
#[rustc_const_unstable(feature = "const_ptr_is_null", issue = "74939")]
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn is_null(self) -> bool {
|
pub const fn is_null(self) -> bool {
|
||||||
// Compare via a cast to a thin pointer, so fat pointers are only
|
// Compare via a cast to a thin pointer, so fat pointers are only
|
||||||
// considering their "data" part for null-ness.
|
// considering their "data" part for null-ness.
|
||||||
(self as *const u8) == null()
|
(self as *const u8).guaranteed_eq(null())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Casts to a pointer of another type.
|
/// Casts to a pointer of another type.
|
||||||
|
@ -12,6 +12,15 @@ impl<T: ?Sized> *mut T {
|
|||||||
/// Therefore, two pointers that are null may still not compare equal to
|
/// Therefore, two pointers that are null may still not compare equal to
|
||||||
/// each other.
|
/// each other.
|
||||||
///
|
///
|
||||||
|
/// ## Behavior during const evaluation
|
||||||
|
///
|
||||||
|
/// When this function is used during const evaluation, it may return `false` for pointers
|
||||||
|
/// that turn out to be null at runtime. Specifically, when a pointer to some memory
|
||||||
|
/// is offset beyond its bounds in such a way that the resulting pointer is null,
|
||||||
|
/// the function will still return `false`. There is no way for CTFE to know
|
||||||
|
/// the absolute position of that memory, so we cannot tell if the pointer is
|
||||||
|
/// null or not.
|
||||||
|
///
|
||||||
/// # Examples
|
/// # Examples
|
||||||
///
|
///
|
||||||
/// Basic usage:
|
/// Basic usage:
|
||||||
@ -22,11 +31,12 @@ impl<T: ?Sized> *mut T {
|
|||||||
/// assert!(!ptr.is_null());
|
/// assert!(!ptr.is_null());
|
||||||
/// ```
|
/// ```
|
||||||
#[stable(feature = "rust1", since = "1.0.0")]
|
#[stable(feature = "rust1", since = "1.0.0")]
|
||||||
|
#[rustc_const_unstable(feature = "const_ptr_is_null", issue = "74939")]
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn is_null(self) -> bool {
|
pub const fn is_null(self) -> bool {
|
||||||
// Compare via a cast to a thin pointer, so fat pointers are only
|
// Compare via a cast to a thin pointer, so fat pointers are only
|
||||||
// considering their "data" part for null-ness.
|
// considering their "data" part for null-ness.
|
||||||
(self as *mut u8) == null_mut()
|
(self as *mut u8).guaranteed_eq(null_mut())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Casts to a pointer of another type.
|
/// Casts to a pointer of another type.
|
||||||
|
@ -329,9 +329,14 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
|
|||||||
self.write_scalar(offset_ptr, dest)?;
|
self.write_scalar(offset_ptr, dest)?;
|
||||||
}
|
}
|
||||||
sym::ptr_guaranteed_eq | sym::ptr_guaranteed_ne => {
|
sym::ptr_guaranteed_eq | sym::ptr_guaranteed_ne => {
|
||||||
// FIXME: return `true` for at least some comparisons where we can reliably
|
let a = self.read_immediate(args[0])?.to_scalar()?;
|
||||||
// determine the result of runtime (in)equality tests at compile-time.
|
let b = self.read_immediate(args[1])?.to_scalar()?;
|
||||||
self.write_scalar(Scalar::from_bool(false), dest)?;
|
let cmp = if intrinsic_name == sym::ptr_guaranteed_eq {
|
||||||
|
self.guaranteed_eq(a, b)
|
||||||
|
} else {
|
||||||
|
self.guaranteed_ne(a, b)
|
||||||
|
};
|
||||||
|
self.write_scalar(Scalar::from_bool(cmp), dest)?;
|
||||||
}
|
}
|
||||||
sym::ptr_offset_from => {
|
sym::ptr_offset_from => {
|
||||||
let a = self.read_immediate(args[0])?.to_scalar()?;
|
let a = self.read_immediate(args[0])?.to_scalar()?;
|
||||||
@ -448,6 +453,37 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
|
|||||||
Ok(true)
|
Ok(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn guaranteed_eq(&mut self, a: Scalar<M::PointerTag>, b: Scalar<M::PointerTag>) -> bool {
|
||||||
|
match (a, b) {
|
||||||
|
// Comparisons between integers are always known.
|
||||||
|
(Scalar::Raw { .. }, Scalar::Raw { .. }) => a == b,
|
||||||
|
// Equality with integers can never be known for sure.
|
||||||
|
(Scalar::Raw { .. }, Scalar::Ptr(_)) | (Scalar::Ptr(_), Scalar::Raw { .. }) => false,
|
||||||
|
// FIXME: return `true` for when both sides are the same pointer, *except* that
|
||||||
|
// some things (like functions and vtables) do not have stable addresses
|
||||||
|
// so we need to be careful around them.
|
||||||
|
(Scalar::Ptr(_), Scalar::Ptr(_)) => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn guaranteed_ne(&mut self, a: Scalar<M::PointerTag>, b: Scalar<M::PointerTag>) -> bool {
|
||||||
|
match (a, b) {
|
||||||
|
// Comparisons between integers are always known.
|
||||||
|
(Scalar::Raw { .. }, Scalar::Raw { .. }) => a != b,
|
||||||
|
// Comparisons of abstract pointers with null pointers are known if the pointer
|
||||||
|
// is in bounds, because if they are in bounds, the pointer can't be null.
|
||||||
|
(Scalar::Raw { data: 0, .. }, Scalar::Ptr(ptr))
|
||||||
|
| (Scalar::Ptr(ptr), Scalar::Raw { data: 0, .. }) => !self.memory.ptr_may_be_null(ptr),
|
||||||
|
// Inequality with integers other than null can never be known for sure.
|
||||||
|
(Scalar::Raw { .. }, Scalar::Ptr(_)) | (Scalar::Ptr(_), Scalar::Raw { .. }) => false,
|
||||||
|
// FIXME: return `true` for at least some comparisons where we can reliably
|
||||||
|
// determine the result of runtime inequality tests at compile-time.
|
||||||
|
// Examples include comparison of addresses in static items, for these we can
|
||||||
|
// give reliable results.
|
||||||
|
(Scalar::Ptr(_), Scalar::Ptr(_)) => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn exact_div(
|
pub fn exact_div(
|
||||||
&mut self,
|
&mut self,
|
||||||
a: ImmTy<'tcx, M::PointerTag>,
|
a: ImmTy<'tcx, M::PointerTag>,
|
||||||
|
74
src/test/ui/consts/ptr_comparisons.rs
Normal file
74
src/test/ui/consts/ptr_comparisons.rs
Normal file
@ -0,0 +1,74 @@
|
|||||||
|
// compile-flags: --crate-type=lib
|
||||||
|
|
||||||
|
#![feature(
|
||||||
|
const_panic,
|
||||||
|
core_intrinsics,
|
||||||
|
const_raw_ptr_comparison,
|
||||||
|
const_ptr_offset,
|
||||||
|
const_raw_ptr_deref,
|
||||||
|
raw_ref_macros
|
||||||
|
)]
|
||||||
|
|
||||||
|
const FOO: &usize = &42;
|
||||||
|
|
||||||
|
macro_rules! check {
|
||||||
|
(eq, $a:expr, $b:expr) => {
|
||||||
|
pub const _: () =
|
||||||
|
assert!(std::intrinsics::ptr_guaranteed_eq($a as *const u8, $b as *const u8));
|
||||||
|
};
|
||||||
|
(ne, $a:expr, $b:expr) => {
|
||||||
|
pub const _: () =
|
||||||
|
assert!(std::intrinsics::ptr_guaranteed_ne($a as *const u8, $b as *const u8));
|
||||||
|
};
|
||||||
|
(!eq, $a:expr, $b:expr) => {
|
||||||
|
pub const _: () =
|
||||||
|
assert!(!std::intrinsics::ptr_guaranteed_eq($a as *const u8, $b as *const u8));
|
||||||
|
};
|
||||||
|
(!ne, $a:expr, $b:expr) => {
|
||||||
|
pub const _: () =
|
||||||
|
assert!(!std::intrinsics::ptr_guaranteed_ne($a as *const u8, $b as *const u8));
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
check!(eq, 0, 0);
|
||||||
|
check!(ne, 0, 1);
|
||||||
|
check!(!eq, 0, 1);
|
||||||
|
check!(!ne, 0, 0);
|
||||||
|
check!(ne, FOO as *const _, 0);
|
||||||
|
check!(!eq, FOO as *const _, 0);
|
||||||
|
// We want pointers to be equal to themselves, but aren't checking this yet because
|
||||||
|
// there are some open questions (e.g. whether function pointers to the same function
|
||||||
|
// compare equal, they don't necessarily at runtime).
|
||||||
|
// The case tested here should work eventually, but does not work yet.
|
||||||
|
check!(!eq, FOO as *const _, FOO as *const _);
|
||||||
|
check!(ne, unsafe { (FOO as *const usize).offset(1) }, 0);
|
||||||
|
check!(!eq, unsafe { (FOO as *const usize).offset(1) }, 0);
|
||||||
|
|
||||||
|
check!(ne, unsafe { (FOO as *const usize as *const u8).offset(3) }, 0);
|
||||||
|
check!(!eq, unsafe { (FOO as *const usize as *const u8).offset(3) }, 0);
|
||||||
|
|
||||||
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
|
// If any of the below start compiling, make sure to add a `check` test for it.
|
||||||
|
// These invocations exist as canaries so we don't forget to check that the
|
||||||
|
// behaviour of `guaranteed_eq` and `guaranteed_ne` is still correct.
|
||||||
|
// All of these try to obtain an out of bounds pointer in some manner. If we
|
||||||
|
// can create out of bounds pointers, we can offset a pointer far enough that
|
||||||
|
// at runtime it would be zero and at compile-time it would not be zero.
|
||||||
|
|
||||||
|
const _: *const usize = unsafe { (FOO as *const usize).offset(2) };
|
||||||
|
//~^ NOTE
|
||||||
|
|
||||||
|
const _: *const u8 =
|
||||||
|
//~^ NOTE
|
||||||
|
unsafe { std::ptr::raw_const!((*(FOO as *const usize as *const [u8; 1000]))[999]) };
|
||||||
|
//~^ ERROR any use of this value will cause an error
|
||||||
|
|
||||||
|
const _: usize = unsafe { std::mem::transmute::<*const usize, usize>(FOO) + 4 };
|
||||||
|
//~^ ERROR any use of this value will cause an error
|
||||||
|
//~| NOTE "pointer-to-integer cast" needs an rfc
|
||||||
|
//~| NOTE
|
||||||
|
|
||||||
|
const _: usize = unsafe { *std::mem::transmute::<&&usize, &usize>(&FOO) + 4 };
|
||||||
|
//~^ ERROR any use of this value will cause an error
|
||||||
|
//~| NOTE "pointer-to-integer cast" needs an rfc
|
||||||
|
//~| NOTE
|
47
src/test/ui/consts/ptr_comparisons.stderr
Normal file
47
src/test/ui/consts/ptr_comparisons.stderr
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
error: any use of this value will cause an error
|
||||||
|
--> $SRC_DIR/core/src/ptr/const_ptr.rs:LL:COL
|
||||||
|
|
|
||||||
|
LL | unsafe { intrinsics::offset(self, count) }
|
||||||
|
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
| |
|
||||||
|
| inbounds test failed: pointer must be in-bounds at offset 16, but is outside bounds of alloc2 which has size 8
|
||||||
|
| inside `std::ptr::const_ptr::<impl *const usize>::offset` at $SRC_DIR/core/src/ptr/const_ptr.rs:LL:COL
|
||||||
|
| inside `_` at $DIR/ptr_comparisons.rs:58:34
|
||||||
|
|
|
||||||
|
::: $DIR/ptr_comparisons.rs:58:1
|
||||||
|
|
|
||||||
|
LL | const _: *const usize = unsafe { (FOO as *const usize).offset(2) };
|
||||||
|
| -------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
= note: `#[deny(const_err)]` on by default
|
||||||
|
|
||||||
|
error: any use of this value will cause an error
|
||||||
|
--> $DIR/ptr_comparisons.rs:63:14
|
||||||
|
|
|
||||||
|
LL | / const _: *const u8 =
|
||||||
|
LL | |
|
||||||
|
LL | | unsafe { std::ptr::raw_const!((*(FOO as *const usize as *const [u8; 1000]))[999]) };
|
||||||
|
| |______________^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^__-
|
||||||
|
| |
|
||||||
|
| memory access failed: pointer must be in-bounds at offset 1000, but is outside bounds of alloc2 which has size 8
|
||||||
|
|
|
||||||
|
= note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info)
|
||||||
|
|
||||||
|
error: any use of this value will cause an error
|
||||||
|
--> $DIR/ptr_comparisons.rs:66:27
|
||||||
|
|
|
||||||
|
LL | const _: usize = unsafe { std::mem::transmute::<*const usize, usize>(FOO) + 4 };
|
||||||
|
| --------------------------^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^---
|
||||||
|
| |
|
||||||
|
| "pointer-to-integer cast" needs an rfc before being allowed inside constants
|
||||||
|
|
||||||
|
error: any use of this value will cause an error
|
||||||
|
--> $DIR/ptr_comparisons.rs:71:27
|
||||||
|
|
|
||||||
|
LL | const _: usize = unsafe { *std::mem::transmute::<&&usize, &usize>(&FOO) + 4 };
|
||||||
|
| --------------------------^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^---
|
||||||
|
| |
|
||||||
|
| "pointer-to-integer cast" needs an rfc before being allowed inside constants
|
||||||
|
|
||||||
|
error: aborting due to 4 previous errors
|
||||||
|
|
16
src/test/ui/consts/ptr_is_null.rs
Normal file
16
src/test/ui/consts/ptr_is_null.rs
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
// compile-flags: --crate-type=lib
|
||||||
|
// check-pass
|
||||||
|
|
||||||
|
#![feature(const_ptr_is_null, const_panic)]
|
||||||
|
|
||||||
|
const FOO: &usize = &42;
|
||||||
|
|
||||||
|
pub const _: () = assert!(!(FOO as *const usize).is_null());
|
||||||
|
|
||||||
|
pub const _: () = assert!(!(42 as *const usize).is_null());
|
||||||
|
|
||||||
|
pub const _: () = assert!((0 as *const usize).is_null());
|
||||||
|
|
||||||
|
pub const _: () = assert!(std::ptr::null::<usize>().is_null());
|
||||||
|
|
||||||
|
pub const _: () = assert!(!("foo" as *const str).is_null());
|
Loading…
x
Reference in New Issue
Block a user