Skip to content

Commit 3a2190a

Browse files
committed
Auto merge of #53438 - matthewjasper:permissive-match-access, r=pnkfelix
[NLL] Be more permissive when checking access due to Match Partially addresses #53114. notably, we should now have parity with AST borrowck. Matching on uninitialized values is still forbidden. * ~~Give fake borrows for match their own `BorrowKind`~~ * ~~Allow borrows with this kind to happen on values that are already mutably borrowed.~~ * ~~Track borrows with this type even behind shared reference dereferences and consider all accesses to be deep when checking for conflicts with this borrow type. See [src/test/ui/issues/issue-27282-mutate-before-diverging-arm-3.rs](cb5c989#diff-a2126cd3263a1f5342e2ecd5e699fbc6) for an example soundness issue this fixes (a case of #27282 that wasn't handled correctly).~~ * Create a new `BorrowKind`: `Shallow` (name can be bike-shed) * `Shallow` borrows differ from shared borrows in that * When we check for access we treat them as a `Shallow(Some(_))` read * When we check for conflicts with them, if the borrow place is a strict prefix of the access place then we don't consider that a conflict. * For example, a `Shallow` borrow of `x` does not conflict with any access or borrow of `x.0` or `*x` * Remove the current fake borrow in matches. * When building matches, we take a `Shallow` borrow of any `Place` that we switch on or bind in a match, and any prefix of those places. (There are some optimizations where we do fewer borrows, but this shouldn't change semantics) * `match x { &Some(1) => (), _ => (), }` would `Shallow` borrow `x`, `*x` and `(*x as Some).0` (the `*x` borrow is unnecessary, but I'm not sure how easy it would be to remove.) * Replace the fake discriminant read with a `ReadForMatch`. * Change ReadForMatch to only check for initializedness (to prevent `let x: !; match x {}`), but not conflicting borrows. It is still considered a use for liveness and `unsafe` checking. * Give special cased error messages for this kind of borrow. Table from the above issue after this PR | Thing | AST | MIR | Want | Example | | --- | --- | --- | --- |---| | `let _ = <unsafe-field>` | 💚 | 💚 | ❌ | [playground](https://play.rust-lang.org/?gist=bb7843e42fa5318c1043d04bd72abfe4&version=nightly&mode=debug&edition=2015) | | `match <unsafe_field> { _ => () }` | ❌ | ❌ | ❌ | [playground](https://play.rust-lang.org/?gist=3e3af05fbf1fae28fab2aaf9412fb2ea&version=nightly&mode=debug&edition=2015) | | `let _ = <moved>` | 💚 | 💚 | 💚 | [playground](https://play.rust-lang.org/?gist=91a6efde8288558e584aaeee0a50558b&version=nightly&mode=debug&edition=2015) | | `match <moved> { _ => () }` | ❌ | ❌ | 💚 | [playground](https://play.rust-lang.org/?gist=804f8185040b2fe131f2c4a64b3048ca&version=nightly&mode=debug&edition=2015) | | `let _ = <borrowed>` | 💚 | 💚 | 💚 | [playground](https://play.rust-lang.org/?gist=0e487c2893b89cb772ec2f2b7c5da876&version=nightly&mode=debug&edition=2015) | | `match <borrowed> { _ => () }` | 💚 | 💚 | 💚 | [playground](https://play.rust-lang.org/?gist=0e487c2893b89cb772ec2f2b7c5da876&version=nightly&mode=debug&edition=2015) | r? @nikomatsakis
2 parents 5c875d9 + a830732 commit 3a2190a

File tree

53 files changed

+1191
-900
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

53 files changed

+1191
-900
lines changed

