Skip to content

Commit 93f85f5

Browse files
committed
Consider indirect mutation during const qualification dataflow
Previously a local would be qualified if either one of two separate data flow computations indicated so. First determined if a local could contain the qualif, but ignored any forms of indirect mutation. Second determined if a local could be mutably borrowed (and so indirectly mutated), but which in turn ignored the qualif. The end result was incorrect because the effect of indirect mutation was effectivelly ignored in the all but the final stage of computation. In the new implementation the indirect mutation is directly incorporated into the qualif data flow. The local variable becomes immediately qualified once it is mutably borrowed and borrowed place type can contain the qualif. In general we will now reject additional programs, program that were prevously unintentionally accepted. There are also some cases which are now accepted but were previously rejected, because previous implementation didn't consider whether borrowed place could have the qualif under the consideration.
1 parent 17e13b5 commit 93f85f5

File tree

5 files changed

+250
-63
lines changed

5 files changed

+250
-63
lines changed

compiler/rustc_const_eval/src/transform/check_consts/check.rs

+4-38
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@ use rustc_middle::ty::cast::CastTy;
1212
use rustc_middle::ty::subst::{GenericArgKind, InternalSubsts};
1313
use rustc_middle::ty::{self, adjustment::PointerCast, Instance, InstanceDef, Ty, TyCtxt};
1414
use rustc_middle::ty::{Binder, TraitPredicate, TraitRef};
15-
use rustc_mir_dataflow::impls::MaybeMutBorrowedLocals;
1615
use rustc_mir_dataflow::{self, Analysis};
1716
use rustc_span::{sym, Span, Symbol};
1817
use rustc_trait_selection::traits::error_reporting::InferCtxtExt;
@@ -27,12 +26,6 @@ use super::resolver::FlowSensitiveAnalysis;
2726
use super::{is_lang_panic_fn, is_lang_special_const_fn, ConstCx, Qualif};
2827
use crate::const_eval::is_unstable_const_fn;
2928

30-
// We are using `MaybeMutBorrowedLocals` as a proxy for whether an item may have been mutated
31-
// through a pointer prior to the given point. This is okay even though `MaybeMutBorrowedLocals`
32-
// kills locals upon `StorageDead` because a local will never be used after a `StorageDead`.
33-
type IndirectlyMutableResults<'mir, 'tcx> =
34-
rustc_mir_dataflow::ResultsCursor<'mir, 'tcx, MaybeMutBorrowedLocals<'mir, 'tcx>>;
35-
3629
type QualifResults<'mir, 'tcx, Q> =
3730
rustc_mir_dataflow::ResultsCursor<'mir, 'tcx, FlowSensitiveAnalysis<'mir, 'mir, 'tcx, Q>>;
3831

@@ -41,36 +34,9 @@ pub struct Qualifs<'mir, 'tcx> {
4134
has_mut_interior: Option<QualifResults<'mir, 'tcx, HasMutInterior>>,
4235
needs_drop: Option<QualifResults<'mir, 'tcx, NeedsDrop>>,
4336
needs_non_const_drop: Option<QualifResults<'mir, 'tcx, NeedsNonConstDrop>>,
44-
indirectly_mutable: Option<IndirectlyMutableResults<'mir, 'tcx>>,
4537
}
4638

4739
impl Qualifs<'mir, 'tcx> {
48-
pub fn indirectly_mutable(
49-
&mut self,
50-
ccx: &'mir ConstCx<'mir, 'tcx>,
51-
local: Local,
52-
location: Location,
53-
) -> bool {
54-
let indirectly_mutable = self.indirectly_mutable.get_or_insert_with(|| {
55-
let ConstCx { tcx, body, param_env, .. } = *ccx;
56-
57-
// We can use `unsound_ignore_borrow_on_drop` here because custom drop impls are not
58-
// allowed in a const.
59-
//
60-
// FIXME(ecstaticmorse): Someday we want to allow custom drop impls. How do we do this
61-
// without breaking stable code?
62-
MaybeMutBorrowedLocals::mut_borrows_only(tcx, &body, param_env)
63-
.unsound_ignore_borrow_on_drop()
64-
.into_engine(tcx, &body)
65-
.pass_name("const_qualification")
66-
.iterate_to_fixpoint()
67-
.into_results_cursor(&body)
68-
});
69-
70-
indirectly_mutable.seek_before_primary_effect(location);
71-
indirectly_mutable.get().contains(local)
72-
}
73-
7440
/// Returns `true` if `local` is `NeedsDrop` at the given `Location`.
7541
///
7642
/// Only updates the cursor if absolutely necessary
@@ -95,7 +61,7 @@ impl Qualifs<'mir, 'tcx> {
9561
});
9662

