Skip to content

Commit 22572d0

Browse files
committed
Auto merge of rust-lang#129508 - RalfJung:transient-locals, r=cjgillot
const checking: properly compute the set of transient locals For const-checking the MIR of a const/static initializer, we have to know the set of "transient" locals. The reason for this is that creating a mutable (or interior mutable) reference to a transient local is "safe" in the sense that this reference cannot possibly end up in the final value of the const -- even if it is turned into a raw pointer and stored in a union, we will see that pointer during interning and reliably reject it as dangling. So far, we determined the set of transient locals as "locals that have a `StorageDead` somewhere". But that's not quite right -- if we had MIR like ```rust StorageLive(_5); StorageDead(_5); StorageLive(_5); // no further storage annotations for _5 ``` Then we'd consider `_5` to be "transient", but it is not actually transient. We do not currently generate such MIR, but I feel uneasy relying on subtle invariants like this. So this PR implements a proper analysis for computing the set of "transient" locals: a local is "transient" if it is guaranteed dead at all `Return` terminators. Cc `@cjgillot`
2 parents f48062e + 74a2142 commit 22572d0

File tree

1 file changed

+32
-18
lines changed
  • compiler/rustc_const_eval/src/check_consts

1 file changed

+32
-18
lines changed

compiler/rustc_const_eval/src/check_consts/check.rs

+32-18
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
//! The `Visitor` responsible for actually checking a `mir::Body` for invalid operations.
22
33
use std::assert_matches::assert_matches;
4+
use std::borrow::Cow;
45
use std::mem;
56
use std::ops::Deref;
67

@@ -15,6 +16,8 @@ use rustc_middle::mir::*;
1516
use rustc_middle::span_bug;
1617
use rustc_middle::ty::adjustment::PointerCoercion;
1718
use rustc_middle::ty::{self, Instance, InstanceKind, Ty, TyCtxt, TypeVisitableExt};
19+
use rustc_mir_dataflow::impls::MaybeStorageLive;
20+
use rustc_mir_dataflow::storage::always_storage_live_locals;
1821
use rustc_mir_dataflow::Analysis;
1922
use rustc_span::{sym, Span, Symbol, DUMMY_SP};
2023
use rustc_trait_selection::error_reporting::InferCtxtErrorExt;
@@ -188,8 +191,9 @@ pub struct Checker<'mir, 'tcx> {
188191
/// The span of the current statement.
189192
span: Span,
190193

191-
/// A set that stores for each local whether it has a `StorageDead` for it somewhere.
192-
local_has_storage_dead: Option<BitSet<Local>>,
194+
/// A set that stores for each local whether it is "transient", i.e. guaranteed to be dead
195+
/// when this MIR body returns.
196+
transient_locals: Option<BitSet<Local>>,
193197

194198
error_emitted: Option<ErrorGuaranteed>,
195199
secondary_errors: Vec<Diag<'tcx>>,
@@ -209,7 +213,7 @@ impl<'mir, 'tcx> Checker<'mir, 'tcx> {
209213
span: ccx.body.span,
210214
ccx,
211215
qualifs: Default::default(),
212-
local_has_storage_dead: None,
216+
transient_locals: None,
213217
error_emitted: None,
214218
secondary_errors: Vec::new(),
215219
}
@@ -264,23 +268,33 @@ impl<'mir, 'tcx> Checker<'mir, 'tcx> {
264268
}
265269
}
266270

267-
fn local_has_storage_dead(&mut self, local: Local) -> bool {
271+
fn local_is_transient(&mut self, local: Local) -> bool {
268272
let ccx = self.ccx;
269-
self.local_has_storage_dead
273+
self.transient_locals
270274
.get_or_insert_with(|| {
271-
struct StorageDeads {
272-
locals: BitSet<Local>,
273-
}
274-
impl<'tcx> Visitor<'tcx> for StorageDeads {
275-
fn visit_statement(&mut self, stmt: &Statement<'tcx>, _: Location) {
276-
if let StatementKind::StorageDead(l) = stmt.kind {
277-
self.locals.insert(l);
278-
}
275+
// A local is "transient" if it is guaranteed dead at all `Return`.
276+
// So first compute the say of "maybe live" locals at each program point.
277+
let always_live_locals = &always_storage_live_locals(&ccx.body);
278+
let mut maybe_storage_live =
279+
MaybeStorageLive::new(Cow::Borrowed(always_live_locals))
280+
.into_engine(ccx.tcx, &ccx.body)
281+
.iterate_to_fixpoint()
282+
.into_results_cursor(&ccx.body);
283+
284+
// And then check all `Return` in the MIR, and if a local is "maybe live" at a
285+
// `Return` then it is definitely not transient.
286+
let mut transient = BitSet::new_filled(ccx.body.local_decls.len());
287+
// Make sure to only visit reachable blocks, the dataflow engine can ICE otherwise.
288+
for (bb, data) in traversal::reachable(&ccx.body) {
289+
if matches!(data.terminator().kind, TerminatorKind::Return) {
290+
let location = ccx.body.terminator_loc(bb);
291+
maybe_storage_live.seek_after_primary_effect(location);
292+
// If a local may be live here, it is definitely not transient.
293+
transient.subtract(maybe_storage_live.get());
279294
}
280295
}
281-
let mut v = StorageDeads { locals: BitSet::new_empty(ccx.body.local_decls.len()) };
282-
v.visit_body(ccx.body);
283-
v.locals
296+
297+
transient
284298
})
285299
.contains(local)
286300
}
@@ -375,7 +389,7 @@ impl<'mir, 'tcx> Checker<'mir, 'tcx> {
375389
// `StorageDead` in every control flow path leading to a `return` terminator.
376390
// The good news is that interning will detect if any unexpected mutable
377391
// pointer slips through.
378-
if place.is_indirect() || self.local_has_storage_dead(place.local) {
392+
if place.is_indirect() || self.local_is_transient(place.local) {
379393
self.check_op(ops::TransientMutBorrow(kind));
380394
} else {
381395
self.check_op(ops::MutBorrow(kind));
@@ -526,7 +540,7 @@ impl<'tcx> Visitor<'tcx> for Checker<'_, 'tcx> {
526540
// `StorageDead` in every control flow path leading to a `return` terminator.
527541
// The good news is that interning will detect if any unexpected mutable
528542
// pointer slips through.
529-
if self.local_has_storage_dead(place.local) {
543+
if self.local_is_transient(place.local) {
530544
self.check_op(ops::TransientCellBorrow);
531545
} else {
532546
self.check_op(ops::CellBorrow);

0 commit comments

Comments
 (0)