const validation: better error for maybe-null references

This commit is contained in:
Ralf Jung 2025-09-24 12:12:41 +02:00
parent 0a41add629
commit 8328c3dada
9 changed files with 108 additions and 29 deletions

View file

@ -476,9 +476,15 @@ const_eval_validation_invalid_vtable_trait = {$front_matter}: wrong trait in wid
const_eval_validation_mutable_ref_in_const = {$front_matter}: encountered mutable reference in `const` value
const_eval_validation_mutable_ref_to_immutable = {$front_matter}: encountered mutable reference or box pointing to read-only memory
const_eval_validation_never_val = {$front_matter}: encountered a value of the never type `!`
const_eval_validation_null_box = {$front_matter}: encountered a null box
const_eval_validation_null_box = {$front_matter}: encountered a {$maybe ->
[true] maybe-null
*[false] null
} box
const_eval_validation_null_fn_ptr = {$front_matter}: encountered a null function pointer
const_eval_validation_null_ref = {$front_matter}: encountered a null reference
const_eval_validation_null_ref = {$front_matter}: encountered a {$maybe ->
[true] maybe-null
*[false] null
} reference
const_eval_validation_nonnull_ptr_out_of_range = {$front_matter}: encountered a maybe-null pointer, but expected something that is definitely non-zero
const_eval_validation_out_of_range = {$front_matter}: encountered {$value}, but expected something {$in_range}
const_eval_validation_partial_pointer = {$front_matter}: encountered a partial pointer or a mix of pointers

View file

@ -696,8 +696,8 @@ impl<'tcx> ReportErrorExt for ValidationErrorInfo<'tcx> {
}
UnalignedPtr { ptr_kind: PointerKind::Box, .. } => const_eval_validation_unaligned_box,
NullPtr { ptr_kind: PointerKind::Box } => const_eval_validation_null_box,
NullPtr { ptr_kind: PointerKind::Ref(_) } => const_eval_validation_null_ref,
NullPtr { ptr_kind: PointerKind::Box, .. } => const_eval_validation_null_box,
NullPtr { ptr_kind: PointerKind::Ref(_), .. } => const_eval_validation_null_ref,
DanglingPtrNoProvenance { ptr_kind: PointerKind::Box, .. } => {
const_eval_validation_dangling_box_no_provenance
}
@ -820,8 +820,10 @@ impl<'tcx> ReportErrorExt for ValidationErrorInfo<'tcx> {
err.arg("vtable_dyn_type", vtable_dyn_type.to_string());
err.arg("expected_dyn_type", expected_dyn_type.to_string());
}
NullPtr { .. }
| MutableRefToImmutable
NullPtr { maybe, .. } => {
err.arg("maybe", maybe);
}
MutableRefToImmutable
| MutableRefInConst
| NullFnPtr
| NonnullPtrMaybeNull

View file

