Skip to content

Commit 5cae7a0

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 18aedf6 commit 5cae7a0

File tree

3 files changed

+192
-15
lines changed

3 files changed

+192
-15
lines changed

src/librustc_mir/borrow_check/mod.rs

Lines changed: 120 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ use dataflow::{MaybeInitializedLvals, MaybeUninitializedLvals};
3636
use dataflow::{EverInitializedLvals, MovingOutStatements};
3737
use dataflow::{Borrows, BorrowData, ReserveOrActivateIndex};
3838
use dataflow::{ActiveBorrows, Reservations};
39+
use dataflow::indexes::{BorrowIndex};
3940
use dataflow::move_paths::{IllegalMoveOriginKind, MoveError};
4041
use dataflow::move_paths::{HasMoveData, LookupResult, MoveData, MovePathIndex};
4142
use util::borrowck_errors::{BorrowckErrors, Origin};
@@ -222,6 +223,7 @@ fn do_mir_borrowck<'a, 'gcx, 'tcx>(
222223
},
223224
storage_dead_or_drop_error_reported_l: FxHashSet(),
224225
storage_dead_or_drop_error_reported_s: FxHashSet(),
226+
reservation_error_reported: FxHashSet(),
225227
};
226228

227229
let borrows = Borrows::new(tcx, mir, opt_regioncx, def_id, body_id);
@@ -287,6 +289,14 @@ pub struct MirBorrowckCtxt<'cx, 'gcx: 'tcx, 'tcx: 'cx> {
287289
storage_dead_or_drop_error_reported_l: FxHashSet<Local>,
288290
/// Same as the above, but for statics (thread-locals)
289291
storage_dead_or_drop_error_reported_s: FxHashSet<DefId>,
292+
/// This field keeps track of when borrow conflict errors are reported
293+
/// for reservations, so that we don't report seemingly duplicate
294+
/// errors for corresponding activations
295+
///
296+
/// FIXME: Ideally this would be a set of BorrowIndex, not Places,
297+
/// but it is currently inconvenient to track down the BorrowIndex
298+
/// at the time we detect and report a reservation error.
299+
reservation_error_reported: FxHashSet<Place<'tcx>>,
290300
}
291301

