Skip to content

Commit 7714c43

Browse files
committed
Auto merge of #54310 - pnkfelix:issue-52059-report-borrow-drop-conflict, r=nikomatsakis
Report when borrow could cause `&mut` aliasing during Drop We were already issuing an error for the cases where this cropped up, so this is not fixing any soundness holes. The previous diagnostic just wasn't accurately describing the problem in the user's code. Fix #52059
2 parents 576b640 + c9cf499 commit 7714c43

16 files changed

+346
-73
lines changed

src/librustc_mir/borrow_check/error_reporting.rs

+92-1
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,8 @@
88
// option. This file may not be copied, modified, or distributed
99
// except according to those terms.
1010

11-
use borrow_check::WriteKind;
11+
use borrow_check::{WriteKind, StorageDeadOrDrop};
12+
use borrow_check::prefixes::IsPrefixOf;
1213
use rustc::middle::region::ScopeTree;
1314
use rustc::mir::VarBindingForm;
1415
use rustc::mir::{BindingForm, BorrowKind, ClearCrossCrate, Field, Local};
@@ -394,13 +395,27 @@ impl<'cx, 'gcx, 'tcx> MirBorrowckCtxt<'cx, 'gcx, 'tcx> {
394395
err.buffer(&mut self.errors_buffer);
395396
}
396397

