Rollup merge of #148746 - RalfJung:mutable-ref-in-const, r=davidtwco

const validation: remove check for mutable refs in final value of const

This check rejects code that is not necessarily UB, e.g. a mutable ref to a `static mut` that is very carefully used correctly. That led to us having to describe it in the Reference, which uncovered just how ad-hoc this check is (https://github.com/rust-lang/reference/issues/2074).

Even without this check, we still reject things like
```rust
const C: &mut i32 = &mut 0;
```
This is rejected by const checking -- the part of the frontend that looks at the source code and says whether it is allowed in const context. In the Reference, this restriction is explained [here](https://doc.rust-lang.org/nightly/reference/const_eval.html#r-const-eval.const-expr.borrows).

So, the check during validation is just a safety net. And it is already a safety net with gaping holes since we only check `&mut T`, not `&UnsafeCell<T>`, due to the fact that we promote some immutable values that have `!Freeze` type so `&!Freeze` actually can occur in the final value of a const.

So... it may be time for me to acknowledge that the "mutable ref in final value of const" check is a cure that's worth than the disease. Nobody asked for that check, I just added it because I was worried about soundness issues when we allow mutable references in constants. Originally it was much stricter, but I had to slowly relax it to its current form to prevent t from firing on code we intend to allow. In the end there are only 3 tests left that trigger this error, and they are all just constants containing references to mutable statics -- not the safest code in the world, but also not so bad that we have to spend a lot of time devising a core language limitation and associated Reference wording to prevent it from ever happening.

So... `@rust-lang/wg-const-eval` `@rust-lang/lang`  I propose that we allow code like this
```rust
static mut S: i32 = 3;
const C2: &'static mut i32 = unsafe { &mut * &raw mut S };
```
`@theemathas` would be great if you could try to poke a hole into this. ;)
This commit is contained in:
Matthias Krüger 2025-11-29 20:54:05 +01:00 committed by GitHub
commit 8e4c70f02b
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
12 changed files with 79 additions and 137 deletions

View file

@ -473,7 +473,6 @@ const_eval_validation_invalid_ref_meta = {$front_matter}: encountered invalid re
const_eval_validation_invalid_ref_slice_meta = {$front_matter}: encountered invalid reference metadata: slice is bigger than largest supported object
const_eval_validation_invalid_vtable_ptr = {$front_matter}: encountered {$value}, but expected a vtable pointer
const_eval_validation_invalid_vtable_trait = {$front_matter}: wrong trait in wide pointer vtable: expected `{$expected_dyn_type}`, but encountered `{$vtable_dyn_type}`
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_nonnull_ptr_out_of_range = {$front_matter}: encountered a maybe-null pointer, but expected something that is definitely non-zero

View file

@ -665,7 +665,6 @@ impl<'tcx> ReportErrorExt for ValidationErrorInfo<'tcx> {
PointerAsInt { .. } => const_eval_validation_pointer_as_int,
PartialPointer => const_eval_validation_partial_pointer,
MutableRefToImmutable => const_eval_validation_mutable_ref_to_immutable,
MutableRefInConst => const_eval_validation_mutable_ref_in_const,
NullFnPtr { .. } => const_eval_validation_null_fn_ptr,
NeverVal => const_eval_validation_never_val,
NonnullPtrMaybeNull { .. } => const_eval_validation_nonnull_ptr_out_of_range,
@ -824,7 +823,6 @@ impl<'tcx> ReportErrorExt for ValidationErrorInfo<'tcx> {
err.arg("maybe", maybe);
}
MutableRefToImmutable
| MutableRefInConst
| NonnullPtrMaybeNull
| NeverVal
| UnsafeCellInImmutable

View file

@ -639,12 +639,6 @@ impl<'rt, 'tcx, M: Machine<'tcx>> ValidityVisitor<'rt, 'tcx, M> {
// This can actually occur with transmutes.
throw_validation_failure!(self.path, MutableRefToImmutable);
}
// In a const, any kind of mutable reference is not good.
if matches!(self.ctfe_mode, Some(CtfeValidationMode::Const { .. })) {
if ptr_expected_mutbl == Mutability::Mut {
throw_validation_failure!(self.path, MutableRefInConst);
}
}
}
}
// Potentially skip recursive check.

View file