9763
needs_drop.seek_before_primary_effect(location);
98-
needs_drop.get().contains(local) || self.indirectly_mutable(ccx, local, location)
64+
needs_drop.get().contains(local)
9965
}
10066

10167
/// Returns `true` if `local` is `NeedsNonConstDrop` at the given `Location`.
@@ -122,7 +88,7 @@ impl Qualifs<'mir, 'tcx> {
12288
});
12389

12490
needs_non_const_drop.seek_before_primary_effect(location);
125-
needs_non_const_drop.get().contains(local) || self.indirectly_mutable(ccx, local, location)
91+
needs_non_const_drop.get().contains(local)
12692
}
12793

12894
/// Returns `true` if `local` is `HasMutInterior` at the given `Location`.
@@ -149,7 +115,7 @@ impl Qualifs<'mir, 'tcx> {
149115
});
150116

151117
has_mut_interior.seek_before_primary_effect(location);
152-
has_mut_interior.get().contains(local) || self.indirectly_mutable(ccx, local, location)
118+
has_mut_interior.get().contains(local)
153119
}
154120

155121
fn in_return_place(
@@ -195,7 +161,7 @@ impl Qualifs<'mir, 'tcx> {
195161
.into_results_cursor(&ccx.body);
196162

197163
cursor.seek_after_primary_effect(return_loc);
198-
cursor.contains(RETURN_PLACE)
164+
cursor.get().contains(RETURN_PLACE)
199165
}
200166
};
201167

compiler/rustc_const_eval/src/transform/check_consts/resolver.rs

+153-25
Original file line numberDiff line numberDiff line change
@@ -5,39 +5,44 @@
55
use rustc_index::bit_set::BitSet;
66
use rustc_middle::mir::visit::Visitor;
77
use rustc_middle::mir::{self, BasicBlock, Local, Location, Statement, StatementKind};
8+
use rustc_mir_dataflow::fmt::DebugWithContext;
9+
use rustc_mir_dataflow::JoinSemiLattice;
10+
use rustc_span::DUMMY_SP;
811

12+
use std::fmt;
913
use std::marker::PhantomData;
1014

1115
use super::{qualifs, ConstCx, Qualif};
1216

1317
/// A `Visitor` that propagates qualifs between locals. This defines the transfer function of
1418
/// `FlowSensitiveAnalysis`.
1519
///
16-
/// This transfer does nothing when encountering an indirect assignment. Consumers should rely on
17-
/// the `MaybeMutBorrowedLocals` dataflow pass to see if a `Local` may have become qualified via
18-
/// an indirect assignment or function call.
20+
/// To account for indirect assignments, data flow conservatively assumes that local becomes
21+
/// qualified immediately after it is borrowed or its address escapes. The borrow must allow for
22+
/// mutation, which includes shared borrows of places with interior mutability. The type of
23+
/// borrowed place must contain the qualif.
1924
struct TransferFunction<'a, 'mir, 'tcx, Q> {
2025
ccx: &'a ConstCx<'mir, 'tcx>,
21-
qualifs_per_local: &'a mut BitSet<Local>,
22-
26+
state: &'a mut State,
2327
_qualif: PhantomData<Q>,
2428
}
2529

2630
impl<Q> TransferFunction<'a, 'mir, 'tcx, Q>
2731
where
2832
Q: Qualif,
2933
{
30-
fn new(ccx: &'a ConstCx<'mir, 'tcx>, qualifs_per_local: &'a mut BitSet<Local>) -> Self {
31-
TransferFunction { ccx, qualifs_per_local, _qualif: PhantomData }
34+
fn new(ccx: &'a ConstCx<'mir, 'tcx>, state: &'a mut State) -> Self {
35+
TransferFunction { ccx, state, _qualif: PhantomData }
3236
}
3337

3438
fn initialize_state(&mut self) {
35-
self.qualifs_per_local.clear();
39+
self.state.qualif.clear();
40+
self.state.borrow.clear();
3641

3742
for arg in self.ccx.body.args_iter() {
3843
let arg_ty = self.ccx.body.local_decls[arg].ty;
3944
if Q::in_any_value_of_ty(self.ccx, arg_ty) {
40-
self.qualifs_per_local.insert(arg);
45+
self.state.qualif.insert(arg);
4146
}
4247
}
4348
}
@@ -47,15 +52,15 @@ where
4752

4853
match (value, place.as_ref()) {
4954
(true, mir::PlaceRef { local, .. }) => {
50-
self.qualifs_per_local.insert(local);
55+
self.state.qualif.insert(local);
5156
}
5257

5358
// For now, we do not clear the qualif if a local is overwritten in full by
5459
// an unqualified rvalue (e.g. `y = 5`). This is to be consistent
5560
// with aggregates where we overwrite all fields with assignments, which would not
5661
// get this feature.
5762
(false, mir::PlaceRef { local: _, projection: &[] }) => {
58-
// self.qualifs_per_local.remove(*local);
63+
// self.state.qualif.remove(*local);
5964
}
6065

6166
_ => {}
@@ -78,6 +83,29 @@ where
7883
self.assign_qualif_direct(&return_place, qualif);
7984
}
8085
}
86+
87+
fn address_of_allows_mutation(&self, mt: mir::Mutability, place: mir::Place<'tcx>) -> bool {
88+
match mt {
89+
mir::Mutability::Mut => true,
90+
mir::Mutability::Not => self.shared_borrow_allows_mutation(place),
91+
}
92+
}
93+
94+
fn ref_allows_mutation(&self, kind: mir::BorrowKind, place: mir::Place<'tcx>) -> bool {
95+
match kind {
96+
mir::BorrowKind::Mut { .. } => true,
97+
mir::BorrowKind::Shared | mir::BorrowKind::Shallow | mir::BorrowKind::Unique => {
98+
self.shared_borrow_allows_mutation(place)
99+
}
100+
}
101+
}
102+
103+
fn shared_borrow_allows_mutation(&self, place: mir::Place<'tcx>) -> bool {
104+
!place
105+
.ty(self.ccx.body, self.ccx.tcx)
106+
.ty
107+
.is_freeze(self.ccx.tcx.at(DUMMY_SP), self.ccx.param_env)
108+
}
81109
}
82110

