Rollup merge of #126154 - RalfJung:storage-live, r=compiler-errors
StorageLive: refresh storage (instead of UB) when local is already live Blocked on [this FCP](https://github.com/rust-lang/rust/issues/99160#issuecomment-2155924538), which also contains the motivation. Fixes https://github.com/rust-lang/rust/issues/99160 Fixes https://github.com/rust-lang/rust/issues/98896 (by declaring it not-a-bug) Fixes https://github.com/rust-lang/rust/issues/119366 Fixes https://github.com/rust-lang/unsafe-code-guidelines/issues/129
This commit is contained in:
commit
035285b464
7 changed files with 71 additions and 11 deletions
|
@ -73,8 +73,6 @@ const_eval_division_by_zero =
|
||||||
dividing by zero
|
dividing by zero
|
||||||
const_eval_division_overflow =
|
const_eval_division_overflow =
|
||||||
overflow in signed division (dividing MIN by -1)
|
overflow in signed division (dividing MIN by -1)
|
||||||
const_eval_double_storage_live =
|
|
||||||
StorageLive on a local that was already live
|
|
||||||
|
|
||||||
const_eval_dyn_call_not_a_method =
|
const_eval_dyn_call_not_a_method =
|
||||||
`dyn` call trying to call something that is not a method
|
`dyn` call trying to call something that is not a method
|
||||||
|
|
|
@ -1100,11 +1100,9 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
|
||||||
Operand::Immediate(Immediate::Uninit)
|
Operand::Immediate(Immediate::Uninit)
|
||||||
});
|
});
|
||||||
|
|
||||||
// StorageLive expects the local to be dead, and marks it live.
|
// If the local is already live, deallocate its old memory.
|
||||||
let old = mem::replace(&mut self.frame_mut().locals[local].value, local_val);
|
let old = mem::replace(&mut self.frame_mut().locals[local].value, local_val);
|
||||||
if !matches!(old, LocalValue::Dead) {
|
self.deallocate_local(old)?;
|
||||||
throw_ub_custom!(fluent::const_eval_double_storage_live);
|
|
||||||
}
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1118,7 +1116,7 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
|
||||||
assert!(local != mir::RETURN_PLACE, "Cannot make return place dead");
|
assert!(local != mir::RETURN_PLACE, "Cannot make return place dead");
|
||||||
trace!("{:?} is now dead", local);
|
trace!("{:?} is now dead", local);
|
||||||
|
|
||||||
// It is entirely okay for this local to be already dead (at least that's how we currently generate MIR)
|
// If the local is already dead, this is a NOP.
|
||||||
let old = mem::replace(&mut self.frame_mut().locals[local].value, LocalValue::Dead);
|
let old = mem::replace(&mut self.frame_mut().locals[local].value, LocalValue::Dead);
|
||||||
self.deallocate_local(old)?;
|
self.deallocate_local(old)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|
|
@ -361,16 +361,19 @@ pub enum StatementKind<'tcx> {
|
||||||
/// At any point during the execution of a function, each local is either allocated or
|
/// At any point during the execution of a function, each local is either allocated or
|
||||||
/// unallocated. Except as noted below, all locals except function parameters are initially
|
/// unallocated. Except as noted below, all locals except function parameters are initially
|
||||||
/// unallocated. `StorageLive` statements cause memory to be allocated for the local while
|
/// unallocated. `StorageLive` statements cause memory to be allocated for the local while
|
||||||
/// `StorageDead` statements cause the memory to be freed. Using a local in any way (not only
|
/// `StorageDead` statements cause the memory to be freed. In other words,
|
||||||
/// reading/writing from it) while it is unallocated is UB.
|
/// `StorageLive`/`StorageDead` act like the heap operations `allocate`/`deallocate`, but for
|
||||||
|
/// stack-allocated local variables. Using a local in any way (not only reading/writing from it)
|
||||||
|
/// while it is unallocated is UB.
|
||||||
///
|
///
|
||||||
/// Some locals have no `StorageLive` or `StorageDead` statements within the entire MIR body.
|
/// Some locals have no `StorageLive` or `StorageDead` statements within the entire MIR body.
|
||||||
/// These locals are implicitly allocated for the full duration of the function. There is a
|
/// These locals are implicitly allocated for the full duration of the function. There is a
|
||||||
/// convenience method at `rustc_mir_dataflow::storage::always_storage_live_locals` for
|
/// convenience method at `rustc_mir_dataflow::storage::always_storage_live_locals` for
|
||||||
/// computing these locals.
|
/// computing these locals.
|
||||||
///
|
///
|
||||||
/// If the local is already allocated, calling `StorageLive` again is UB. However, for an
|
/// If the local is already allocated, calling `StorageLive` again will implicitly free the
|
||||||
/// unallocated local an additional `StorageDead` all is simply a nop.
|
/// local and then allocate fresh uninitilized memory. If a local is already deallocated,
|
||||||
|
/// calling `StorageDead` again is a NOP.
|
||||||
StorageLive(Local),
|
StorageLive(Local),
|
||||||
|
|
||||||
/// See `StorageLive` above.
|
/// See `StorageLive` above.
|
||||||
|
|
14
src/tools/miri/tests/fail/storage-live-dead-var.rs
Normal file
14
src/tools/miri/tests/fail/storage-live-dead-var.rs
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
#![feature(core_intrinsics, custom_mir)]
|
||||||
|
use std::intrinsics::mir::*;
|
||||||
|
|
||||||
|
#[custom_mir(dialect = "runtime")]
|
||||||
|
fn main() {
|
||||||
|
mir! {
|
||||||
|
let val: i32;
|
||||||
|
{
|
||||||
|
val = 42; //~ERROR: accessing a dead local variable
|
||||||
|
StorageLive(val); // too late... (but needs to be here to make `val` not implicitly live)
|
||||||
|
Return()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
15
src/tools/miri/tests/fail/storage-live-dead-var.stderr
Normal file
15
src/tools/miri/tests/fail/storage-live-dead-var.stderr
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
error: Undefined Behavior: accessing a dead local variable
|
||||||
|
--> $DIR/storage-live-dead-var.rs:LL:CC
|
||||||
|
|
|
||||||
|
LL | val = 42;
|
||||||
|
| ^^^^^^^^ accessing a dead local variable
|
||||||
|
|
|
||||||
|
= help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
|
||||||
|
= help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information
|
||||||
|
= note: BACKTRACE:
|
||||||
|
= note: inside `main` at $DIR/storage-live-dead-var.rs:LL:CC
|
||||||
|
|
||||||
|
note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace
|
||||||
|
|
||||||
|
error: aborting due to 1 previous error
|
||||||
|
|
17
src/tools/miri/tests/fail/storage-live-resets-var.rs
Normal file
17
src/tools/miri/tests/fail/storage-live-resets-var.rs
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
#![feature(core_intrinsics, custom_mir)]
|
||||||
|
use std::intrinsics::mir::*;
|
||||||
|
|
||||||
|
#[custom_mir(dialect = "runtime")]
|
||||||
|
fn main() {
|
||||||
|
mir! {
|
||||||
|
let val: i32;
|
||||||
|
let _val2: i32;
|
||||||
|
{
|
||||||
|
StorageLive(val);
|
||||||
|
val = 42;
|
||||||
|
StorageLive(val); // reset val to `uninit`
|
||||||
|
_val2 = val; //~ERROR: uninitialized
|
||||||
|
Return()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
15
src/tools/miri/tests/fail/storage-live-resets-var.stderr
Normal file
15
src/tools/miri/tests/fail/storage-live-resets-var.stderr
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
error: Undefined Behavior: constructing invalid value: encountered uninitialized memory, but expected an integer
|
||||||
|
--> $DIR/storage-live-resets-var.rs:LL:CC
|
||||||
|
|
|
||||||
|
LL | _val2 = val;
|
||||||
|
| ^^^^^^^^^^^ constructing invalid value: encountered uninitialized memory, but expected an integer
|
||||||
|
|
|
||||||
|
= help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
|
||||||
|
= help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information
|
||||||
|
= note: BACKTRACE:
|
||||||
|
= note: inside `main` at $DIR/storage-live-resets-var.rs:LL:CC
|
||||||
|
|
||||||
|
note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace
|
||||||
|
|
||||||
|
error: aborting due to 1 previous error
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue