Skip to content

Commit f32f569

Browse files
authored
Rollup merge of #66654 - ecstatic-morse:check-consts-ref, r=eddyb,matthewjasper
Handle const-checks for `&mut` outside of `HasMutInterior` Addresses [this comment](#64470 (comment)). Const-checking relied on `HasMutInterior` to forbid `&mut` in a const context. This was strange because all we needed to do was look for an `Rvalue::Ref` with a certain `BorrowKind`, whereas the `Qualif` traits are specifically meant to get the qualifs for a *value*. This PR removes that logic from `HasMutInterior` and moves it into `check_consts::Validator`. As a result, we can now properly handle qualifications for `static`s, which had to be ignored previously since you can e.g. borrow a static `Cell` from another `static`. We also remove the `derived_from_illegal_borrow` logic, since it is no longer necessary; we give good errors for subsequent reborrows/borrows of illegal borrows.
2 parents 8438770 + ccb4eed commit f32f569

File tree

9 files changed

+145
-181
lines changed

9 files changed

+145
-181
lines changed

src/librustc_mir/lib.rs

+1
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ Rust MIR: a lowered representation of Rust. Also: an experiment!
2929
#![feature(stmt_expr_attributes)]
3030
#![feature(bool_to_option)]
3131
#![feature(trait_alias)]
32+
#![feature(matches_macro)]
3233

3334
#![recursion_limit="256"]
3435

src/librustc_mir/transform/check_consts/qualifs.rs

+5-32
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ use rustc::ty::{self, Ty};
55
use rustc::hir::def_id::DefId;
66
use syntax_pos::DUMMY_SP;
77

8-
use super::{ConstKind, Item as ConstCx};
8+
use super::Item as ConstCx;
99

1010
pub fn in_any_value_of_ty(cx: &ConstCx<'_, 'tcx>, ty: Ty<'tcx>) -> ConstQualifs {
1111
ConstQualifs {
@@ -33,9 +33,10 @@ pub trait Qualif {
3333
/// of the type.
3434
fn in_any_value_of_ty(_cx: &ConstCx<'_, 'tcx>, _ty: Ty<'tcx>) -> bool;
3535

36-
fn in_static(_cx: &ConstCx<'_, 'tcx>, _def_id: DefId) -> bool {
37-
// FIXME(eddyb) should we do anything here for value properties?
38-
false
36+
fn in_static(cx: &ConstCx<'_, 'tcx>, def_id: DefId) -> bool {
37+
// `mir_const_qualif` does return the qualifs in the final value of a `static`, so we could
38+
// use value-based qualification here, but we shouldn't do this without a good reason.
39+
Self::in_any_value_of_ty(cx, cx.tcx.type_of(def_id))
3940
}
4041

4142
fn in_projection_structurally(
@@ -217,34 +218,6 @@ impl Qualif for HasMutInterior {
217218
rvalue: &Rvalue<'tcx>,
218219
) -> bool {
219220
match *rvalue {
220-
// Returning `true` for `Rvalue::Ref` indicates the borrow isn't
221-
// allowed in constants (and the `Checker` will error), and/or it
222-
// won't be promoted, due to `&mut ...` or interior mutability.
223-
Rvalue::Ref(_, kind, ref place) => {
224-
let ty = place.ty(cx.body, cx.tcx).ty;
225-
226-
if let BorrowKind::Mut { .. } = kind {
227-
// In theory, any zero-sized value could be borrowed
228-
// mutably without consequences.
229-
match ty.kind {
230-
// Inside a `static mut`, &mut [...] is also allowed.
231-
| ty::Array(..)
232-
| ty::Slice(_)
233-
if cx.const_kind == Some(ConstKind::StaticMut)
234-
=> {},
235-
236-
// FIXME(eddyb): We only return false for `&mut []` outside a const
237-
// context which seems unnecessary given that this is merely a ZST.
238-
| ty::Array(_, len)
239-
if len.try_eval_usize(cx.tcx, cx.param_env) == Some(0)
240-
&& cx.const_kind == None
241-
=> {},
242-
243-
_ => return true,
244-
}
245-
}
246-
}
247-
248221
Rvalue::Aggregate(ref kind, _) => {
249222
if let AggregateKind::Adt(def, ..) = **kind {
250223
if Some(def.did) == cx.tcx.lang_items().unsafe_cell_type() {

src/librustc_mir/transform/check_consts/validation.rs

+95-97
Original file line numberDiff line numberDiff line change
@@ -23,13 +23,6 @@ use super::qualifs::{self, HasMutInterior, NeedsDrop};
2323
use super::resolver::FlowSensitiveAnalysis;
2424
use super::{ConstKind, Item, Qualif, is_lang_panic_fn};
2525

26-
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
27-
pub enum CheckOpResult {
28-
Forbidden,
29-
Unleashed,
30-
Allowed,
31-
}
32-
3326
pub type IndirectlyMutableResults<'mir, 'tcx> =
3427
old_dataflow::DataflowResultsCursor<'mir, 'tcx, IndirectlyMutableLocals<'mir, 'tcx>>;
3528

@@ -149,17 +142,6 @@ pub struct Validator<'a, 'mir, 'tcx> {
149142

150143
/// The span of the current statement.
151144
span: Span,
152-
153-
/// True if the local was assigned the result of an illegal borrow (`ops::MutBorrow`).
154-
///
155-
/// This is used to hide errors from {re,}borrowing the newly-assigned local, instead pointing
156-
/// the user to the place where the illegal borrow occurred. This set is only populated once an
157-
/// error has been emitted, so it will never cause an erroneous `mir::Body` to pass validation.
158-
///
159-
/// FIXME(ecstaticmorse): assert at the end of checking that if `tcx.has_errors() == false`,
160-
/// this set is empty. Note that if we start removing locals from
161-
/// `derived_from_illegal_borrow`, just checking at the end won't be enough.
162-
derived_from_illegal_borrow: BitSet<Local>,
163145
}
164146

165147
impl Deref for Validator<'_, 'mir, 'tcx> {
@@ -213,7 +195,6 @@ impl Validator<'a, 'mir, 'tcx> {
213195
span: item.body.span,
214196
item,
215197
qualifs,
216-
derived_from_illegal_borrow: BitSet::new_empty(item.body.local_decls.len()),
217198
}
218199
}
219200

@@ -258,15 +239,15 @@ impl Validator<'a, 'mir, 'tcx> {
258239
}
259240

260241
/// Emits an error at the given `span` if an expression cannot be evaluated in the current
261-
/// context. Returns `Forbidden` if an error was emitted.
262-
pub fn check_op_spanned<O>(&mut self, op: O, span: Span) -> CheckOpResult
242+
/// context.
243+
pub fn check_op_spanned<O>(&mut self, op: O, span: Span)
263244
where
264245
O: NonConstOp
265246
{
266247
trace!("check_op: op={:?}", op);
267248

268249
if op.is_allowed_in_item(self) {
269-
return CheckOpResult::Allowed;
250+
return;
270251
}
271252

272253
// If an operation is supported in miri (and is not already controlled by a feature gate) it
@@ -276,20 +257,19 @@ impl Validator<'a, 'mir, 'tcx> {
276257

277258
if is_unleashable && self.tcx.sess.opts.debugging_opts.unleash_the_miri_inside_of_you {
278259
self.tcx.sess.span_warn(span, "skipping const checks");
279-
return CheckOpResult::Unleashed;
260+
return;
280261
}
281262

282263
op.emit_error(self, span);
283-
CheckOpResult::Forbidden
284264
}
285265

286266
/// Emits an error if an expression cannot be evaluated in the current context.
287-
pub fn check_op(&mut self, op: impl NonConstOp) -> CheckOpResult {
267+
pub fn check_op(&mut self, op: impl NonConstOp) {
288268
let span = self.span;
289269
self.check_op_spanned(op, span)
290270
}
291271

292-
fn check_static(&mut self, def_id: DefId, span: Span) -> CheckOpResult {
272+
fn check_static(&mut self, def_id: DefId, span: Span) {
293273
let is_thread_local = self.tcx.has_attr(def_id, sym::thread_local);
294274
if is_thread_local {
295275
self.check_op_spanned(ops::ThreadLocalAccess, span)
@@ -322,20 +302,9 @@ impl Visitor<'tcx> for Validator<'_, 'mir, 'tcx> {
322302
fn visit_rvalue(&mut self, rvalue: &Rvalue<'tcx>, location: Location) {
323303
trace!("visit_rvalue: rvalue={:?} location={:?}", rvalue, location);
324304

325-
// Check nested operands and places.
305+
// Special-case reborrows to be more like a copy of a reference.
326306
if let Rvalue::Ref(_, kind, ref place) = *rvalue {
327-
// Special-case reborrows to be more like a copy of a reference.
328-
let mut reborrow_place = None;
329-
if let &[ref proj_base @ .., elem] = place.projection.as_ref() {
330-
if elem == ProjectionElem::Deref {
331-
let base_ty = Place::ty_from(&place.base, proj_base, self.body, self.tcx).ty;
332-
if let ty::Ref(..) = base_ty.kind {
333-
reborrow_place = Some(proj_base);
334-
}
335-
}
336-
}
337-
338-
if let Some(proj) = reborrow_place {
307+
if let Some(reborrowed_proj) = place_as_reborrow(self.tcx, self.body, place) {
339308
let ctx = match kind {
340309
BorrowKind::Shared => PlaceContext::NonMutatingUse(
341310
NonMutatingUseContext::SharedBorrow,
@@ -351,14 +320,13 @@ impl Visitor<'tcx> for Validator<'_, 'mir, 'tcx> {
351320
),
352321
};
353322
self.visit_place_base(&place.base, ctx, location);
354-
self.visit_projection(&place.base, proj, ctx, location);
355-
} else {
356-
self.super_rvalue(rvalue, location);
323+
self.visit_projection(&place.base, reborrowed_proj, ctx, location);
324+
return;
357325
}
358-
} else {
359-
self.super_rvalue(rvalue, location);
360326
}
361327

328+
self.super_rvalue(rvalue, location);
329+
362330
match *rvalue {
363331
Rvalue::Use(_) |
364332
Rvalue::Repeat(..) |
@@ -369,9 +337,58 @@ impl Visitor<'tcx> for Validator<'_, 'mir, 'tcx> {
369337
Rvalue::Cast(CastKind::Pointer(_), ..) |
370338
Rvalue::Discriminant(..) |
371339
Rvalue::Len(_) |
372-
Rvalue::Ref(..) |
373340
Rvalue::Aggregate(..) => {}
374341

342+
| Rvalue::Ref(_, kind @ BorrowKind::Mut { .. }, ref place)
343+
| Rvalue::Ref(_, kind @ BorrowKind::Unique, ref place)
344+
=> {
345+
let ty = place.ty(self.body, self.tcx).ty;
346+
let is_allowed = match ty.kind {
347+
// Inside a `static mut`, `&mut [...]` is allowed.
348+
ty::Array(..) | ty::Slice(_) if self.const_kind() == ConstKind::StaticMut
349+
=> true,
350+
351+
// FIXME(ecstaticmorse): We could allow `&mut []` inside a const context given
352+
// that this is merely a ZST and it is already eligible for promotion.
353+
// This may require an RFC?
354+
/*
355+
ty::Array(_, len) if len.try_eval_usize(cx.tcx, cx.param_env) == Some(0)
356+
=> true,
357+
*/
358+
359+
_ => false,
360+
};
361+
362+
if !is_allowed {
363+
self.check_op(ops::MutBorrow(kind));
364+
}
365+
}
366+
367+
// At the moment, `PlaceBase::Static` is only used for promoted MIR.
368+
| Rvalue::Ref(_, BorrowKind::Shared, ref place)
369+
| Rvalue::Ref(_, BorrowKind::Shallow, ref place)
370+
if matches!(place.base, PlaceBase::Static(_))
371+
=> bug!("Saw a promoted during const-checking, which must run before promotion"),
372+
373+
| Rvalue::Ref(_, kind @ BorrowKind::Shared, ref place)
374+
| Rvalue::Ref(_, kind @ BorrowKind::Shallow, ref place)
375+
=> {
376+
// FIXME: Change the `in_*` methods to take a `FnMut` so we don't have to manually
377+
// seek the cursors beforehand.
378+
self.qualifs.has_mut_interior.cursor.seek_before(location);
379+
self.qualifs.indirectly_mutable.seek(location);
380+
381+
let borrowed_place_has_mut_interior = HasMutInterior::in_place(
382+
&self.item,
383+
&|local| self.qualifs.has_mut_interior_eager_seek(local),
384+
place.as_ref(),
385+
);
386+
387+
if borrowed_place_has_mut_interior {
388+
self.check_op(ops::MutBorrow(kind));
389+
}
390+
}
391+
375392
Rvalue::Cast(CastKind::Misc, ref operand, cast_ty) => {
376393
let operand_ty = operand.ty(self.body, self.tcx);
377394
let cast_in = CastTy::from_ty(operand_ty).expect("bad input type for cast");
@@ -436,58 +453,6 @@ impl Visitor<'tcx> for Validator<'_, 'mir, 'tcx> {
436453
}
437454
}
438455

439-
fn visit_assign(&mut self, dest: &Place<'tcx>, rvalue: &Rvalue<'tcx>, location: Location) {
440-
trace!("visit_assign: dest={:?} rvalue={:?} location={:?}", dest, rvalue, location);
441-
442-
// Error on mutable borrows or shared borrows of values with interior mutability.
443-
//
444-
// This replicates the logic at the start of `assign` in the old const checker. Note that
445-
// it depends on `HasMutInterior` being set for mutable borrows as well as values with
446-
// interior mutability.
447-
if let Rvalue::Ref(_, kind, ref borrowed_place) = *rvalue {
448-
// FIXME: Change the `in_*` methods to take a `FnMut` so we don't have to manually seek
449-
// the cursors beforehand.
450-
self.qualifs.has_mut_interior.cursor.seek_before(location);
451-
self.qualifs.indirectly_mutable.seek(location);
452-
453-
let rvalue_has_mut_interior = HasMutInterior::in_rvalue(
454-
&self.item,
455-
&|local| self.qualifs.has_mut_interior_eager_seek(local),
456-
rvalue,
457-
);
458-
459-
if rvalue_has_mut_interior {
460-
let is_derived_from_illegal_borrow = match borrowed_place.as_local() {
461-
// If an unprojected local was borrowed and its value was the result of an
462-
// illegal borrow, suppress this error and mark the result of this borrow as
463-
// illegal as well.
464-
Some(borrowed_local)
465-
if self.derived_from_illegal_borrow.contains(borrowed_local) =>
466-
{
467-
true
468-
}
469-
470-
// Otherwise proceed normally: check the legality of a mutable borrow in this
471-
// context.
472-
_ => self.check_op(ops::MutBorrow(kind)) == CheckOpResult::Forbidden,
473-
};
474-
475-
// When the target of the assignment is a local with no projections, mark it as
476-
// derived from an illegal borrow if necessary.
477-
//
478-
// FIXME: should we also clear `derived_from_illegal_borrow` when a local is
479-
// assigned a new value?
480-
if is_derived_from_illegal_borrow {
481-
if let Some(dest) = dest.as_local() {
482-
self.derived_from_illegal_borrow.insert(dest);
483-
}
484-
}
485-
}
486-
}
487-
488-
self.super_assign(dest, rvalue, location);
489-
}
490-
491456
fn visit_projection_elem(
492457
&mut self,
493458
place_base: &PlaceBase<'tcx>,
@@ -724,3 +689,36 @@ fn check_return_ty_is_sync(tcx: TyCtxt<'tcx>, body: &Body<'tcx>, hir_id: HirId)
724689
}
725690
});
726691
}
692+
693+
fn place_as_reborrow(
694+
tcx: TyCtxt<'tcx>,
695+
body: &Body<'tcx>,
696+
place: &'a Place<'tcx>,
697+
) -> Option<&'a [PlaceElem<'tcx>]> {
698+
place
699+
.projection
700+
.split_last()
701+
.and_then(|(outermost, inner)| {
702+
if outermost != &ProjectionElem::Deref {
703+
return None;
704+
}
705+
706+
// A borrow of a `static` also looks like `&(*_1)` in the MIR, but `_1` is a `const`
707+
// that points to the allocation for the static. Don't treat these as reborrows.
708+
if let PlaceBase::Local(local) = place.base {
709+
if body.local_decls[local].is_ref_to_static() {
710+
return None;
711+
}
712+
}
713+
714+
// Ensure the type being derefed is a reference and not a raw pointer.
715+
//
716+
// This is sufficient to prevent an access to a `static mut` from being marked as a
717+
// reborrow, even if the check above were to disappear.
718+
let inner_ty = Place::ty_from(&place.base, inner, body, tcx).ty;
719+
match inner_ty.kind {
720+
ty::Ref(..) => Some(inner),
721+
_ => None,
722+
}
723+
})
724+
}

src/test/ui/consts/const-multi-ref.rs

+16-3
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,24 @@
1-
const _X: i32 = {
1+
// Ensure that we point the user to the erroneous borrow but not to any subsequent borrows of that
2+
// initial one.
3+
4+
const _: i32 = {
25
let mut a = 5;
3-
let p = &mut a; //~ ERROR references in constants may only refer to immutable values
6+
let p = &mut a; //~ ERROR references in constants may only refer to immutable values
47

5-
let reborrow = {p}; //~ ERROR references in constants may only refer to immutable values
8+
let reborrow = {p};
69
let pp = &reborrow;
710
let ppp = &pp;
811
***ppp
912
};
1013

14+
const _: std::cell::Cell<i32> = {
15+
let mut a = std::cell::Cell::new(5);
16+
let p = &a; //~ ERROR cannot borrow a constant which may contain interior mutability
17+
18+
let reborrow = {p};
19+
let pp = &reborrow;
20+
let ppp = &pp;
21+
a
22+
};
23+
1124
fn main() {}
+7-6
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,16 @@
11
error[E0017]: references in constants may only refer to immutable values
2-
--> $DIR/const-multi-ref.rs:3:13
2+
--> $DIR/const-multi-ref.rs:6:13
33
|
44
LL | let p = &mut a;
55
| ^^^^^^ constants require immutable values
66

7-
error[E0017]: references in constants may only refer to immutable values
8-
--> $DIR/const-multi-ref.rs:5:21
7+
error[E0492]: cannot borrow a constant which may contain interior mutability, create a static instead
8+
--> $DIR/const-multi-ref.rs:16:13
99
|
10-
LL | let reborrow = {p};
11-
| ^ constants require immutable values
10+
LL | let p = &a;
11+
| ^^
1212

1313
error: aborting due to 2 previous errors
1414

15-
For more information about this error, try `rustc --explain E0017`.
15+
Some errors have detailed explanations: E0017, E0492.
16+
For more information about an error, try `rustc --explain E0017`.

0 commit comments

Comments
 (0)