Skip to content

Commit e8df0b8

Browse files
committed
Auto merge of #74940 - oli-obk:const_is_null, r=RalfJung
Make `<*const T>::is_null` const fn r? @RalfJung cc @rust-lang/wg-const-eval tracking issue: #74939
2 parents 8cdc94e + daf7a37 commit e8df0b8

File tree

6 files changed

+204
-7
lines changed

6 files changed

+204
-7
lines changed

library/core/src/ptr/const_ptr.rs

+12-2
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,15 @@ impl<T: ?Sized> *const T {
1313
/// Therefore, two pointers that are null may still not compare equal to
1414
/// each other.
1515
///
16+
/// ## Behavior during const evaluation
17+
///
18+
/// When this function is used during const evaluation, it may return `false` for pointers
19+
/// that turn out to be null at runtime. Specifically, when a pointer to some memory
20+
/// is offset beyond its bounds in such a way that the resulting pointer is null,
21+
/// the function will still return `false`. There is no way for CTFE to know
22+
/// the absolute position of that memory, so we cannot tell if the pointer is
23+
/// null or not.
24+
///
1625
/// # Examples
1726
///
1827
/// Basic usage:
@@ -23,11 +32,12 @@ impl<T: ?Sized> *const T {
2332
/// assert!(!ptr.is_null());
2433
/// ```
2534
#[stable(feature = "rust1", since = "1.0.0")]
35+
#[rustc_const_unstable(feature = "const_ptr_is_null", issue = "74939")]
2636
#[inline]
27-
pub fn is_null(self) -> bool {
37+
pub const fn is_null(self) -> bool {
2838
// Compare via a cast to a thin pointer, so fat pointers are only
2939
// considering their "data" part for null-ness.
30-
(self as *const u8) == null()
40+
(self as *const u8).guaranteed_eq(null())
3141
}
3242

3343
/// Casts to a pointer of another type.

library/core/src/ptr/mut_ptr.rs

+12-2
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,15 @@ impl<T: ?Sized> *mut T {
1212
/// Therefore, two pointers that are null may still not compare equal to
1313
/// each other.
1414
///
15+
/// ## Behavior during const evaluation
16+
///
17+
/// When this function is used during const evaluation, it may return `false` for pointers
18+
/// that turn out to be null at runtime. Specifically, when a pointer to some memory
19+
/// is offset beyond its bounds in such a way that the resulting pointer is null,
20+
/// the function will still return `false`. There is no way for CTFE to know
21+
/// the absolute position of that memory, so we cannot tell if the pointer is
22+
/// null or not.
23+
///
1524
/// # Examples
1625
///
1726
/// Basic usage:
@@ -22,11 +31,12 @@ impl<T: ?Sized> *mut T {
2231
/// assert!(!ptr.is_null());
2332
/// ```
2433
#[stable(feature = "rust1", since = "1.0.0")]
34+
#[rustc_const_unstable(feature = "const_ptr_is_null", issue = "74939")]
2535
#[inline]
26-
pub fn is_null(self) -> bool {
36+
pub const fn is_null(self) -> bool {
2737
// Compare via a cast to a thin pointer, so fat pointers are only
2838
// considering their "data" part for null-ness.
29-
(self as *mut u8) == null_mut()
39+
(self as *mut u8).guaranteed_eq(null_mut())
3040
}
3141

3242
/// Casts to a pointer of another type.

src/librustc_mir/interpret/intrinsics.rs

+39-3
Original file line numberDiff line numberDiff line change
@@ -329,9 +329,14 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
329329
self.write_scalar(offset_ptr, dest)?;
330330
}
331331
sym::ptr_guaranteed_eq | sym::ptr_guaranteed_ne => {
332-
// FIXME: return `true` for at least some comparisons where we can reliably
333-
// determine the result of runtime (in)equality tests at compile-time.
334-
self.write_scalar(Scalar::from_bool(false), dest)?;
332+
let a = self.read_immediate(args[0])?.to_scalar()?;
333+
let b = self.read_immediate(args[1])?.to_scalar()?;
334+
let cmp = if intrinsic_name == sym::ptr_guaranteed_eq {
335+
self.guaranteed_eq(a, b)
336+
} else {
337+
self.guaranteed_ne(a, b)
338+
};
339+
self.write_scalar(Scalar::from_bool(cmp), dest)?;
335340
}
336341
sym::ptr_offset_from => {
337342
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> {
448453
Ok(true)
449454
}
450455

456+
fn guaranteed_eq(&mut self, a: Scalar<M::PointerTag>, b: Scalar<M::PointerTag>) -> bool {
457+
match (a, b) {
458+
// Comparisons between integers are always known.
459+
(Scalar::Raw { .. }, Scalar::Raw { .. }) => a == b,
460+
// Equality with integers can never be known for sure.
461+
(Scalar::Raw { .. }, Scalar::Ptr(_)) | (Scalar::Ptr(_), Scalar::Raw { .. }) => false,
462+
// FIXME: return `true` for when both sides are the same pointer, *except* that
463+
// some things (like functions and vtables) do not have stable addresses
464+
// so we need to be careful around them.
465+
(Scalar::Ptr(_), Scalar::Ptr(_)) => false,
466+
}
467+
}
468+
469+
fn guaranteed_ne(&mut self, a: Scalar<M::PointerTag>, b: Scalar<M::PointerTag>) -> bool {
470+
match (a, b) {
471+
// Comparisons between integers are always known.
472+
(Scalar::Raw { .. }, Scalar::Raw { .. }) => a != b,
473+
// Comparisons of abstract pointers with null pointers are known if the pointer
474+
// is in bounds, because if they are in bounds, the pointer can't be null.
475+
(Scalar::Raw { data: 0, .. }, Scalar::Ptr(ptr))
476+
| (Scalar::Ptr(ptr), Scalar::Raw { data: 0, .. }) => !self.memory.ptr_may_be_null(ptr),
477+
// Inequality with integers other than null can never be known for sure.
478+
(Scalar::Raw { .. }, Scalar::Ptr(_)) | (Scalar::Ptr(_), Scalar::Raw { .. }) => false,
479+
// FIXME: return `true` for at least some comparisons where we can reliably
480+
// determine the result of runtime inequality tests at compile-time.
481+
// Examples include comparison of addresses in static items, for these we can
482+
// give reliable results.
483+
(Scalar::Ptr(_), Scalar::Ptr(_)) => false,
484+
}
485+
}
486+
451487
pub fn exact_div(
452488
&mut self,
453489
a: ImmTy<'tcx, M::PointerTag>,

src/test/ui/consts/ptr_comparisons.rs

+78
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
// compile-flags: --crate-type=lib
2+
// normalize-stderr-32bit: "offset 8" -> "offset $$TWO_WORDS"
3+
// normalize-stderr-64bit: "offset 16" -> "offset $$TWO_WORDS"
4+
// normalize-stderr-32bit: "size 4" -> "size $$WORD"
5+
// normalize-stderr-64bit: "size 8" -> "size $$WORD"
6+
7+
#![feature(
8+
const_panic,
9+
core_intrinsics,
10+
const_raw_ptr_comparison,
11+
const_ptr_offset,
12+
const_raw_ptr_deref,
13+
raw_ref_macros
14+
)]
15+
16+
const FOO: &usize = &42;
17+
18+
macro_rules! check {
19+
(eq, $a:expr, $b:expr) => {
20+
pub const _: () =
21+
assert!(std::intrinsics::ptr_guaranteed_eq($a as *const u8, $b as *const u8));
22+
};
23+
(ne, $a:expr, $b:expr) => {
24+
pub const _: () =
25+
assert!(std::intrinsics::ptr_guaranteed_ne($a as *const u8, $b as *const u8));
26+
};
27+
(!eq, $a:expr, $b:expr) => {
28+
pub const _: () =
29+
assert!(!std::intrinsics::ptr_guaranteed_eq($a as *const u8, $b as *const u8));
30+
};
31+
(!ne, $a:expr, $b:expr) => {
32+
pub const _: () =
33+
assert!(!std::intrinsics::ptr_guaranteed_ne($a as *const u8, $b as *const u8));
34+
};
35+
}
36+
37+
check!(eq, 0, 0);
38+
check!(ne, 0, 1);
39+
check!(!eq, 0, 1);
40+
check!(!ne, 0, 0);
41+
check!(ne, FOO as *const _, 0);
42+
check!(!eq, FOO as *const _, 0);
43+
// We want pointers to be equal to themselves, but aren't checking this yet because
44+
// there are some open questions (e.g. whether function pointers to the same function
45+
// compare equal, they don't necessarily at runtime).
46+
// The case tested here should work eventually, but does not work yet.
47+
check!(!eq, FOO as *const _, FOO as *const _);
48+
check!(ne, unsafe { (FOO as *const usize).offset(1) }, 0);
49+
check!(!eq, unsafe { (FOO as *const usize).offset(1) }, 0);
50+
51+
check!(ne, unsafe { (FOO as *const usize as *const u8).offset(3) }, 0);
52+
check!(!eq, unsafe { (FOO as *const usize as *const u8).offset(3) }, 0);
53+
54+
///////////////////////////////////////////////////////////////////////////////
55+
// If any of the below start compiling, make sure to add a `check` test for it.
56+
// These invocations exist as canaries so we don't forget to check that the
57+
// behaviour of `guaranteed_eq` and `guaranteed_ne` is still correct.
58+
// All of these try to obtain an out of bounds pointer in some manner. If we
59+
// can create out of bounds pointers, we can offset a pointer far enough that
60+
// at runtime it would be zero and at compile-time it would not be zero.
61+
62+
const _: *const usize = unsafe { (FOO as *const usize).offset(2) };
63+
//~^ NOTE
64+
65+
const _: *const u8 =
66+
//~^ NOTE
67+
unsafe { std::ptr::raw_const!((*(FOO as *const usize as *const [u8; 1000]))[999]) };
68+
//~^ ERROR any use of this value will cause an error
69+
70+
const _: usize = unsafe { std::mem::transmute::<*const usize, usize>(FOO) + 4 };
71+
//~^ ERROR any use of this value will cause an error
72+
//~| NOTE "pointer-to-integer cast" needs an rfc
73+
//~| NOTE
74+
75+
const _: usize = unsafe { *std::mem::transmute::<&&usize, &usize>(&FOO) + 4 };
76+
//~^ ERROR any use of this value will cause an error
77+
//~| NOTE "pointer-to-integer cast" needs an rfc
78+
//~| NOTE
+47
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
error: any use of this value will cause an error
2+
--> $SRC_DIR/core/src/ptr/const_ptr.rs:LL:COL
3+
|
4+
LL | unsafe { intrinsics::offset(self, count) }
5+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
6+
| |
7+
| inbounds test failed: pointer must be in-bounds at offset $TWO_WORDS, but is outside bounds of alloc2 which has size $WORD
8+
| inside `std::ptr::const_ptr::<impl *const usize>::offset` at $SRC_DIR/core/src/ptr/const_ptr.rs:LL:COL
9+
| inside `_` at $DIR/ptr_comparisons.rs:62:34
10+
|
11+
::: $DIR/ptr_comparisons.rs:62:1
12+
|
13+
LL | const _: *const usize = unsafe { (FOO as *const usize).offset(2) };
14+
| -------------------------------------------------------------------
15+
|
16+
= note: `#[deny(const_err)]` on by default
17+
18+
error: any use of this value will cause an error
19+
--> $DIR/ptr_comparisons.rs:67:14
20+
|
21+
LL | / const _: *const u8 =
22+
LL | |
23+
LL | | unsafe { std::ptr::raw_const!((*(FOO as *const usize as *const [u8; 1000]))[999]) };
24+
| |______________^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^__-
25+
| |
26+
| memory access failed: pointer must be in-bounds at offset 1000, but is outside bounds of alloc2 which has size $WORD
27+
|
28+
= note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info)
29+
30+
error: any use of this value will cause an error
31+
--> $DIR/ptr_comparisons.rs:70:27
32+
|
33+
LL | const _: usize = unsafe { std::mem::transmute::<*const usize, usize>(FOO) + 4 };
34+
| --------------------------^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^---
35+
| |
36+
| "pointer-to-integer cast" needs an rfc before being allowed inside constants
37+
38+
error: any use of this value will cause an error
39+
--> $DIR/ptr_comparisons.rs:75:27
40+
|
41+
LL | const _: usize = unsafe { *std::mem::transmute::<&&usize, &usize>(&FOO) + 4 };
42+
| --------------------------^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^---
43+
| |
44+
| "pointer-to-integer cast" needs an rfc before being allowed inside constants
45+
46+
error: aborting due to 4 previous errors
47+

src/test/ui/consts/ptr_is_null.rs

+16
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
// compile-flags: --crate-type=lib
2+
// check-pass
3+
4+
#![feature(const_ptr_is_null, const_panic)]
5+
6+
const FOO: &usize = &42;
7+
8+
pub const _: () = assert!(!(FOO as *const usize).is_null());
9+
10+
pub const _: () = assert!(!(42 as *const usize).is_null());
11+
12+
pub const _: () = assert!((0 as *const usize).is_null());
13+
14+
pub const _: () = assert!(std::ptr::null::<usize>().is_null());
15+
16+
pub const _: () = assert!(!("foo" as *const str).is_null());

0 commit comments

Comments
 (0)