83111
impl<Q> Visitor<'tcx> for TransferFunction<'_, '_, 'tcx, Q>
@@ -95,7 +123,12 @@ where
95123
// it no longer needs to be dropped.
96124
if let mir::Operand::Move(place) = operand {
97125
if let Some(local) = place.as_local() {
98-
self.qualifs_per_local.remove(local);
126+
// For backward compatibility with the MaybeMutBorrowedLocals used in an earlier
127+
// implementation we retain qualif if a local had been borrowed before. This might
128+
// not be strictly necessary since the local is no longer initialized.
129+
if !self.state.borrow.contains(local) {
130+
self.state.qualif.remove(local);
131+
}
99132
}
100133
}
101134
}
@@ -106,11 +139,8 @@ where
106139
rvalue: &mir::Rvalue<'tcx>,
107140
location: Location,
108141
) {
109-
let qualif = qualifs::in_rvalue::<Q, _>(
110-
self.ccx,
111-
&mut |l| self.qualifs_per_local.contains(l),
112-
rvalue,
113-
);
142+
let qualif =
143+
qualifs::in_rvalue::<Q, _>(self.ccx, &mut |l| self.state.qualif.contains(l), rvalue);
114144
if !place.is_indirect() {
115145
self.assign_qualif_direct(place, qualif);
116146
}
@@ -120,10 +150,53 @@ where
120150
self.super_assign(place, rvalue, location);
121151
}
122152

153+
fn visit_rvalue(&mut self, rvalue: &mir::Rvalue<'tcx>, location: Location) {
154+
self.super_rvalue(rvalue, location);
155+
156+
match rvalue {
157+
mir::Rvalue::AddressOf(mt, borrowed_place) => {
158+
if !borrowed_place.is_indirect()
159+
&& self.address_of_allows_mutation(*mt, *borrowed_place)
160+
{
161+
let place_ty = borrowed_place.ty(self.ccx.body, self.ccx.tcx).ty;
162+
if Q::in_any_value_of_ty(self.ccx, place_ty) {
163+
self.state.qualif.insert(borrowed_place.local);
164+
self.state.borrow.insert(borrowed_place.local);
165+
}
166+
}
167+
}
168+
169+
mir::Rvalue::Ref(_, kind, borrowed_place) => {
170+
if !borrowed_place.is_indirect() && self.ref_allows_mutation(*kind, *borrowed_place)
171+
{
172+
let place_ty = borrowed_place.ty(self.ccx.body, self.ccx.tcx).ty;
173+
if Q::in_any_value_of_ty(self.ccx, place_ty) {
174+
self.state.qualif.insert(borrowed_place.local);
175+
self.state.borrow.insert(borrowed_place.local);
176+
}
177+
}
178+
}
179+
180+
mir::Rvalue::Cast(..)
181+
| mir::Rvalue::ShallowInitBox(..)
182+
| mir::Rvalue::Use(..)
183+
| mir::Rvalue::ThreadLocalRef(..)
184+
| mir::Rvalue::Repeat(..)
185+
| mir::Rvalue::Len(..)
186+
| mir::Rvalue::BinaryOp(..)
187+
| mir::Rvalue::CheckedBinaryOp(..)
188+
| mir::Rvalue::NullaryOp(..)
189+
| mir::Rvalue::UnaryOp(..)
190+
| mir::Rvalue::Discriminant(..)
191+
| mir::Rvalue::Aggregate(..) => {}
192+
}
193+
}
194+
123195
fn visit_statement(&mut self, statement: &Statement<'tcx>, location: Location) {
124196
match statement.kind {
125197
StatementKind::StorageDead(local) => {
126-
self.qualifs_per_local.remove(local);
198+
self.state.qualif.remove(local);
199+
self.state.borrow.remove(local);
127200
}
128201
_ => self.super_statement(statement, location),
129202
}
@@ -136,7 +209,7 @@ where
136209
if let mir::TerminatorKind::DropAndReplace { value, place, .. } = &terminator.kind {
137210
let qualif = qualifs::in_operand::<Q, _>(
138211
self.ccx,
139-
&mut |l| self.qualifs_per_local.contains(l),
212+
&mut |l| self.state.qualif.contains(l),
140213
value,
141214
);
142215

@@ -145,6 +218,9 @@ where
145218
}
146219
}
147220

