Skip to content

Commit ff3a7f0

Browse files
committed
Check activation points as the place where mutable borrows become relevant.
Since we are now checking activation points, I removed one of the checks at the reservation point. (You can see the effect this had on two-phase-reservation-sharing-interference-2.rs) Also, since we now have checks at both the reservation point and the activation point, we sometimes would observe duplicate errors (since either one independently interferes with another mutable borrow). To deal with this, I used a similar strategy to one used as discussed on issue #45360: keep a set of errors reported (in this case for reservations), and then avoid doing the checks for the corresponding activations. (This does mean that some errors could get masked, namely for conflicting borrows that start after the reservation but still conflict with the activation, which is unchecked when there was an error for the reservation. But this seems like a reasonable price to pay.)
1 parent aa896b6 commit ff3a7f0

File tree

3 files changed

+200
-15
lines changed

3 files changed

+200
-15
lines changed

src/librustc_mir/borrow_check/mod.rs

+128-4
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ use dataflow::{MaybeInitializedLvals, MaybeUninitializedLvals};
3434
use dataflow::{EverInitializedLvals, MovingOutStatements};
3535
use dataflow::{Borrows, BorrowData, ResActIndex};
3636
use dataflow::{ActiveBorrows, Reservations};
37+
use dataflow::indexes::{BorrowIndex};
3738
use dataflow::move_paths::{IllegalMoveOriginKind, MoveError};
3839
use dataflow::move_paths::{HasMoveData, LookupResult, MoveData, MoveOutIndex, MovePathIndex};
3940
use util::borrowck_errors::{BorrowckErrors, Origin};
@@ -195,6 +196,7 @@ fn do_mir_borrowck<'a, 'gcx, 'tcx>(
195196
move_data: &mdpe.move_data,
196197
param_env: param_env,
197198
storage_dead_or_drop_error_reported: FxHashSet(),
199+
reservation_error_reported: FxHashSet(),
198200
};
199201

200202
let borrows = Borrows::new(tcx, mir, opt_regioncx);
@@ -249,6 +251,14 @@ pub struct MirBorrowckCtxt<'cx, 'gcx: 'tcx, 'tcx: 'cx> {
249251
/// in order to stop duplicate error reporting and identify the conditions required
250252
/// for a "temporary value dropped here while still borrowed" error. See #45360.
251253
storage_dead_or_drop_error_reported: FxHashSet<Local>,
254+
/// This field keeps track of when borrow conflict errors are reported
255+
/// for reservations, so that we don't report seemingly duplicate
256+
/// errors for corresponding activations
257+
///
258+
/// FIXME: Ideally this would be a set of BorrowIndex, not Places,
259+
/// but it is currently inconvenient to track down the BorrowIndex
260+
/// at the time we detect and report a reservation error.
261+
reservation_error_reported: FxHashSet<Place<'tcx>>,
252262
}
253263

254264
// (forced to be `pub` due to its use as an associated type below.)
@@ -347,6 +357,9 @@ impl<'cx, 'gcx, 'tcx> DataflowResultsConsumer<'cx, 'tcx> for MirBorrowckCtxt<'cx
347357
summary
348358
);
349359
let span = stmt.source_info.span;
360+
361+
self.check_activations(location, span, flow_state);
362+
350363
match stmt.kind {
351364
StatementKind::Assign(ref lhs, ref rhs) => {
352365
// NOTE: NLL RFC calls for *shallow* write; using Deep
@@ -454,6 +467,9 @@ impl<'cx, 'gcx, 'tcx> DataflowResultsConsumer<'cx, 'tcx> for MirBorrowckCtxt<'cx
454467
summary
455468
);
456469
let span = term.source_info.span;
470+
471+
self.check_activations(location, span, flow_state);
472+
457473
match term.kind {
458474
TerminatorKind::SwitchInt {
459475
ref discr,
@@ -600,7 +616,7 @@ enum Control {
600616
}
601617

602618
use self::ShallowOrDeep::{Deep, Shallow};
603-
use self::ReadOrWrite::{Read, Write};
619+
use self::ReadOrWrite::{Activation, Read, Reservation, Write};
604620

605621
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
606622
enum ArtificialField {
@@ -635,6 +651,12 @@ enum ReadOrWrite {
635651
/// new values or otherwise invalidated (for example, it could be
636652
/// de-initialized, as in a move operation).
637653
Write(WriteKind),
654+
655+
/// For two-phase borrows, we distinguish a reservation (which is treated
656+
/// like a Read) from an activation (which is treated like a write), and
657+
/// each of those is furthermore distinguished from Reads/Writes above.
658+
Reservation(WriteKind),
659+
Activation(WriteKind, BorrowIndex),
638660
}
639661

640662
/// Kind of read access to a value
@@ -726,6 +748,14 @@ impl<'cx, 'gcx, 'tcx> MirBorrowckCtxt<'cx, 'gcx, 'tcx> {
726748
}
727749
}
728750

751+
if let Activation(_, borrow_index) = rw {
752+
if self.reservation_error_reported.contains(&place_span.0) {
753+
debug!("skipping check_place for activation of invalid reservation \
754+
place: {:?} borrow_index: {:?}", place_span.0, borrow_index);
755+
return;
756+
}
757+
}
758+
729759
// Check permissions
730760
let mut error_reported =
731761
self.check_access_permissions(place_span, rw, is_local_mutation_allowed);
@@ -735,9 +765,17 @@ impl<'cx, 'gcx, 'tcx> MirBorrowckCtxt<'cx, 'gcx, 'tcx> {
735765
(sd, place_span.0),
736766
flow_state,
737767
|this, index, borrow, common_prefix| match (rw, borrow.kind) {
738-
(Read(_), BorrowKind::Shared) => Control::Continue,
739768

740-
(Read(kind), BorrowKind::Unique) | (Read(kind), BorrowKind::Mut) => {
769+
// Obviously an activation is compatible with its own reservation;
770+
// so don't check if they interfere.
771+
(Activation(_, activating), _) if index.is_reservation() &&
772+
activating == index.borrow_index() => Control::Continue,
773+
774+
(Read(_), BorrowKind::Shared) |
775+
(Reservation(..), BorrowKind::Shared) => Control::Continue,
776+
777+
(Read(kind), BorrowKind::Unique) |
778+
(Read(kind), BorrowKind::Mut) => {
741779
// Reading from mere reservations of mutable-borrows is OK.
742780
if this.tcx.sess.opts.debugging_opts.two_phase_borrows &&
743781
index.is_reservation()
@@ -769,14 +807,33 @@ impl<'cx, 'gcx, 'tcx> MirBorrowckCtxt<'cx, 'gcx, 'tcx> {
769807
}
770808
Control::Break
771809
}
810+
811+
(Reservation(kind), BorrowKind::Unique) |
812+
(Reservation(kind), BorrowKind::Mut) |
813+
(Activation(kind, _), _) |
772814
(Write(kind), _) => {
815+
816+
match rw {
817+
Reservation(_) => {
818+
debug!("recording invalid reservation of \
819+
place: {:?}", place_span.0);
820+
this.reservation_error_reported.insert(place_span.0.clone());
821+
}
822+
Activation(_, activating) => {
823+
debug!("observing check_place for activation of \
824+
borrow_index: {:?}", activating);
825+
}
826+
Read(..) | Write(..) => {}
827+
}
828+
773829
match kind {
774830
WriteKind::MutableBorrow(bk) => {
775831
let end_issued_loan_span = flow_state
776832
.borrows
777833
.base_results
778834
.operator()
779835
.opt_region_end_span(&borrow.region);
836+
780837
error_reported = true;
781838
this.report_conflicting_borrow(
782839
context,
@@ -868,16 +925,23 @@ impl<'cx, 'gcx, 'tcx> MirBorrowckCtxt<'cx, 'gcx, 'tcx> {
868925
let access_kind = match bk {
869926
BorrowKind::Shared => (Deep, Read(ReadKind::Borrow(bk))),
870927
BorrowKind::Unique | BorrowKind::Mut => {
871-
(Deep, Write(WriteKind::MutableBorrow(bk)))
928+
let wk = WriteKind::MutableBorrow(bk);
929+
if self.tcx.sess.opts.debugging_opts.two_phase_borrows {
930+
(Deep, Reservation(wk))
931+
} else {
932+
(Deep, Write(wk))
933+
}
872934
}
873935
};
936+
874937
self.access_place(
875938
context,
876939
(place, span),
877940
access_kind,
878941
LocalMutationIsAllowed::No,
879942
flow_state,
880943
);
944+
881945
self.check_if_path_is_moved(
882946
context,
883947
InitializationRequiringAction::Borrow,
@@ -1031,6 +1095,50 @@ impl<'cx, 'gcx, 'tcx> MirBorrowckCtxt<'cx, 'gcx, 'tcx> {
10311095
self.prefixes(place, prefix_set)
10321096
.any(|prefix| prefix == root_place)
10331097
}
1098+
1099+
fn check_activations(&mut self,
1100+
location: Location,
1101+
span: Span,
1102+
flow_state: &InProgress<'cx, 'gcx, 'tcx>)
1103+
{
1104+
if !self.tcx.sess.opts.debugging_opts.two_phase_borrows {
1105+
return;
1106+
}
1107+
1108+
// Two-phase borrow support: For each activation that is newly
1109+
// generated at this statement, check if it interferes with
1110+
// another borrow.
1111+
let domain = flow_state.borrows.base_results.operator();
1112+
let data = domain.borrows();
1113+
let sets = &flow_state.borrows.sets;
1114+
for gen in sets.gen_set.iter() {
1115+
if gen.is_activation() && // must be activation,
1116+
!sets.on_entry.contains(&gen) // and newly generated.
1117+
{
1118+
let borrow_index = gen.borrow_index();
1119+
let borrow = &data[borrow_index];
1120+
// currently the flow analysis registers
1121+
// activations for both mutable and immutable
1122+
// borrows. So make sure we are talking about a
1123+
// mutable borrow before we check it.
1124+
match borrow.kind {
1125+
BorrowKind::Shared => continue,
1126+
BorrowKind::Unique |
1127+
BorrowKind::Mut => {}
1128+
}
1129+
1130+
self.access_place(ContextKind::Activation.new(location),
1131+
(&borrow.borrowed_place, span),
1132+
(Deep, Activation(WriteKind::MutableBorrow(borrow.kind),
1133+
borrow_index)),
1134+
LocalMutationIsAllowed::No,
1135+
flow_state);
1136+
// We do not need to call `check_if_path_is_moved`
1137+
// again, as we already called it when we made the
1138+
// initial reservation.
1139+
}
1140+
}
1141+
}
10341142
}
10351143

10361144
impl<'cx, 'gcx, 'tcx> MirBorrowckCtxt<'cx, 'gcx, 'tcx> {
@@ -1293,11 +1401,13 @@ impl<'cx, 'gcx, 'tcx> MirBorrowckCtxt<'cx, 'gcx, 'tcx> {
12931401
);
12941402
let mut error_reported = false;
12951403
match kind {
1404+
Reservation(WriteKind::MutableBorrow(BorrowKind::Unique)) |
12961405
Write(WriteKind::MutableBorrow(BorrowKind::Unique)) => {
12971406
if let Err(_place_err) = self.is_unique(place) {
12981407
span_bug!(span, "&unique borrow for {:?} should not fail", place);
12991408
}
13001409
}
1410+
Reservation(WriteKind::MutableBorrow(BorrowKind::Mut)) |
13011411
Write(WriteKind::MutableBorrow(BorrowKind::Mut)) => if let Err(place_err) =
13021412
self.is_mutable(place, is_local_mutation_allowed)
13031413
{
@@ -1320,6 +1430,7 @@ impl<'cx, 'gcx, 'tcx> MirBorrowckCtxt<'cx, 'gcx, 'tcx> {
13201430

13211431
err.emit();
13221432
},
1433+
Reservation(WriteKind::Mutate) |
13231434
Write(WriteKind::Mutate) => {
13241435
if let Err(place_err) = self.is_mutable(place, is_local_mutation_allowed) {
13251436
error_reported = true;
@@ -1341,6 +1452,9 @@ impl<'cx, 'gcx, 'tcx> MirBorrowckCtxt<'cx, 'gcx, 'tcx> {
13411452
err.emit();
13421453
}
13431454
}
1455+
Reservation(WriteKind::Move) |
1456+
Reservation(WriteKind::StorageDeadOrDrop) |
1457+
Reservation(WriteKind::MutableBorrow(BorrowKind::Shared)) |
13441458
Write(WriteKind::Move) |
13451459
Write(WriteKind::StorageDeadOrDrop) |
13461460
Write(WriteKind::MutableBorrow(BorrowKind::Shared)) => {
@@ -1355,6 +1469,9 @@ impl<'cx, 'gcx, 'tcx> MirBorrowckCtxt<'cx, 'gcx, 'tcx> {
13551469
);
13561470
}
13571471
}
1472+
1473+
Activation(..) => {} // permission checks are done at Reservation point.
1474+
13581475
Read(ReadKind::Borrow(BorrowKind::Unique)) |
13591476
Read(ReadKind::Borrow(BorrowKind::Mut)) |
13601477
Read(ReadKind::Borrow(BorrowKind::Shared)) |
@@ -1892,6 +2009,12 @@ impl<'cx, 'gcx, 'tcx> MirBorrowckCtxt<'cx, 'gcx, 'tcx> {
18922009
use rustc::hir::ExprClosure;
18932010
use rustc::mir::AggregateKind;
18942011

2012+
if location.statement_index == self.mir[location.block].statements.len() {
2013+
// Code below hasn't been written in a manner to deal with
2014+
// a terminator location.
2015+
return None;
2016+
}
2017+
18952018
let local = if let StatementKind::Assign(Place::Local(local), _) =
18962019
self.mir[location.block].statements[location.statement_index].kind
18972020
{
@@ -2355,6 +2478,7 @@ struct Context {
23552478

23562479
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
23572480
enum ContextKind {
2481+
Activation,
23582482
AssignLhs,
23592483
AssignRhs,
23602484
SetDiscrim,
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
// Copyright 2017 The Rust Project Developers. See the COPYRIGHT
2+
// file at the top-level directory of this distribution and at
3+
// http://rust-lang.org/COPYRIGHT.
4+
//
5+
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
6+
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
7+
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
8+
// option. This file may not be copied, modified, or distributed
9+
// except according to those terms.
10+
11+
// revisions: lxl nll
12+
//[lxl]compile-flags: -Z borrowck=mir -Z two-phase-borrows
13+
//[nll]compile-flags: -Z borrowck=mir -Z two-phase-borrows -Z nll
14+
15+
// This is an important corner case pointed out by Niko: one is
16+
// allowed to initiate a shared borrow during a reservation, but it
17+
// *must end* before the activation occurs.
18+
//
19+
// FIXME: for clarity, diagnostics for these cases might be better off
20+
// if they specifically said "cannot activate mutable borrow of `x`"
21+
22+
#![allow(dead_code)]
23+
24+
fn read(_: &i32) { }
25+
26+
fn ok() {
27+
let mut x = 3;
28+
let y = &mut x;
29+
{ let z = &x; read(z); }
30+
*y += 1;
31+
}
32+
33+
fn not_ok() {
34+
let mut x = 3;
35+
let y = &mut x;
36+
let z = &x;
37+
*y += 1;
38+
//[lxl]~^ ERROR cannot borrow `x` as mutable because it is also borrowed as immutable
39+
//[nll]~^^ ERROR cannot borrow `x` as mutable because it is also borrowed as immutable
40+
read(z);
41+
}
42+
43+
fn should_be_ok_with_nll() {
44+
let mut x = 3;
45+
let y = &mut x;
46+
let z = &x;
47+
read(z);
48+
*y += 1;
49+
//[lxl]~^ ERROR cannot borrow `x` as mutable because it is also borrowed as immutable
50+
// (okay with nll today)
51+
}
52+
53+
fn should_also_eventually_be_ok_with_nll() {
54+
let mut x = 3;
55+
let y = &mut x;
56+
let _z = &x;
57+
*y += 1;
58+
//[lxl]~^ ERROR cannot borrow `x` as mutable because it is also borrowed as immutable
59+
//[nll]~^^ ERROR cannot borrow `x` as mutable because it is also borrowed as immutable
60+
}
61+
62+
fn main() { }

src/test/compile-fail/borrowck/two-phase-reservation-sharing-interference-2.rs

+10-11
Original file line numberDiff line numberDiff line change
@@ -15,24 +15,23 @@
1515
// This is similar to two-phase-reservation-sharing-interference.rs
1616
// in that it shows a reservation that overlaps with a shared borrow.
1717
//
18-
// However, it is also more immediately concerning because one would
19-
// intutively think that if run-pass/borrowck/two-phase-baseline.rs
20-
// works, then this *should* work too.
18+
// Currently, this test fails with lexical lifetimes, but succeeds
19+
// with non-lexical lifetimes. (The reason is because the activation
20+
// of the mutable borrow ends up overlapping with a lexically-scoped
21+
// shared borrow; but a non-lexical shared borrow can end before the
22+
// activation occurs.)
2123
//
22-
// As before, the current implementation is (probably) more
23-
// conservative than is necessary.
24-
//
25-
// So this test is just making a note of the current behavior, with
26-
// the caveat that in the future, the rules may be loosened, at which
27-
// point this test might be thrown out.
24+
// So this test is just making a note of the current behavior.
25+
26+
#![feature(rustc_attrs)]
2827

29-
fn main() {
28+
#[rustc_error]
29+
fn main() { //[nll]~ ERROR compilation successful
3030
let mut v = vec![0, 1, 2];
3131
let shared = &v;
3232

3333
v.push(shared.len());
3434
//[lxl]~^ ERROR cannot borrow `v` as mutable because it is also borrowed as immutable [E0502]
35-
//[nll]~^^ ERROR cannot borrow `v` as mutable because it is also borrowed as immutable [E0502]
3635

3736
assert_eq!(v, [0, 1, 2, 3]);
3837
}

0 commit comments

Comments
 (0)