@ -496,7 +496,6 @@ pub enum ValidationErrorKind<'tcx> {
},
MutableRefToImmutable,
UnsafeCellInImmutable,
MutableRefInConst,
NullFnPtr {
/// Records whether this pointer is definitely null or just may be null.
maybe: bool,

View file

@ -77,6 +77,36 @@ const RAW_MUT_CAST_C: SyncPtr<i32> = SyncPtr { x : &mut 42 as *mut _ as *const _
const RAW_MUT_COERCE_C: SyncPtr<i32> = SyncPtr { x: &mut 0 };
//~^ ERROR mutable borrows of temporaries
// Various cases of dangling references.
fn dangling() {
const fn helper_int2ptr() -> Option<&'static mut i32> { unsafe {
// Undefined behaviour (integer as pointer), who doesn't love tests like this.
Some(&mut *(42 as *mut i32))
} }
const INT2PTR: Option<&mut i32> = helper_int2ptr(); //~ ERROR encountered a dangling reference
static INT2PTR_STATIC: Option<&mut i32> = helper_int2ptr(); //~ ERROR encountered a dangling reference
const fn helper_dangling() -> Option<&'static mut i32> { unsafe {
// Undefined behaviour (dangling pointer), who doesn't love tests like this.
Some(&mut *(&mut 42 as *mut i32))
} }
const DANGLING: Option<&mut i32> = helper_dangling(); //~ ERROR dangling reference
static DANGLING_STATIC: Option<&mut i32> = helper_dangling(); //~ ERROR dangling reference
}
// Allowed, because there is an explicit static mut.
static mut BUFFER: i32 = 42;
const fn ptr_to_buffer() -> Option<&'static mut i32> { unsafe {
Some(&mut *std::ptr::addr_of_mut!(BUFFER))
} }
const MUT_TO_BUFFER: Option<&mut i32> = ptr_to_buffer();
// These are fine! Just statics pointing to mutable statics, nothing fundamentally wrong with this.
static MUT_STATIC: Option<&mut i32> = ptr_to_buffer();
static mut MUT_ARRAY: &mut [u8] = &mut [42];
static MUTEX: std::sync::Mutex<&mut [u8]> = std::sync::Mutex::new(unsafe { &mut *MUT_ARRAY });
fn main() {
println!("{}", unsafe { *A });
unsafe { *B = 4 } // Bad news

View file

@ -120,7 +120,51 @@ LL | const RAW_MUT_COERCE_C: SyncPtr<i32> = SyncPtr { x: &mut 0 };
= note: To avoid accidentally creating global mutable state, such temporaries must be immutable
= help: If you really want global mutable state, try replacing the temporary by an interior mutable `static` or a `static mut`
error: aborting due to 12 previous errors
error[E0080]: constructing invalid value at .<enum-variant(Some)>.0: encountered a dangling reference (0x2a[noalloc] has no provenance)
--> $DIR/mut_ref_in_final.rs:86:5
|
LL | const INT2PTR: Option<&mut i32> = helper_int2ptr();
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 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]: constructing invalid value at .<enum-variant(Some)>.0: encountered a dangling reference (0x2a[noalloc] has no provenance)
--> $DIR/mut_ref_in_final.rs:87:5
|
LL | static INT2PTR_STATIC: Option<&mut i32> = helper_int2ptr();
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 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]: constructing invalid value at .<enum-variant(Some)>.0: encountered a dangling reference (use-after-free)
--> $DIR/mut_ref_in_final.rs:93:5
|
LL | const DANGLING: Option<&mut i32> = helper_dangling();
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 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]: constructing invalid value at .<enum-variant(Some)>.0: encountered a dangling reference (use-after-free)
--> $DIR/mut_ref_in_final.rs:94:5
|
LL | static DANGLING_STATIC: Option<&mut i32> = helper_dangling();
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 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 16 previous errors
Some errors have detailed explanations: E0080, E0716, E0764.
For more information about an error, try `rustc --explain E0080`.

View file

@ -1,40 +0,0 @@
//@ normalize-stderr: "(the raw bytes of the constant) \(size: [0-9]*, align: [0-9]*\)" -> "$1 (size: $$SIZE, align: $$ALIGN)"
//@ normalize-stderr: "( 0x[0-9a-f][0-9a-f] │)? ([0-9a-f][0-9a-f] |__ |╾─*ALLOC[0-9]+(\+[a-z0-9]+)?(<imm>)?─*╼ )+ *│.*" -> " HEX_DUMP"
//@ normalize-stderr: "HEX_DUMP\s*\n\s*HEX_DUMP" -> "HEX_DUMP"
//@ dont-require-annotations: NOTE
use std::sync::Mutex;
// This file checks that our dynamic checks catch things that the static checks miss.
// We do not have static checks for these, because we do not look into function bodies.
// We treat all functions as not returning a mutable reference, because there is no way to
// do that without causing the borrow checker to complain (see the B4/helper test in
// mut_ref_in_final.rs).
static mut BUFFER: i32 = 42;
const fn helper() -> Option<&'static mut i32> { unsafe {
Some(&mut *std::ptr::addr_of_mut!(BUFFER))
} }
const MUT: Option<&mut i32> = helper(); //~ ERROR encountered mutable reference
const fn helper_int2ptr() -> Option<&'static mut i32> { unsafe {
// Undefined behaviour (integer as pointer), who doesn't love tests like this.
Some(&mut *(42 as *mut i32))
} }
const INT2PTR: Option<&mut i32> = helper_int2ptr(); //~ ERROR encountered a dangling reference
static INT2PTR_STATIC: Option<&mut i32> = helper_int2ptr(); //~ ERROR encountered a dangling reference
const fn helper_dangling() -> Option<&'static mut i32> { unsafe {
// Undefined behaviour (dangling pointer), who doesn't love tests like this.
Some(&mut *(&mut 42 as *mut i32))
} }
const DANGLING: Option<&mut i32> = helper_dangling(); //~ ERROR dangling reference
static DANGLING_STATIC: Option<&mut i32> = helper_dangling(); //~ ERROR dangling reference
// These are fine! Just statics pointing to mutable statics, nothing fundamentally wrong with this.
static MUT_STATIC: Option<&mut i32> = helper();
static mut MUT_ARRAY: &mut [u8] = &mut [42];
static MUTEX: Mutex<&mut [u8]> = Mutex::new(unsafe { &mut *MUT_ARRAY });
fn main() {}