292302
// Check that:
@@ -318,6 +328,9 @@ impl<'cx, 'gcx, 'tcx> DataflowResultsConsumer<'cx, 'tcx> for MirBorrowckCtxt<'cx
318328
flow_state
319329
);
320330
let span = stmt.source_info.span;
331+
332+
self.check_activations(location, span, flow_state);
333+
321334
match stmt.kind {
322335
StatementKind::Assign(ref lhs, ref rhs) => {
323336
// NOTE: NLL RFC calls for *shallow* write; using Deep
@@ -424,6 +437,9 @@ impl<'cx, 'gcx, 'tcx> DataflowResultsConsumer<'cx, 'tcx> for MirBorrowckCtxt<'cx
424437
flow_state
425438
);
426439
let span = term.source_info.span;
440+
441+
self.check_activations(location, span, flow_state);
442+
427443
match term.kind {
428444
TerminatorKind::SwitchInt {
429445
ref discr,
@@ -557,7 +573,7 @@ enum Control {
557573
}
558574

559575
use self::ShallowOrDeep::{Deep, Shallow};
560-
use self::ReadOrWrite::{Read, Write};
576+
use self::ReadOrWrite::{Activation, Read, Reservation, Write};
561577

562578
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
563579
enum ArtificialField {
@@ -592,6 +608,12 @@ enum ReadOrWrite {
592608
/// new values or otherwise invalidated (for example, it could be
593609
/// de-initialized, as in a move operation).
594610
Write(WriteKind),
611+
612+
/// For two-phase borrows, we distinguish a reservation (which is treated
613+
/// like a Read) from an activation (which is treated like a write), and
614+
/// each of those is furthermore distinguished from Reads/Writes above.
615+
Reservation(WriteKind),
616+
Activation(WriteKind, BorrowIndex),
595617
}
596618

597619
/// Kind of read access to a value
@@ -680,6 +702,14 @@ impl<'cx, 'gcx, 'tcx> MirBorrowckCtxt<'cx, 'gcx, 'tcx> {
680702
) -> AccessErrorsReported {
681703
let (sd, rw) = kind;
682704

705+
if let Activation(_, borrow_index) = rw {
706+
if self.reservation_error_reported.contains(&place_span.0) {
707+
debug!("skipping access_place for activation of invalid reservation \
708+
place: {:?} borrow_index: {:?}", place_span.0, borrow_index);
709+
return AccessErrorsReported { mutability_error: false, conflict_error: false };
710+
}
711+
}
712+
683713
let mutability_error =
684714
self.check_access_permissions(place_span, rw, is_local_mutation_allowed);
685715
let conflict_error =
@@ -702,9 +732,16 @@ impl<'cx, 'gcx, 'tcx> MirBorrowckCtxt<'cx, 'gcx, 'tcx> {
702732
(sd, place_span.0),
703733
flow_state,
704734
|this, index, borrow| match (rw, borrow.kind) {
705-
(Read(_), BorrowKind::Shared) => Control::Continue,
735+
// Obviously an activation is compatible with its own reservation;
736+
// so don't check if they interfere.
737+
(Activation(_, activating), _) if index.is_reservation() &&
738+
activating == index.borrow_index() => Control::Continue,
706739

707-
(Read(kind), BorrowKind::Unique) | (Read(kind), BorrowKind::Mut) => {
740+
(Read(_), BorrowKind::Shared) |
741+
(Reservation(..), BorrowKind::Shared) => Control::Continue,
742+
743+
(Read(kind), BorrowKind::Unique) |
744+
(Read(kind), BorrowKind::Mut) => {
708745
// Reading from mere reservations of mutable-borrows is OK.
709746
if this.tcx.sess.opts.debugging_opts.two_phase_borrows &&
710747
index.is_reservation()
@@ -734,13 +771,32 @@ impl<'cx, 'gcx, 'tcx> MirBorrowckCtxt<'cx, 'gcx, 'tcx> {
734771
}
735772
Control::Break
736773
}
774+
775+
(Reservation(kind), BorrowKind::Unique) |
776+
(Reservation(kind), BorrowKind::Mut) |
777+
(Activation(kind, _), _) |
737778
(Write(kind), _) => {
779+
780+
match rw {
781+
Reservation(_) => {
782+
debug!("recording invalid reservation of \
783+
place: {:?}", place_span.0);
784+
this.reservation_error_reported.insert(place_span.0.clone());
785+
}
786+
Activation(_, activating) => {
787+
debug!("observing check_place for activation of \
788+
borrow_index: {:?}", activating);
789+
}
790+
Read(..) | Write(..) => {}
791+
}
792+
738793
match kind {
739794
WriteKind::MutableBorrow(bk) => {
740795
let end_issued_loan_span = flow_state
741796
.borrows
742797
.operator()
743798
.opt_region_end_span(&borrow.region);
799+
744800
error_reported = true;
745801
this.report_conflicting_borrow(
746802
context,
@@ -827,16 +883,23 @@ impl<'cx, 'gcx, 'tcx> MirBorrowckCtxt<'cx, 'gcx, 'tcx> {
827883
let access_kind = match bk {
828884
BorrowKind::Shared => (Deep, Read(ReadKind::Borrow(bk))),
829885
BorrowKind::Unique | BorrowKind::Mut => {
830-
(Deep, Write(WriteKind::MutableBorrow(bk)))
886+
let wk = WriteKind::MutableBorrow(bk);
887+
if self.tcx.sess.opts.debugging_opts.two_phase_borrows {
888+
(Deep, Reservation(wk))
889+
} else {
890+
(Deep, Write(wk))
891+
}
831892
}
832893
};
894+
833895
self.access_place(
834896
context,
835897
(place, span),
836898
access_kind,
837899
LocalMutationIsAllowed::No,
838900
flow_state,
839901
);
902+
840903
self.check_if_path_is_moved(
841904
context,
842905
InitializationRequiringAction::Borrow,
@@ -1007,6 +1070,49 @@ impl<'cx, 'gcx, 'tcx> MirBorrowckCtxt<'cx, 'gcx, 'tcx> {
10071070
)
10081071
}
10091072
}
1073+
1074+
fn check_activations(&mut self,
1075+
location: Location,
1076+
span: Span,
1077+
flow_state: &Flows<'cx, 'gcx, 'tcx>)
1078+
{
1079+
if !self.tcx.sess.opts.debugging_opts.two_phase_borrows {
1080+
return;
1081+
}
1082+
1083+
// Two-phase borrow support: For each activation that is newly
1084+
// generated at this statement, check if it interferes with
1085+
// another borrow.
1086+
let domain = flow_state.borrows.operator();
1087+
let data = domain.borrows();
1088+
flow_state.borrows.each_gen_bit(|gen| {
1089+
if gen.is_activation() && // must be activation,
1090+
!flow_state.borrows.contains(&gen) // and newly generated.
1091+
{
1092+
let borrow_index = gen.borrow_index();
1093+
let borrow = &data[borrow_index];
1094+
// currently the flow analysis registers
1095+
// activations for both mutable and immutable
1096+
// borrows. So make sure we are talking about a
1097+
// mutable borrow before we check it.
1098+
match borrow.kind {
1099+
BorrowKind::Shared => return,
1100+
BorrowKind::Unique |
1101+
BorrowKind::Mut => {}
1102+
}
1103+
1104+
self.access_place(ContextKind::Activation.new(location),
1105+
(&borrow.borrowed_place, span),
1106+
(Deep, Activation(WriteKind::MutableBorrow(borrow.kind),
1107+
borrow_index)),
1108+
LocalMutationIsAllowed::No,
1109+
flow_state);
1110+
// We do not need to call `check_if_path_is_moved`
1111+
// again, as we already called it when we made the
1112+
// initial reservation.
1113+
}
1114+
});
1115+
}
10101116
}
10111117

10121118
impl<'cx, 'gcx, 'tcx> MirBorrowckCtxt<'cx, 'gcx, 'tcx> {
@@ -1250,11 +1356,13 @@ impl<'cx, 'gcx, 'tcx> MirBorrowckCtxt<'cx, 'gcx, 'tcx> {
12501356
);
12511357
let mut error_reported = false;
12521358
match kind {
1359+
Reservation(WriteKind::MutableBorrow(BorrowKind::Unique)) |
12531360
Write(WriteKind::MutableBorrow(BorrowKind::Unique)) => {
12541361
if let Err(_place_err) = self.is_mutable(place, LocalMutationIsAllowed::Yes) {
12551362
span_bug!(span, "&unique borrow for {:?} should not fail", place);
12561363
}
12571364
}
1365+
Reservation(WriteKind::MutableBorrow(BorrowKind::Mut)) |
12581366
Write(WriteKind::MutableBorrow(BorrowKind::Mut)) => if let Err(place_err) =
12591367
self.is_mutable(place, is_local_mutation_allowed)
12601368
{
@@ -1277,6 +1385,7 @@ impl<'cx, 'gcx, 'tcx> MirBorrowckCtxt<'cx, 'gcx, 'tcx> {
12771385

12781386
err.emit();
12791387
},
1388+
Reservation(WriteKind::Mutate) |
12801389
Write(WriteKind::Mutate) => {
12811390
if let Err(place_err) = self.is_mutable(place, is_local_mutation_allowed) {
12821391
error_reported = true;
@@ -1298,6 +1407,9 @@ impl<'cx, 'gcx, 'tcx> MirBorrowckCtxt<'cx, 'gcx, 'tcx> {
12981407
err.emit();
12991408
}
13001409
}
1410+
Reservation(WriteKind::Move) |
1411+
Reservation(WriteKind::StorageDeadOrDrop) |
1412+
Reservation(WriteKind::MutableBorrow(BorrowKind::Shared)) |
13011413
Write(WriteKind::Move) |
13021414
Write(WriteKind::StorageDeadOrDrop) |
13031415
Write(WriteKind::MutableBorrow(BorrowKind::Shared)) => {
@@ -1312,6 +1424,9 @@ impl<'cx, 'gcx, 'tcx> MirBorrowckCtxt<'cx, 'gcx, 'tcx> {
13121424
);
13131425
}
13141426
}
1427+
1428+
Activation(..) => {} // permission checks are done at Reservation point.
1429+
13151430
Read(ReadKind::Borrow(BorrowKind::Unique)) |
13161431
Read(ReadKind::Borrow(BorrowKind::Mut)) |
13171432
Read(ReadKind::Borrow(BorrowKind::Shared)) |
@@ -1892,6 +2007,7 @@ struct Context {
18922007

18932008
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
18942009
enum ContextKind {
2010+
Activation,
18952011
AssignLhs,
18962012
AssignRhs,
18972013
SetDiscrim,
Lines changed: 62 additions & 0 deletions
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

Lines changed: 10 additions & 11 deletions
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)