src/librustc/ich/impls_mir.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ for mir::BorrowKind {
4646

4747
match *self {
4848
mir::BorrowKind::Shared |
49+
mir::BorrowKind::Shallow |
4950
mir::BorrowKind::Unique => {}
5051
mir::BorrowKind::Mut { allow_two_phase_borrow } => {
5152
allow_two_phase_borrow.hash_stable(hcx, hasher);
@@ -272,7 +273,7 @@ for mir::StatementKind<'gcx> {
272273
}
273274
}
274275

275-
impl_stable_hash_for!(enum mir::FakeReadCause { ForMatch, ForLet });
276+
impl_stable_hash_for!(enum mir::FakeReadCause { ForMatchGuard, ForMatchedPlace, ForLet });
276277

277278
impl<'a, 'gcx, T> HashStable<StableHashingContext<'a>>
278279
for mir::ValidationOperand<'gcx, T>

src/librustc/mir/mod.rs

Lines changed: 33 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -451,11 +451,32 @@ impl From<Mutability> for hir::Mutability {
451451
}
452452
}
453453

454-
#[derive(Copy, Clone, Debug, PartialEq, Eq, RustcEncodable, RustcDecodable)]
454+
#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, RustcEncodable, RustcDecodable)]
455455
pub enum BorrowKind {
456456
/// Data must be immutable and is aliasable.
457457
Shared,
458458

459+
/// The immediately borrowed place must be immutable, but projections from
460+
/// it don't need to be. For example, a shallow borrow of `a.b` doesn't
461+
/// conflict with a mutable borrow of `a.b.c`.
462+
///
463+
/// This is used when lowering matches: when matching on a place we want to
464+
/// ensure that place have the same value from the start of the match until
465+
/// an arm is selected. This prevents this code from compiling:
466+
///
467+
/// let mut x = &Some(0);
468+
/// match *x {
469+
/// None => (),
470+
/// Some(_) if { x = &None; false } => (),
471+
/// Some(_) => (),
472+
/// }
473+
///
474+
/// This can't be a shared borrow because mutably borrowing (*x as Some).0
475+
/// should not prevent `if let None = x { ... }`, for example, becase the
476+
/// mutating `(*x as Some).0` can't affect the discriminant of `x`.
477+
/// We can also report errors with this kind of borrow differently.
478+
Shallow,
479+
459480
/// Data must be immutable but not aliasable. This kind of borrow
460481
/// cannot currently be expressed by the user and is used only in
461482
/// implicit closure bindings. It is needed when the closure is
@@ -504,7 +525,7 @@ pub enum BorrowKind {
504525
impl BorrowKind {
505526
pub fn allows_two_phase_borrow(&self) -> bool {
506527
match *self {
507-
BorrowKind::Shared | BorrowKind::Unique => false,
528+
BorrowKind::Shared | BorrowKind::Shallow | BorrowKind::Unique => false,
508529
BorrowKind::Mut {
509530
allow_two_phase_borrow,
510531
} => allow_two_phase_borrow,
@@ -1672,7 +1693,11 @@ pub enum FakeReadCause {
16721693
///
16731694
/// This should ensure that you cannot change the variant for an enum
16741695
/// while you are in the midst of matching on it.
1675-
ForMatch,
1696+
ForMatchGuard,
1697+
1698+
/// `let x: !; match x {}` doesn't generate any read of x so we need to
1699+
/// generate a read of x to check that it is initialized and safe.
1700+
ForMatchedPlace,
16761701

16771702
/// Officially, the semantics of
16781703
///
@@ -1773,7 +1798,7 @@ impl<'tcx> Debug for Statement<'tcx> {
17731798

17741799
/// A path to a value; something that can be evaluated without
17751800
/// changing or disturbing program state.
1776-
#[derive(Clone, PartialEq, Eq, Hash, RustcEncodable, RustcDecodable)]
1801+
#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash, RustcEncodable, RustcDecodable)]
17771802
pub enum Place<'tcx> {
17781803
/// local variable
17791804
Local(Local),
@@ -1790,7 +1815,7 @@ pub enum Place<'tcx> {
17901815

17911816
/// The def-id of a static, along with its normalized type (which is
17921817
/// stored to avoid requiring normalization when reading MIR).
1793-
#[derive(Clone, PartialEq, Eq, Hash, RustcEncodable, RustcDecodable)]
1818+
#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash, RustcEncodable, RustcDecodable)]
17941819
pub struct Static<'tcx> {
17951820
pub def_id: DefId,
17961821
pub ty: Ty<'tcx>,
@@ -1805,13 +1830,13 @@ impl_stable_hash_for!(struct Static<'tcx> {
18051830
/// or `*B` or `B[index]`. Note that it is parameterized because it is
18061831
/// shared between `Constant` and `Place`. See the aliases
18071832
/// `PlaceProjection` etc below.
1808-
#[derive(Clone, Debug, PartialEq, Eq, Hash, RustcEncodable, RustcDecodable)]
1833+
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, RustcEncodable, RustcDecodable)]
18091834
pub struct Projection<'tcx, B, V, T> {
18101835
pub base: B,
18111836
pub elem: ProjectionElem<'tcx, V, T>,
18121837
}
18131838

1814-
#[derive(Clone, Debug, PartialEq, Eq, Hash, RustcEncodable, RustcDecodable)]
1839+
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, RustcEncodable, RustcDecodable)]
18151840
pub enum ProjectionElem<'tcx, V, T> {
18161841
Deref,
18171842
Field(Field, T),
@@ -2198,6 +2223,7 @@ impl<'tcx> Debug for Rvalue<'tcx> {
21982223
Ref(region, borrow_kind, ref place) => {
21992224
let kind_str = match borrow_kind {
22002225
BorrowKind::Shared => "",
2226+
BorrowKind::Shallow => "shallow ",
22012227
BorrowKind::Mut { .. } | BorrowKind::Unique => "mut ",
22022228
};
22032229

src/librustc/mir/tcx.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -287,6 +287,10 @@ impl BorrowKind {
287287
// use `&mut`. It gives all the capabilities of an `&uniq`
288288
// and hence is a safe "over approximation".
289289
BorrowKind::Unique => hir::MutMutable,
290+
291+
// We have no type corresponding to a shallow borrow, so use
292+
// `&` as an approximation.
293+
BorrowKind::Shallow => hir::MutImmutable,
290294
}
291295
}
292296
}

src/librustc/mir/visit.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -963,6 +963,7 @@ impl<'tcx> PlaceContext<'tcx> {
963963

964964
PlaceContext::Inspect |
965965
PlaceContext::Borrow { kind: BorrowKind::Shared, .. } |
966+
PlaceContext::Borrow { kind: BorrowKind::Shallow, .. } |
966967
PlaceContext::Borrow { kind: BorrowKind::Unique, .. } |
967968
PlaceContext::Projection(Mutability::Not) |
968969
PlaceContext::Copy | PlaceContext::Move |
@@ -974,7 +975,9 @@ impl<'tcx> PlaceContext<'tcx> {
974975
/// Returns true if this place context represents a use that does not change the value.
975976
pub fn is_nonmutating_use(&self) -> bool {
976977
match *self {
977-
PlaceContext::Inspect | PlaceContext::Borrow { kind: BorrowKind::Shared, .. } |
978+
PlaceContext::Inspect |
979+
PlaceContext::Borrow { kind: BorrowKind::Shared, .. } |
980+
PlaceContext::Borrow { kind: BorrowKind::Shallow, .. } |
978981
PlaceContext::Borrow { kind: BorrowKind::Unique, .. } |
979982
PlaceContext::Projection(Mutability::Not) |
980983
PlaceContext::Copy | PlaceContext::Move => true,

src/librustc_mir/borrow_check/borrow_set.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,7 @@ impl<'tcx> fmt::Display for BorrowData<'tcx> {
8787
fn fmt(&self, w: &mut fmt::Formatter) -> fmt::Result {
8888
let kind = match self.kind {
8989
mir::BorrowKind::Shared => "",
90+
mir::BorrowKind::Shallow => "shallow ",
9091
mir::BorrowKind::Unique => "uniq ",
9192
mir::BorrowKind::Mut { .. } => "mut ",
9293
};
@@ -287,7 +288,8 @@ impl<'a, 'gcx, 'tcx> Visitor<'tcx> for GatherBorrows<'a, 'gcx, 'tcx> {
287288
borrow_data.activation_location = match context {
288289
// The use of TMP in a shared borrow does not
289290
// count as an actual activation.
290-
PlaceContext::Borrow { kind: mir::BorrowKind::Shared, .. } => {
291+
PlaceContext::Borrow { kind: mir::BorrowKind::Shared, .. }
292+
| PlaceContext::Borrow { kind: mir::BorrowKind::Shallow, .. } => {
291293
TwoPhaseActivation::NotActivated
292294
}
293295
_ => {

src/librustc_mir/borrow_check/error_reporting.rs

Lines changed: 43 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -333,6 +333,27 @@ impl<'cx, 'gcx, 'tcx> MirBorrowckCtxt<'cx, 'gcx, 'tcx> {
333333
Origin::Mir,
334334
),
335335

336+
(BorrowKind::Mut { .. }, _, _, BorrowKind::Shallow, _, _)
337+
| (BorrowKind::Unique, _, _, BorrowKind::Shallow, _, _) => {
338+
let mut err = tcx.cannot_mutate_in_match_guard(
339+
span,
340+
issued_span,
341+
&desc_place,
342+
"mutably borrow",
343+
Origin::Mir,
344+
);
345+
borrow_spans.var_span_label(
346+
&mut err,
347+
format!(
348+
"borrow occurs due to use of `{}` in closure",
349+
desc_place
350+
),
351+
);
352+
err.buffer(&mut self.errors_buffer);
353+
354+
return;
355+
}
356+
336357
(BorrowKind::Unique, _, _, _, _, _) => tcx.cannot_uniquely_borrow_by_one_closure(
337358
span,
338359
&desc_place,
@@ -368,7 +389,16 @@ impl<'cx, 'gcx, 'tcx> MirBorrowckCtxt<'cx, 'gcx, 'tcx> {
368389
Origin::Mir,
369390
),
370391

371-
(BorrowKind::Shared, _, _, BorrowKind::Shared, _, _) => unreachable!(),
392+
(BorrowKind::Shallow, _, _, BorrowKind::Unique, _, _)
393+
| (BorrowKind::Shallow, _, _, BorrowKind::Mut { .. }, _, _) => {
394+
// Shallow borrows are uses from the user's point of view.
395+
self.report_use_while_mutably_borrowed(context, (place, span), issued_borrow);
396+
return
397+
}
398+
(BorrowKind::Shared, _, _, BorrowKind::Shared, _, _)
399+
| (BorrowKind::Shared, _, _, BorrowKind::Shallow, _, _)
400+
| (BorrowKind::Shallow, _, _, BorrowKind::Shared, _, _)
401+
| (BorrowKind::Shallow, _, _, BorrowKind::Shallow, _, _) => unreachable!(),
372402
};
373403

374404
if issued_spans == borrow_spans {
@@ -780,12 +810,22 @@ impl<'cx, 'gcx, 'tcx> MirBorrowckCtxt<'cx, 'gcx, 'tcx> {
780810
let loan_span = loan_spans.args_or_use();
781811

782812
let tcx = self.infcx.tcx;
783-
let mut err = tcx.cannot_assign_to_borrowed(
813+
let mut err = if loan.kind == BorrowKind::Shallow {
814+
tcx.cannot_mutate_in_match_guard(
784815
span,
785816
loan_span,
786817
&self.describe_place(place).unwrap_or("_".to_owned()),
818+
"assign",
787819
Origin::Mir,
788-
);
820+
)
821+
} else {
822+
tcx.cannot_assign_to_borrowed(
823+
span,
824+
loan_span,
825+
&self.describe_place(place).unwrap_or("_".to_owned()),
826+
Origin::Mir,
827+
)
828+
};
789829

790830
loan_spans.var_span_label(&mut err, "borrow occurs due to use in closure");
791831

src/librustc_mir/borrow_check/mod.rs

Lines changed: 47 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -499,11 +499,20 @@ impl<'cx, 'gcx, 'tcx> DataflowResultsConsumer<'cx, 'tcx> for MirBorrowckCtxt<'cx
499499
);
500500
}
501501
StatementKind::FakeRead(_, ref place) => {
502-
self.access_place(
502+
// Read for match doesn't access any memory and is used to
503+
// assert that a place is safe and live. So we don't have to
504+
// do any checks here.
505+
//
506+
// FIXME: Remove check that the place is initialized. This is
507+
// needed for now because matches don't have never patterns yet.
508+
// So this is the only place we prevent
509+
// let x: !;
510+
// match x {};
511+
// from compiling.
512+
self.check_if_path_or_subpath_is_moved(
503513
ContextKind::FakeRead.new(location),
514+
InitializationRequiringAction::Use,
504515
(place, span),
505-
(Deep, Read(ReadKind::Borrow(BorrowKind::Shared))),
506-
LocalMutationIsAllowed::No,
507516
flow_state,
508517
);
509518
}
@@ -755,6 +764,7 @@ use self::AccessDepth::{Deep, Shallow};
755764
enum ArtificialField {
756765
Discriminant,
757766
ArrayLength,
767+
ShallowBorrow,
758768
}
759769

760770
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
@@ -835,6 +845,7 @@ enum LocalMutationIsAllowed {
835845
enum InitializationRequiringAction {
836846
Update,
837847
Borrow,
848+
MatchOn,
838849
Use,
839850
Assignment,
840851
}
@@ -849,6 +860,7 @@ impl InitializationRequiringAction {
849860
match self {
850861
InitializationRequiringAction::Update => "update",
851862
InitializationRequiringAction::Borrow => "borrow",
863+
InitializationRequiringAction::MatchOn => "use", // no good noun
852864
InitializationRequiringAction::Use => "use",
853865
InitializationRequiringAction::Assignment => "assign",
854866
}
@@ -858,6 +870,7 @@ impl InitializationRequiringAction {
858870
match self {
859871
InitializationRequiringAction::Update => "updated",
860872
InitializationRequiringAction::Borrow => "borrowed",
873+
InitializationRequiringAction::MatchOn => "matched on",
861874
InitializationRequiringAction::Use => "used",
862875
InitializationRequiringAction::Assignment => "assigned",
863876
}
@@ -972,7 +985,13 @@ impl<'cx, 'gcx, 'tcx> MirBorrowckCtxt<'cx, 'gcx, 'tcx> {
972985
Control::Continue
973986
}
974987

975-
(Read(_), BorrowKind::Shared) | (Reservation(..), BorrowKind::Shared) => {
988+
(Read(_), BorrowKind::Shared) | (Reservation(..), BorrowKind::Shared)
989+
| (Read(_), BorrowKind::Shallow) | (Reservation(..), BorrowKind::Shallow) => {
990+
Control::Continue
991+
}
992+
993+
(Write(WriteKind::Move), BorrowKind::Shallow) => {
994+
// Handled by initialization checks.
976995
Control::Continue
977996
}
978997

@@ -1108,6 +1127,9 @@ impl<'cx, 'gcx, 'tcx> MirBorrowckCtxt<'cx, 'gcx, 'tcx> {
11081127
match *rvalue {
11091128
Rvalue::Ref(_ /*rgn*/, bk, ref place) => {
11101129
let access_kind = match bk {
1130+
BorrowKind::Shallow => {
1131+
(Shallow(Some(ArtificialField::ShallowBorrow)), Read(ReadKind::Borrow(bk)))
1132+
},
11111133
BorrowKind::Shared => (Deep, Read(ReadKind::Borrow(bk))),
11121134
BorrowKind::Unique | BorrowKind::Mut { .. } => {
11131135
let wk = WriteKind::MutableBorrow(bk);
@@ -1127,9 +1149,15 @@ impl<'cx, 'gcx, 'tcx> MirBorrowckCtxt<'cx, 'gcx, 'tcx> {
11271149
flow_state,
11281150
);
11291151

1152+
let action = if bk == BorrowKind::Shallow {
1153+
InitializationRequiringAction::MatchOn
1154+
} else {
1155+
InitializationRequiringAction::Borrow
1156+
};
1157+
11301158
self.check_if_path_or_subpath_is_moved(
11311159
context,
1132-
InitializationRequiringAction::Borrow,
1160+
action,
11331161
(place, span),
11341162
flow_state,
11351163
);
@@ -1315,11 +1343,16 @@ impl<'cx, 'gcx, 'tcx> MirBorrowckCtxt<'cx, 'gcx, 'tcx> {
13151343
return;
13161344
}
13171345

1318-
// FIXME: replace this with a proper borrow_conflicts_with_place when
1319-
// that is merged.
13201346
let sd = if might_be_alive { Deep } else { Shallow(None) };
13211347

1322-
if places_conflict::places_conflict(self.infcx.tcx, self.mir, place, root_place, sd) {
1348+
if places_conflict::borrow_conflicts_with_place(
1349+
self.infcx.tcx,
1350+
self.mir,
1351+
place,
1352+
borrow.kind,
1353+
root_place,
1354+
sd
1355+
) {
13231356
debug!("check_for_invalidation_at_exit({:?}): INVALID", place);
13241357
// FIXME: should be talking about the region lifetime instead
13251358
// of just a span here.
@@ -1369,7 +1402,7 @@ impl<'cx, 'gcx, 'tcx> MirBorrowckCtxt<'cx, 'gcx, 'tcx> {
13691402

13701403
// only mutable borrows should be 2-phase
13711404
assert!(match borrow.kind {
1372-
BorrowKind::Shared => false,
1405+
BorrowKind::Shared | BorrowKind::Shallow => false,
13731406
BorrowKind::Unique | BorrowKind::Mut { .. } => true,
13741407
});
13751408

@@ -1669,7 +1702,7 @@ impl<'cx, 'gcx, 'tcx> MirBorrowckCtxt<'cx, 'gcx, 'tcx> {
16691702
let is_local_mutation_allowed = match borrow_kind {
16701703
BorrowKind::Unique => LocalMutationIsAllowed::Yes,
16711704
BorrowKind::Mut { .. } => is_local_mutation_allowed,
1672-
BorrowKind::Shared => unreachable!(),
1705+
BorrowKind::Shared | BorrowKind::Shallow => unreachable!(),
16731706
};
16741707
match self.is_mutable(place, is_local_mutation_allowed) {
16751708
Ok(root_place) => {
@@ -1699,8 +1732,10 @@ impl<'cx, 'gcx, 'tcx> MirBorrowckCtxt<'cx, 'gcx, 'tcx> {
16991732
| Write(wk @ WriteKind::Move)
17001733
| Reservation(wk @ WriteKind::StorageDeadOrDrop)
17011734
| Reservation(wk @ WriteKind::MutableBorrow(BorrowKind::Shared))
1735+
| Reservation(wk @ WriteKind::MutableBorrow(BorrowKind::Shallow))
17021736
| Write(wk @ WriteKind::StorageDeadOrDrop)
1703-
| Write(wk @ WriteKind::MutableBorrow(BorrowKind::Shared)) => {
1737+
| Write(wk @ WriteKind::MutableBorrow(BorrowKind::Shared))
1738+
| Write(wk @ WriteKind::MutableBorrow(BorrowKind::Shallow)) => {
17041739
if let Err(_place_err) = self.is_mutable(place, is_local_mutation_allowed) {
17051740
if self.infcx.tcx.migrate_borrowck() {
17061741
// rust-lang/rust#46908: In pure NLL mode this
@@ -1743,6 +1778,7 @@ impl<'cx, 'gcx, 'tcx> MirBorrowckCtxt<'cx, 'gcx, 'tcx> {
17431778
Read(ReadKind::Borrow(BorrowKind::Unique))
17441779
| Read(ReadKind::Borrow(BorrowKind::Mut { .. }))
17451780
| Read(ReadKind::Borrow(BorrowKind::Shared))
1781+
| Read(ReadKind::Borrow(BorrowKind::Shallow))
17461782
| Read(ReadKind::Copy) => {
17471783
// Access authorized
17481784
return false;

0 commit comments

Comments
 (0)