@ -511,7 +511,7 @@ impl<'rt, 'tcx, M: Machine<'tcx>> ValidityVisitor<'rt, 'tcx, M> {
CheckInAllocMsg::Dereferenceable, // will anyway be replaced by validity message
),
self.path,
Ub(DanglingIntPointer { addr: 0, .. }) => NullPtr { ptr_kind },
Ub(DanglingIntPointer { addr: 0, .. }) => NullPtr { ptr_kind, maybe: false },
Ub(DanglingIntPointer { addr: i, .. }) => DanglingPtrNoProvenance {
ptr_kind,
// FIXME this says "null pointer" when null but we need translate
@ -538,8 +538,10 @@ impl<'rt, 'tcx, M: Machine<'tcx>> ValidityVisitor<'rt, 'tcx, M> {
);
// Make sure this is non-null. We checked dereferenceability above, but if `size` is zero
// that does not imply non-null.
if self.ecx.scalar_may_be_null(Scalar::from_maybe_pointer(place.ptr(), self.ecx))? {
throw_validation_failure!(self.path, NullPtr { ptr_kind })
let scalar = Scalar::from_maybe_pointer(place.ptr(), self.ecx);
if self.ecx.scalar_may_be_null(scalar)? {
let maybe = !M::Provenance::OFFSET_IS_ADDR && matches!(scalar, Scalar::Ptr(..));
throw_validation_failure!(self.path, NullPtr { ptr_kind, maybe })
}
// Do not allow references to uninhabited types.
if place.layout.is_uninhabited() {
@ -757,6 +759,11 @@ impl<'rt, 'tcx, M: Machine<'tcx>> ValidityVisitor<'rt, 'tcx, M> {
} else {
// Otherwise (for standalone Miri), we have to still check it to be non-null.
if self.ecx.scalar_may_be_null(scalar)? {
let maybe =
!M::Provenance::OFFSET_IS_ADDR && matches!(scalar, Scalar::Ptr(..));
// This can't be a "maybe-null" pointer since the check for this being
// a fn ptr at all already ensures that the pointer is inbounds.
assert!(!maybe);
throw_validation_failure!(self.path, NullFnPtr);
}
}

View file

@ -541,6 +541,8 @@ pub enum ValidationErrorKind<'tcx> {
},
NullPtr {
ptr_kind: PointerKind,
/// Records whether this pointer is definitely null or just may be null.
maybe: bool,
},
DanglingPtrNoProvenance {
ptr_kind: PointerKind,

View file

@ -2655,7 +2655,7 @@ impl<'test> TestCx<'test> {
// The alloc-id appears in pretty-printed allocations.
normalized = static_regex!(
r"╾─*a(lloc)?([0-9]+)(\+0x[0-9]+)?(<imm>)?( \([0-9]+ ptr bytes\))?─*╼"
r"╾─*a(lloc)?([0-9]+)(\+0x[0-9a-f]+)?(<imm>)?( \([0-9]+ ptr bytes\))?─*╼"
)
.replace_all(&normalized, |caps: &Captures<'_>| {
// Renumber the captured index.

View file

@ -27,6 +27,11 @@ const NULL: &u16 = unsafe { mem::transmute(0usize) };
const NULL_BOX: Box<u16> = unsafe { mem::transmute(0usize) };
//~^ ERROR invalid value
const MAYBE_NULL_BOX: Box<()> = unsafe { mem::transmute({
//~^ ERROR maybe-null
let ref_ = &0u8;
(ref_ as *const u8).wrapping_add(10)
}) };
// It is very important that we reject this: We do promote `&(4 * REF_AS_USIZE)`,
// but that would fail to compile; so we ended up breaking user code that would
@ -57,7 +62,12 @@ const DANGLING_FN_PTR: fn() = unsafe { mem::transmute(13usize) };
//~^ ERROR invalid value
const DATA_FN_PTR: fn() = unsafe { mem::transmute(&13) };
//~^ ERROR invalid value
const MAYBE_NULL_FN_PTR: fn() = unsafe { mem::transmute({
//~^ ERROR invalid value
fn fun() {}
let ptr = fun as fn();
(ptr as *const u8).wrapping_add(10)
}) };
const UNALIGNED_READ: () = unsafe {
let x = &[0u8; 4];

View file

@ -42,8 +42,19 @@ LL | const NULL_BOX: Box<u16> = unsafe { mem::transmute(0usize) };
HEX_DUMP
}
error[E0080]: constructing invalid value: encountered a maybe-null box
--> $DIR/ub-ref-ptr.rs:30:1
|
LL | const MAYBE_NULL_BOX: Box<()> = unsafe { mem::transmute({
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ it is undefined behavior to use this value
|
= note: the rules on what exactly is undefined behavior aren't clear, so this check might be overzealous. Please open an issue on the rustc repository if you believe it should not be considered undefined behavior.
= note: the raw bytes of the constant (size: $SIZE, align: $ALIGN) {
HEX_DUMP
}
error[E0080]: unable to turn pointer into integer
--> $DIR/ub-ref-ptr.rs:34:1
--> $DIR/ub-ref-ptr.rs:39:1
|
LL | const REF_AS_USIZE: usize = unsafe { mem::transmute(&0) };
| ^^^^^^^^^^^^^^^^^^^^^^^^^ evaluation of `REF_AS_USIZE` failed here
@ -52,7 +63,7 @@ LL | const REF_AS_USIZE: usize = unsafe { mem::transmute(&0) };
= help: the absolute address of a pointer is not known at compile-time, so such operations are not supported
error[E0080]: unable to turn pointer into integer
--> $DIR/ub-ref-ptr.rs:37:39
--> $DIR/ub-ref-ptr.rs:42:39
|
LL | const REF_AS_USIZE_SLICE: &[usize] = &[unsafe { mem::transmute(&0) }];
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ evaluation of `REF_AS_USIZE_SLICE` failed here
@ -61,13 +72,13 @@ LL | const REF_AS_USIZE_SLICE: &[usize] = &[unsafe { mem::transmute(&0) }];
= help: the absolute address of a pointer is not known at compile-time, so such operations are not supported
note: erroneous constant encountered
--> $DIR/ub-ref-ptr.rs:37:38
--> $DIR/ub-ref-ptr.rs:42:38
|
LL | const REF_AS_USIZE_SLICE: &[usize] = &[unsafe { mem::transmute(&0) }];
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
error[E0080]: unable to turn pointer into integer
--> $DIR/ub-ref-ptr.rs:40:86
--> $DIR/ub-ref-ptr.rs:45:86
|
LL | const REF_AS_USIZE_BOX_SLICE: Box<[usize]> = unsafe { mem::transmute::<&[usize], _>(&[mem::transmute(&0)]) };
| ^^^^^^^^^^^^^^^^^^^^ evaluation of `REF_AS_USIZE_BOX_SLICE` failed here
@ -76,13 +87,13 @@ LL | const REF_AS_USIZE_BOX_SLICE: Box<[usize]> = unsafe { mem::transmute::<&[us
= help: the absolute address of a pointer is not known at compile-time, so such operations are not supported
note: erroneous constant encountered
--> $DIR/ub-ref-ptr.rs:40:85
--> $DIR/ub-ref-ptr.rs:45:85
|
LL | const REF_AS_USIZE_BOX_SLICE: Box<[usize]> = unsafe { mem::transmute::<&[usize], _>(&[mem::transmute(&0)]) };
| ^^^^^^^^^^^^^^^^^^^^^
error[E0080]: constructing invalid value: encountered a dangling reference (0x539[noalloc] has no provenance)
--> $DIR/ub-ref-ptr.rs:43:1
--> $DIR/ub-ref-ptr.rs:48:1
|
LL | const USIZE_AS_REF: &'static u8 = unsafe { mem::transmute(1337usize) };
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ it is undefined behavior to use this value
@ -93,7 +104,7 @@ LL | const USIZE_AS_REF: &'static u8 = unsafe { mem::transmute(1337usize) };
}
error[E0080]: constructing invalid value: encountered a dangling box (0x539[noalloc] has no provenance)
--> $DIR/ub-ref-ptr.rs:46:1
--> $DIR/ub-ref-ptr.rs:51:1
|
LL | const USIZE_AS_BOX: Box<u8> = unsafe { mem::transmute(1337usize) };
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^ it is undefined behavior to use this value
@ -103,8 +114,8 @@ LL | const USIZE_AS_BOX: Box<u8> = unsafe { mem::transmute(1337usize) };
HEX_DUMP
}
error[E0080]: reading memory at ALLOC4[0x%..0x%], but memory is uninitialized at [0x%..0x%], and this operation requires initialized memory
--> $DIR/ub-ref-ptr.rs:49:41
error[E0080]: reading memory at ALLOC6[0x%..0x%], but memory is uninitialized at [0x%..0x%], and this operation requires initialized memory
--> $DIR/ub-ref-ptr.rs:54:41
|
LL | const UNINIT_PTR: *const i32 = unsafe { MaybeUninit { uninit: () }.init };
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ evaluation of `UNINIT_PTR` failed here
@ -114,7 +125,7 @@ LL | const UNINIT_PTR: *const i32 = unsafe { MaybeUninit { uninit: () }.init };
}
error[E0080]: constructing invalid value: encountered null pointer, but expected a function pointer
--> $DIR/ub-ref-ptr.rs:52:1
--> $DIR/ub-ref-ptr.rs:57:1
|
LL | const NULL_FN_PTR: fn() = unsafe { mem::transmute(0usize) };
| ^^^^^^^^^^^^^^^^^^^^^^^ it is undefined behavior to use this value
@ -124,8 +135,8 @@ LL | const NULL_FN_PTR: fn() = unsafe { mem::transmute(0usize) };
HEX_DUMP
}
error[E0080]: reading memory at ALLOC5[0x%..0x%], but memory is uninitialized at [0x%..0x%], and this operation requires initialized memory
--> $DIR/ub-ref-ptr.rs:54:38
error[E0080]: reading memory at ALLOC7[0x%..0x%], but memory is uninitialized at [0x%..0x%], and this operation requires initialized memory
--> $DIR/ub-ref-ptr.rs:59:38
|
LL | const UNINIT_FN_PTR: fn() = unsafe { MaybeUninit { uninit: () }.init };
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ evaluation of `UNINIT_FN_PTR` failed here
@ -135,7 +146,7 @@ LL | const UNINIT_FN_PTR: fn() = unsafe { MaybeUninit { uninit: () }.init };
}
error[E0080]: constructing invalid value: encountered 0xd[noalloc], but expected a function pointer
--> $DIR/ub-ref-ptr.rs:56:1
--> $DIR/ub-ref-ptr.rs:61:1
|
LL | const DANGLING_FN_PTR: fn() = unsafe { mem::transmute(13usize) };
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^ it is undefined behavior to use this value
@ -145,8 +156,8 @@ LL | const DANGLING_FN_PTR: fn() = unsafe { mem::transmute(13usize) };
HEX_DUMP
}
error[E0080]: constructing invalid value: encountered ALLOC2<imm>, but expected a function pointer
--> $DIR/ub-ref-ptr.rs:58:1
error[E0080]: constructing invalid value: encountered ALLOC3<imm>, but expected a function pointer
--> $DIR/ub-ref-ptr.rs:63:1
|
LL | const DATA_FN_PTR: fn() = unsafe { mem::transmute(&13) };
| ^^^^^^^^^^^^^^^^^^^^^^^ it is undefined behavior to use this value
@ -156,14 +167,25 @@ LL | const DATA_FN_PTR: fn() = unsafe { mem::transmute(&13) };
HEX_DUMP
}
error[E0080]: constructing invalid value: encountered ALLOC4+0xa, but expected a function pointer
--> $DIR/ub-ref-ptr.rs:65:1
|
LL | const MAYBE_NULL_FN_PTR: fn() = unsafe { mem::transmute({
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ it is undefined behavior to use this value
|
= note: the rules on what exactly is undefined behavior aren't clear, so this check might be overzealous. Please open an issue on the rustc repository if you believe it should not be considered undefined behavior.
= note: the raw bytes of the constant (size: $SIZE, align: $ALIGN) {
HEX_DUMP
}
error[E0080]: accessing memory based on pointer with alignment 1, but alignment 4 is required
--> $DIR/ub-ref-ptr.rs:65:5
--> $DIR/ub-ref-ptr.rs:75:5
|
LL | ptr.read();
| ^^^^^^^^^^ evaluation of `UNALIGNED_READ` failed here
error[E0080]: constructing invalid value: encountered a pointer with unknown absolute address, but expected something that is definitely greater or equal to 1000
--> $DIR/ub-ref-ptr.rs:74:1
--> $DIR/ub-ref-ptr.rs:84:1
|
LL | const INVALID_VALUE_PTR: High = unsafe { mem::transmute(&S) };
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ it is undefined behavior to use this value
@ -173,6 +195,6 @@ LL | const INVALID_VALUE_PTR: High = unsafe { mem::transmute(&S) };
HEX_DUMP
}
error: aborting due to 16 previous errors
error: aborting due to 18 previous errors
For more information about this error, try `rustc --explain E0080`.

View file

@ -0,0 +1,16 @@
//! Ensure a decent error message for maybe-null references.
//! (see <https://github.com/rust-lang/rust/issues/146748>)
// Strip out raw byte dumps to make comparison platform-independent:
//@ normalize-stderr: "(the raw bytes of the constant) \(size: [0-9]*, align: [0-9]*\)" -> "$1 (size: $$SIZE, align: $$ALIGN)"
//@ normalize-stderr: "([0-9a-f][0-9a-f] |╾─*A(LLOC)?[0-9]+(\+[a-z0-9]+)?(<imm>)?─*╼ )+ *│.*" -> "HEX_DUMP"
#![feature(const_trait_impl, const_cmp)]
use std::any::TypeId;
use std::mem::transmute;
const A: [&(); 16 / size_of::<*const ()>()] = unsafe { transmute(TypeId::of::<i32>()) };
//~^ERROR: maybe-null
fn main() {}

View file

@ -0,0 +1,14 @@
error[E0080]: constructing invalid value at [0]: encountered a maybe-null reference
--> $DIR/const_transmute_type_id7.rs:13:1
|
LL | const A: [&(); 16 / size_of::<*const ()>()] = unsafe { transmute(TypeId::of::<i32>()) };
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ it is undefined behavior to use this value
|
= note: the rules on what exactly is undefined behavior aren't clear, so this check might be overzealous. Please open an issue on the rustc repository if you believe it should not be considered undefined behavior.
= note: the raw bytes of the constant (size: $SIZE, align: $ALIGN) {
HEX_DUMP
}
error: aborting due to 1 previous error
For more information about this error, try `rustc --explain E0080`.