221+
// We ignore borrow on drop because custom drop impls are not allowed in consts.
222+
// FIXME: Reconsider if accounting for borrows in drops is necessary for const drop.
223+
148224
// We need to assign qualifs to the dropped location before visiting the operand that
149225
// replaces it since qualifs can be cleared on move.
150226
self.super_terminator(terminator, location);
@@ -165,24 +241,76 @@ where
165241
FlowSensitiveAnalysis { ccx, _qualif: PhantomData }
166242
}
167243

168-
fn transfer_function(
169-
&self,
170-
state: &'a mut BitSet<Local>,
171-
) -> TransferFunction<'a, 'mir, 'tcx, Q> {
244+
fn transfer_function(&self, state: &'a mut State) -> TransferFunction<'a, 'mir, 'tcx, Q> {
172245
TransferFunction::<Q>::new(self.ccx, state)
173246
}
174247
}
175248

249+
#[derive(Clone, Debug, PartialEq, Eq)]
250+
pub(super) struct State {
251+
/// Describes whether a local contains qualif.
252+
pub qualif: BitSet<Local>,
253+
/// Describes whether a local's address escaped and it might become qualified as a result an
254+
/// indirect mutation.
255+
pub borrow: BitSet<Local>,
256+
}
257+
258+
impl State {
259+
#[inline]
260+
pub(super) fn contains(&self, local: Local) -> bool {
261+
self.qualif.contains(local)
262+
}
263+
}
264+
265+
impl<C> DebugWithContext<C> for State {
266+
fn fmt_with(&self, ctxt: &C, f: &mut fmt::Formatter<'_>) -> fmt::Result {
267+
f.write_str("qualif: ")?;
268+
self.qualif.fmt_with(ctxt, f)?;
269+
f.write_str(" borrow: ")?;
270+
self.borrow.fmt_with(ctxt, f)?;
271+
Ok(())
272+
}
273+
274+
fn fmt_diff_with(&self, old: &Self, ctxt: &C, f: &mut fmt::Formatter<'_>) -> fmt::Result {
275+
if self == old {
276+
return Ok(());
277+
}
278+
279+
if self.qualif != old.qualif {
280+
f.write_str("qualif: ")?;
281+
self.qualif.fmt_diff_with(&old.qualif, ctxt, f)?;
282+
f.write_str("\n")?;
283+
}
284+
285+
if self.borrow != old.borrow {
286+
f.write_str("borrow: ")?;
287+
self.qualif.fmt_diff_with(&old.borrow, ctxt, f)?;
288+
f.write_str("\n")?;
289+
}
290+
291+
Ok(())
292+
}
293+
}
294+
295+
impl JoinSemiLattice for State {
296+
fn join(&mut self, other: &Self) -> bool {
297+
self.qualif.join(&other.qualif) || self.borrow.join(&other.borrow)
298+
}
299+
}
300+
176301
impl<Q> rustc_mir_dataflow::AnalysisDomain<'tcx> for FlowSensitiveAnalysis<'_, '_, 'tcx, Q>
177302
where
178303
Q: Qualif,
179304
{
180-
type Domain = BitSet<Local>;
305+
type Domain = State;
181306

182307
const NAME: &'static str = Q::ANALYSIS_NAME;
183308

184309
fn bottom_value(&self, body: &mir::Body<'tcx>) -> Self::Domain {
185-
BitSet::new_empty(body.local_decls.len())
310+
State {
311+
qualif: BitSet::new_empty(body.local_decls.len()),
312+
borrow: BitSet::new_empty(body.local_decls.len()),
313+
}
186314
}
187315

188316
fn initialize_start_block(&self, _body: &mir::Body<'tcx>, state: &mut Self::Domain) {

0 commit comments

Comments
 (0)