398+
/// Reports StorageDeadOrDrop of `place` conflicts with `borrow`.
399+
///
400+
/// This means that some data referenced by `borrow` needs to live
401+
/// past the point where the StorageDeadOrDrop of `place` occurs.
402+
/// This is usually interpreted as meaning that `place` has too
403+
/// short a lifetime. (But sometimes it is more useful to report
404+
/// it as a more direct conflict between the execution of a
405+
/// `Drop::drop` with an aliasing borrow.)
397406
pub(super) fn report_borrowed_value_does_not_live_long_enough(
398407
&mut self,
399408
context: Context,
400409
borrow: &BorrowData<'tcx>,
401410
place_span: (&Place<'tcx>, Span),
402411
kind: Option<WriteKind>,
403412
) {
413+
debug!("report_borrowed_value_does_not_live_long_enough(\
414+
{:?}, {:?}, {:?}, {:?}\
415+
)",
416+
context, borrow, place_span, kind
417+
);
418+
404419
let drop_span = place_span.1;
405420
let scope_tree = self.tcx.region_scope_tree(self.mir_def_id);
406421
let root_place = self
@@ -432,6 +447,19 @@ impl<'cx, 'gcx, 'tcx> MirBorrowckCtxt<'cx, 'gcx, 'tcx> {
432447

433448
let borrow_reason = self.find_why_borrow_contains_point(context, borrow);
434449

450+
if let Some(WriteKind::StorageDeadOrDrop(StorageDeadOrDrop::Destructor)) = kind
451+
{
452+
// If a borrow of path `B` conflicts with drop of `D` (and
453+
// we're not in the uninteresting case where `B` is a
454+
// prefix of `D`), then report this as a more interesting
455+
// destructor conflict.
456+
if !borrow.borrowed_place.is_prefix_of(place_span.0) {
457+
self.report_borrow_conflicts_with_destructor(
458+
context, borrow, borrow_reason, place_span, kind);
459+
return;
460+
}
461+
}
462+
435463
let mut err = match &self.describe_place(&borrow.borrowed_place) {
436464
Some(_) if self.is_place_thread_local(root_place) => {
437465
self.report_thread_local_value_does_not_live_long_enough(drop_span, borrow_span)
@@ -495,6 +523,69 @@ impl<'cx, 'gcx, 'tcx> MirBorrowckCtxt<'cx, 'gcx, 'tcx> {
495523
err
496524
}
497525

526+
pub(super) fn report_borrow_conflicts_with_destructor(
527+
&mut self,
528+
context: Context,
529+
borrow: &BorrowData<'tcx>,
530+
borrow_reason: BorrowContainsPointReason<'tcx>,
531+
place_span: (&Place<'tcx>, Span),
532+
kind: Option<WriteKind>,
533+
) {
534+
debug!(
535+
"report_borrow_conflicts_with_destructor(\
536+
{:?}, {:?}, {:?}, {:?} {:?}\
537+
)",
538+
context, borrow, borrow_reason, place_span, kind,
539+
);
540+
541+
let borrow_spans = self.retrieve_borrow_spans(borrow);
542+
let borrow_span = borrow_spans.var_or_use();
543+
544+
let mut err = self.tcx.cannot_borrow_across_destructor(borrow_span, Origin::Mir);
545+
546+
let drop_span = place_span.1;
547+
548+
let (what_was_dropped, dropped_ty) = {
549+
let place = place_span.0;
550+
let desc = match self.describe_place(place) {
551+
Some(name) => format!("`{}`", name.as_str()),
552+
None => format!("temporary value"),
553+
};
554+
let ty = place.ty(self.mir, self.tcx).to_ty(self.tcx);
555+
(desc, ty)
556+
};
557+
558+
let label = match dropped_ty.sty {
559+
ty::Adt(adt, _) if adt.has_dtor(self.tcx) && !adt.is_box() => {
560+
match self.describe_place(&borrow.borrowed_place) {
561+
Some(borrowed) =>
562+
format!("here, drop of {D} needs exclusive access to `{B}`, \
563+
because the type `{T}` implements the `Drop` trait",
564+
D=what_was_dropped, T=dropped_ty, B=borrowed),
565+
None =>
566+
format!("here is drop of {D}; whose type `{T}` implements the `Drop` trait",
567+
D=what_was_dropped, T=dropped_ty),
568+
}
569+
}
570+
_ => format!("drop of {D} occurs here", D=what_was_dropped),
571+
};
572+
err.span_label(drop_span, label);
573+
574+
// Only give this note and suggestion if they could be relevant
575+
match borrow_reason {
576+
BorrowContainsPointReason::Liveness {..}
577+
| BorrowContainsPointReason::DropLiveness {..} => {
578+
err.note("consider using a `let` binding to create a longer lived value");
579+
}
580+
BorrowContainsPointReason::OutlivesFreeRegion {..} => (),
581+
}
582+
583+
self.report_why_borrow_contains_point(
584+
&mut err, borrow_reason, kind.map(|k| (k, place_span.0)));
585+
586+
err.buffer(&mut self.errors_buffer);
587+
}
588+
498589
fn report_thread_local_value_does_not_live_long_enough(
499590
&mut self,
500591
drop_span: Span,

src/librustc_mir/borrow_check/mod.rs

+28-16
Original file line numberDiff line numberDiff line change
@@ -574,7 +574,8 @@ impl<'cx, 'gcx, 'tcx> DataflowResultsConsumer<'cx, 'tcx> for MirBorrowckCtxt<'cx
574574
self.access_place(
575575
ContextKind::StorageDead.new(location),
576576
(&Place::Local(local), span),
577-
(Shallow(None), Write(WriteKind::StorageDeadOrDrop)),
577+
(Shallow(None), Write(WriteKind::StorageDeadOrDrop(
578+
StorageDeadOrDrop::LocalStorageDead))),
578579
LocalMutationIsAllowed::Yes,
579580
flow_state,
580581
);
@@ -801,12 +802,21 @@ enum ReadKind {
801802
/// (For informational purposes only)
802803
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
803804
enum WriteKind {
804-
StorageDeadOrDrop,
805+
StorageDeadOrDrop(StorageDeadOrDrop),
805806
MutableBorrow(BorrowKind),
806807
Mutate,
807808
Move,
808809
}
809810

811+
/// Specify whether which case a StorageDeadOrDrop is in.
812+
/// (For informational purposes only)
813+
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
814+
enum StorageDeadOrDrop {
815+
LocalStorageDead,
816+
BoxedStorageDead,
817+
Destructor,
818+
}
819+
810820
/// When checking permissions for a place access, this flag is used to indicate that an immutable
811821
/// local place can be mutated.
812822
///
@@ -1035,7 +1045,8 @@ impl<'cx, 'gcx, 'tcx> MirBorrowckCtxt<'cx, 'gcx, 'tcx> {
10351045
self.access_place(
10361046
ContextKind::Drop.new(loc),
10371047
(drop_place, span),
1038-
(Deep, Write(WriteKind::StorageDeadOrDrop)),
1048+
(Deep, Write(WriteKind::StorageDeadOrDrop(
1049+
StorageDeadOrDrop::Destructor))),
10391050
LocalMutationIsAllowed::Yes,
10401051
flow_state,
10411052
);
@@ -1062,15 +1073,17 @@ impl<'cx, 'gcx, 'tcx> MirBorrowckCtxt<'cx, 'gcx, 'tcx> {
10621073
// Why? Because we do not schedule/emit
10631074
// `Drop(x)` in the MIR unless `x` needs drop in
10641075
// the first place.
1065-
//
1066-
// FIXME: Its possible this logic actually should
1067-
// be attached to the `StorageDead` statement
1068-
// rather than the `Drop`. See discussion on PR
1069-
// #52782.
10701076
self.access_place(
10711077
ContextKind::Drop.new(loc),
10721078
(drop_place, span),
1073-
(Shallow(None), Write(WriteKind::StorageDeadOrDrop)),
1079+
(Shallow(None), Write(WriteKind::StorageDeadOrDrop(
1080+
// rust-lang/rust#52059: distinguish
1081+
// between invaliding the backing storage
1082+
// vs running a destructor.
1083+
//
1084+
// See also: rust-lang/rust#52782,
1085+
// specifically #discussion_r206658948
1086+
StorageDeadOrDrop::BoxedStorageDead))),
10741087
LocalMutationIsAllowed::Yes,
10751088
flow_state,
10761089
);
@@ -1238,14 +1251,13 @@ impl<'cx, 'gcx, 'tcx> MirBorrowckCtxt<'cx, 'gcx, 'tcx> {
12381251
error_reported = true;
12391252
this.report_conflicting_borrow(context, place_span, bk, &borrow)
12401253
}
1241-
WriteKind::StorageDeadOrDrop => {
1254+
WriteKind::StorageDeadOrDrop(_) => {
12421255
error_reported = true;
12431256
this.report_borrowed_value_does_not_live_long_enough(
12441257
context,
12451258
borrow,
12461259
place_span,
1247-
Some(kind),
1248-
);
1260+
Some(kind))
12491261
}
12501262
WriteKind::Mutate => {
12511263
error_reported = true;
@@ -1487,7 +1499,7 @@ impl<'cx, 'gcx, 'tcx> MirBorrowckCtxt<'cx, 'gcx, 'tcx> {
14871499
}
14881500
}
14891501

1490-
/// Returns whether a borrow of this place is invalidated when the function
1502+
/// Checks whether a borrow of this place is invalidated when the function
14911503
/// exits
14921504
fn check_for_invalidation_at_exit(
14931505
&mut self,
@@ -1912,9 +1924,9 @@ impl<'cx, 'gcx, 'tcx> MirBorrowckCtxt<'cx, 'gcx, 'tcx> {
19121924

19131925
Reservation(wk @ WriteKind::Move)
19141926
| Write(wk @ WriteKind::Move)
1915-
| Reservation(wk @ WriteKind::StorageDeadOrDrop)
1927+
| Reservation(wk @ WriteKind::StorageDeadOrDrop(_))
19161928
| Reservation(wk @ WriteKind::MutableBorrow(BorrowKind::Shared))
1917-
| Write(wk @ WriteKind::StorageDeadOrDrop)
1929+
| Write(wk @ WriteKind::StorageDeadOrDrop(_))
19181930
| Write(wk @ WriteKind::MutableBorrow(BorrowKind::Shared)) => {
19191931
if let Err(_place_err) = self.is_mutable(place, is_local_mutation_allowed) {
19201932
if self.tcx.migrate_borrowck() {
@@ -1929,7 +1941,7 @@ impl<'cx, 'gcx, 'tcx> MirBorrowckCtxt<'cx, 'gcx, 'tcx> {
19291941
error_access = match wk {
19301942
WriteKind::MutableBorrow(_) => AccessKind::MutableBorrow,
19311943
WriteKind::Move => AccessKind::Move,
1932-
WriteKind::StorageDeadOrDrop |
1944+
WriteKind::StorageDeadOrDrop(_) |
19331945
WriteKind::Mutate => AccessKind::Mutate,
19341946
};
19351947
self.report_mutability_error(

src/librustc_mir/borrow_check/nll/explain_borrow/mod.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -160,7 +160,7 @@ impl<'cx, 'gcx, 'tcx> MirBorrowckCtxt<'cx, 'gcx, 'tcx> {
160160
format!("borrow later used here, when `{}` is dropped", local_name),
161161
);
162162

163-
if let Some((WriteKind::StorageDeadOrDrop, place)) = kind_place {
163+
if let Some((WriteKind::StorageDeadOrDrop(_), place)) = kind_place {
164164
if let Place::Local(borrowed_local) = place {
165165
let dropped_local_scope = mir.local_decls[local].visibility_scope;
166166
let borrowed_local_scope =

src/librustc_mir/borrow_check/nll/invalidation.rs

+5-3
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ use borrow_check::{ReadOrWrite, Activation, Read, Reservation, Write};
1616
use borrow_check::{Context, ContextKind};
1717
use borrow_check::{LocalMutationIsAllowed, MutateMode};
1818
use borrow_check::ArtificialField;
19-
use borrow_check::{ReadKind, WriteKind};
19+
use borrow_check::{ReadKind, WriteKind, StorageDeadOrDrop};
2020
use borrow_check::nll::facts::AllFacts;
2121
use borrow_check::path_utils::*;
2222
use dataflow::move_paths::indexes::BorrowIndex;
@@ -154,7 +154,8 @@ impl<'cg, 'cx, 'tcx, 'gcx> Visitor<'tcx> for InvalidationGenerator<'cg, 'cx, 'tc
154154
self.access_place(
155155
ContextKind::StorageDead.new(location),
156156
&Place::Local(local),
157-
(Shallow(None), Write(WriteKind::StorageDeadOrDrop)),
157+
(Shallow(None), Write(WriteKind::StorageDeadOrDrop(
158+
StorageDeadOrDrop::LocalStorageDead))),
158159
LocalMutationIsAllowed::Yes,
159160
);
160161
}
@@ -347,7 +348,8 @@ impl<'cg, 'cx, 'tcx, 'gcx> InvalidationGenerator<'cg, 'cx, 'tcx, 'gcx> {
347348
self.access_place(
348349
ContextKind::Drop.new(loc),
349350
drop_place,
350-
(Deep, Write(WriteKind::StorageDeadOrDrop)),
351+
(Deep, Write(WriteKind::StorageDeadOrDrop(
352+
StorageDeadOrDrop::Destructor))),
351353
LocalMutationIsAllowed::Yes,
352354
);
353355
}

src/librustc_mir/diagnostics.rs

+57
Original file line numberDiff line numberDiff line change
@@ -2187,6 +2187,63 @@ fn main() {
21872187
```
21882188
"##,
21892189

2190+
E0713: r##"
2191+
This error occurs when an attempt is made to borrow state past the end of the
2192+
lifetime of a type that implements the `Drop` trait.
2193+
2194+
Example of erroneous code:
2195+
2196+
```compile_fail,E0713
2197+
#![feature(nll)]
2198+
2199+
pub struct S<'a> { data: &'a mut String }
2200+
2201+
impl<'a> Drop for S<'a> {
2202+
fn drop(&mut self) { self.data.push_str("being dropped"); }
2203+
}
2204+
2205+
fn demo<'a>(s: S<'a>) -> &'a mut String { let p = &mut *s.data; p }
2206+
```
2207+
2208+
Here, `demo` tries to borrow the string data held within its
2209+
argument `s` and then return that borrow. However, `S` is
2210+
declared as implementing `Drop`.
2211+
2212+
Structs implementing the `Drop` trait have an implicit destructor that
2213+
gets called when they go out of scope. This destructor gets exclusive
2214+
access to the fields of the struct when it runs.
2215+
2216+
This means that when `s` reaches the end of `demo`, its destructor
2217+
gets exclusive access to its `&mut`-borrowed string data. allowing
2218+
another borrow of that string data (`p`), to exist across the drop of
2219+
`s` would be a violation of the principle that `&mut`-borrows have
2220+
exclusive, unaliased access to their referenced data.
2221+
2222+
This error can be fixed by changing `demo` so that the destructor does
2223+
not run while the string-data is borrowed; for example by taking `S`
2224+
by reference:
2225+
2226+
```
2227+
#![feature(nll)]
2228+
2229+
pub struct S<'a> { data: &'a mut String }
2230+
2231+
impl<'a> Drop for S<'a> {
2232+
fn drop(&mut self) { self.data.push_str("being dropped"); }
2233+
}
2234+
2235+
fn demo<'a>(s: &'a mut S<'a>) -> &'a mut String { let p = &mut *(*s).data; p }
2236+
```
2237+
2238+
Note that this approach needs a reference to S with lifetime `'a`.
2239+
Nothing shorter than `'a` will suffice: a shorter lifetime would imply
2240+
that after `demo` finishes excuting, something else (such as the
2241+
destructor!) could access `s.data` after the end of that shorter
2242+
lifetime, which would again violate the `&mut`-borrow's exclusive
2243+
access.
2244+
2245+
"##,
2246+
21902247
}
21912248

21922249
register_diagnostics! {

src/librustc_mir/util/borrowck_errors.rs

+16
Original file line numberDiff line numberDiff line change
@@ -573,6 +573,22 @@ pub trait BorrowckErrors<'cx>: Sized + Copy {
573573
self.cancel_if_wrong_origin(err, o)
574574
}
575575

576+
fn cannot_borrow_across_destructor(
577+
self,
578+
borrow_span: Span,
579+
o: Origin,
580+
) -> DiagnosticBuilder<'cx> {
581+
let err = struct_span_err!(
582+
self,
583+
borrow_span,
584+
E0713,
585+
"borrow may still be in use when destructor runs{OGN}",
586+
OGN = o
587+
);
588+
589+
self.cancel_if_wrong_origin(err, o)
590+
}
591+
576592
fn path_does_not_live_long_enough(
577593
self,
578594
span: Span,
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
1-
error[E0597]: `local.inner` does not live long enough
1+
error[E0713]: borrow may still be in use when destructor runs
22
--> $DIR/borrowck-fn-in-const-c.rs:27:16
33
|
44
LL | return &local.inner; //~ ERROR does not live long enough
5-
| ^^^^^^^^^^^^ borrowed value does not live long enough
5+
| ^^^^^^^^^^^^
66
LL | }
7-
| - `local.inner` dropped here while still borrowed
7+
| - here, drop of `local` needs exclusive access to `local.inner`, because the type `DropString` implements the `Drop` trait
88
|
99
= note: borrowed value must be valid for the static lifetime...
1010

1111
error: aborting due to previous error
1212

13-
For more information about this error, try `rustc --explain E0597`.
13+
For more information about this error, try `rustc --explain E0713`.

0 commit comments

Comments
 (0)