View file

@ -1,58 +0,0 @@
error[E0080]: constructing invalid value at .<enum-variant(Some)>.0: encountered mutable reference in `const` value
--> $DIR/mut_ref_in_final_dynamic_check.rs:19:1
|
LL | const MUT: Option<&mut i32> = helper();
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^ 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]: constructing invalid value at .<enum-variant(Some)>.0: encountered a dangling reference (0x2a[noalloc] has no provenance)
--> $DIR/mut_ref_in_final_dynamic_check.rs:25:1
|
LL | const INT2PTR: Option<&mut i32> = helper_int2ptr();
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 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]: constructing invalid value at .<enum-variant(Some)>.0: encountered a dangling reference (0x2a[noalloc] has no provenance)
--> $DIR/mut_ref_in_final_dynamic_check.rs:26:1
|
LL | static INT2PTR_STATIC: Option<&mut i32> = helper_int2ptr();
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 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]: constructing invalid value at .<enum-variant(Some)>.0: encountered a dangling reference (use-after-free)
--> $DIR/mut_ref_in_final_dynamic_check.rs:32:1
|
LL | const DANGLING: Option<&mut i32> = helper_dangling();
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 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]: constructing invalid value at .<enum-variant(Some)>.0: encountered a dangling reference (use-after-free)
--> $DIR/mut_ref_in_final_dynamic_check.rs:33:1
|
LL | static DANGLING_STATIC: Option<&mut i32> = helper_dangling();
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 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 5 previous errors
For more information about this error, try `rustc --explain E0080`.

View file

@ -9,6 +9,5 @@ const C1: &'static mut [usize] = &mut [];
static mut S: i32 = 3;
const C2: &'static mut i32 = unsafe { &mut S };
//~^ ERROR: encountered mutable reference
fn main() {}

View file

@ -8,18 +8,6 @@ LL | const C1: &'static mut [usize] = &mut [];
= note: To avoid accidentally creating global mutable state, such temporaries must be immutable
= help: If you really want global mutable state, try replacing the temporary by an interior mutable `static` or a `static mut`
error[E0080]: constructing invalid value: encountered mutable reference in `const` value
--> $DIR/issue-17718-const-bad-values.rs:11:1
|
LL | const C2: &'static mut i32 = unsafe { &mut S };
| ^^^^^^^^^^^^^^^^^^^^^^^^^^ 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: $PTR, align: $PTR) {
HEX_DUMP
}
error: aborting due to 1 previous error
error: aborting due to 2 previous errors
Some errors have detailed explanations: E0080, E0764.
For more information about an error, try `rustc --explain E0080`.
For more information about this error, try `rustc --explain E0764`.

View file

@ -25,8 +25,8 @@ static BOO: &mut Foo<()> = &mut Foo(());
const BLUNT: &mut i32 = &mut 42;
//~^ ERROR: pointing to read-only memory
// This is fine, it points to a static so there are no questions of pointer identity.
const SUBTLE: &mut i32 = unsafe {
//~^ ERROR: encountered mutable reference
static mut STATIC: i32 = 0;
&mut STATIC
};

View file

@ -43,17 +43,6 @@ LL | const BLUNT: &mut i32 = &mut 42;
HEX_DUMP
}
error[E0080]: constructing invalid value: encountered mutable reference in `const` value
--> $DIR/mutable_references.rs:28:1
|
LL | const SUBTLE: &mut i32 = unsafe {
| ^^^^^^^^^^^^^^^^^^^^^^ 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]: constructing invalid value at .x.<deref>: encountered `UnsafeCell` in read-only memory
--> $DIR/mutable_references.rs:40:1
|
@ -210,7 +199,7 @@ help: skipping check that does not even have a feature gate
LL | const RAW_MUT_COERCE: SyncPtr<i32> = SyncPtr { x: &mut 0 };
| ^^^^^^
error: aborting due to 16 previous errors; 1 warning emitted
error: aborting due to 15 previous errors; 1 warning emitted
Some errors have detailed explanations: E0080, E0594.
For more information about an error, try `rustc --explain E0080`.