centralize does_not_live_long_enough error reporting

This commit is contained in:
Ariel Ben-Yehuda 2017-12-10 17:11:02 +02:00
parent ed636c5c5a
commit e798cb0e52
3 changed files with 89 additions and 83 deletions

View File

@ -16,7 +16,7 @@ use rustc_data_structures::indexed_vec::Idx;
use super::{MirBorrowckCtxt, Context}; use super::{MirBorrowckCtxt, Context};
use super::{InitializationRequiringAction, PrefixSet}; use super::{InitializationRequiringAction, PrefixSet};
use dataflow::{BorrowData, FlowAtLocation, MovingOutStatements}; use dataflow::{BorrowData, Borrows, FlowAtLocation, MovingOutStatements};
use dataflow::move_paths::MovePathIndex; use dataflow::move_paths::MovePathIndex;
use util::borrowck_errors::{BorrowckErrors, Origin}; use util::borrowck_errors::{BorrowckErrors, Origin};
@ -319,18 +319,43 @@ impl<'cx, 'gcx, 'tcx> MirBorrowckCtxt<'cx, 'gcx, 'tcx> {
pub(super) fn report_borrowed_value_does_not_live_long_enough( pub(super) fn report_borrowed_value_does_not_live_long_enough(
&mut self, &mut self,
_: Context, _: Context,
(place, span): (&Place<'tcx>, Span), borrow: &BorrowData<'tcx>,
end_span: Option<Span>, drop_span: Span,
borrows: &Borrows<'cx, 'gcx, 'tcx>
) { ) {
let root_place = self.prefixes(place, PrefixSet::All).last().unwrap(); let end_span = borrows.opt_region_end_span(&borrow.region);
let root_place = self.prefixes(&borrow.place, PrefixSet::All).last().unwrap();
match root_place {
&Place::Local(local) => {
if let Some(_) = self.storage_dead_or_drop_error_reported_l.replace(local) {
debug!("report_does_not_live_long_enough({:?}): <suppressed>",
(borrow, drop_span));
return
}
}
&Place::Static(ref statik) => {
if let Some(_) = self.storage_dead_or_drop_error_reported_s
.replace(statik.def_id)
{
debug!("report_does_not_live_long_enough({:?}): <suppressed>",
(borrow, drop_span));
return
}
},
&Place::Projection(_) =>
unreachable!("root_place is an unreachable???")
};
let proper_span = match *root_place { let proper_span = match *root_place {
Place::Local(local) => self.mir.local_decls[local].source_info.span, Place::Local(local) => self.mir.local_decls[local].source_info.span,
_ => span, _ => drop_span,
}; };
let mut err = self.tcx let mut err = self.tcx
.path_does_not_live_long_enough(span, "borrowed value", Origin::Mir); .path_does_not_live_long_enough(drop_span, "borrowed value", Origin::Mir);
err.span_label(proper_span, "temporary value created here"); err.span_label(proper_span, "temporary value created here");
err.span_label(span, "temporary value dropped here while still borrowed"); err.span_label(drop_span, "temporary value dropped here while still borrowed");
err.note("consider using a `let` binding to increase its lifetime"); err.note("consider using a `let` binding to increase its lifetime");
if let Some(end) = end_span { if let Some(end) = end_span {

View File

@ -233,7 +233,8 @@ fn do_mir_borrowck<'a, 'gcx, 'tcx>(
hir::BodyOwnerKind::Static(_) => false, hir::BodyOwnerKind::Static(_) => false,
hir::BodyOwnerKind::Fn => true, hir::BodyOwnerKind::Fn => true,
}, },
storage_dead_or_drop_error_reported: FxHashSet(), storage_dead_or_drop_error_reported_l: FxHashSet(),
storage_dead_or_drop_error_reported_s: FxHashSet(),
}; };
mbcx.analyze_results(&mut state); // entry point for DataflowResultsConsumer mbcx.analyze_results(&mut state); // entry point for DataflowResultsConsumer
@ -258,7 +259,9 @@ pub struct MirBorrowckCtxt<'cx, 'gcx: 'tcx, 'tcx: 'cx> {
/// This field keeps track of when storage dead or drop errors are reported /// This field keeps track of when storage dead or drop errors are reported
/// in order to stop duplicate error reporting and identify the conditions required /// in order to stop duplicate error reporting and identify the conditions required
/// for a "temporary value dropped here while still borrowed" error. See #45360. /// for a "temporary value dropped here while still borrowed" error. See #45360.
storage_dead_or_drop_error_reported: FxHashSet<Local>, storage_dead_or_drop_error_reported_l: FxHashSet<Local>,
/// Same as the above, but for statics (thread-locals)
storage_dead_or_drop_error_reported_s: FxHashSet<DefId>,
} }
// Check that: // Check that:
@ -502,19 +505,9 @@ impl<'cx, 'gcx, 'tcx> DataflowResultsConsumer<'cx, 'tcx> for MirBorrowckCtxt<'cx
flow_state.borrows.with_elems_outgoing(|borrows| { flow_state.borrows.with_elems_outgoing(|borrows| {
for i in borrows { for i in borrows {
let borrow = &data[i]; let borrow = &data[i];
let context = ContextKind::StorageDead.new(loc);
if self.place_is_invalidated_at_exit(&borrow.place) { self.check_for_invalidation_at_exit(context, borrow, span, flow_state);
debug!("borrow conflicts at exit {:?}", borrow);
// FIXME: should be talking about the region lifetime instead
// of just a span here.
let end_span = domain.opt_region_end_span(&borrow.region);
self.report_borrowed_value_does_not_live_long_enough(
ContextKind::StorageDead.new(loc),
(&borrow.place, end_span.unwrap_or(span)),
end_span,
)
}
} }
}); });
} }
@ -663,34 +656,11 @@ impl<'cx, 'gcx, 'tcx> MirBorrowckCtxt<'cx, 'gcx, 'tcx> {
) -> AccessErrorsReported { ) -> AccessErrorsReported {
let (sd, rw) = kind; let (sd, rw) = kind;
let storage_dead_or_drop_local = match (place_span.0, rw) {
(&Place::Local(local), Write(WriteKind::StorageDeadOrDrop)) => Some(local),
_ => None,
};
// Check if error has already been reported to stop duplicate reporting.
if let Some(local) = storage_dead_or_drop_local {
if self.storage_dead_or_drop_error_reported.contains(&local) {
return AccessErrorsReported {
mutability_error: false,
conflict_error: true
};
}
}
let mutability_error = let mutability_error =
self.check_access_permissions(place_span, rw, is_local_mutation_allowed); self.check_access_permissions(place_span, rw, is_local_mutation_allowed);
let conflict_error = let conflict_error =
self.check_access_for_conflict(context, place_span, sd, rw, flow_state); self.check_access_for_conflict(context, place_span, sd, rw, flow_state);
// A conflict with a storagedead/drop is a "borrow does not live long enough"
// error. Avoid reporting such an error multiple times for the same local.
if conflict_error {
if let Some(local) = storage_dead_or_drop_local {
self.storage_dead_or_drop_error_reported.insert(local);
}
}
AccessErrorsReported { mutability_error, conflict_error } AccessErrorsReported { mutability_error, conflict_error }
} }
@ -749,16 +719,9 @@ impl<'cx, 'gcx, 'tcx> MirBorrowckCtxt<'cx, 'gcx, 'tcx> {
) )
} }
WriteKind::StorageDeadOrDrop => { WriteKind::StorageDeadOrDrop => {
let end_span = flow_state
.borrows
.operator()
.opt_region_end_span(&borrow.region);
error_reported = true; error_reported = true;
this.report_borrowed_value_does_not_live_long_enough( this.report_borrowed_value_does_not_live_long_enough(
context, context, borrow, place_span.1, flow_state.borrows.operator());
place_span,
end_span,
)
} }
WriteKind::Mutate => { WriteKind::Mutate => {
error_reported = true; error_reported = true;
@ -947,8 +910,14 @@ impl<'cx, 'gcx, 'tcx> MirBorrowckCtxt<'cx, 'gcx, 'tcx> {
/// Returns whether a borrow of this place is invalidated when the function /// Returns whether a borrow of this place is invalidated when the function
/// exits /// exits
fn place_is_invalidated_at_exit(&mut self, place: &Place<'tcx>) -> bool { fn check_for_invalidation_at_exit(&mut self,
debug!("place_is_invalidated_at_exit({:?})", place); context: Context,
borrow: &BorrowData<'tcx>,
span: Span,
flow_state: &Flows<'cx, 'gcx, 'tcx>)
{
debug!("check_for_invalidation_at_exit({:?})", borrow);
let place = &borrow.place;
let root_place = self.prefixes(place, PrefixSet::All).last().unwrap(); let root_place = self.prefixes(place, PrefixSet::All).last().unwrap();
// FIXME(nll-rfc#40): do more precise destructor tracking here. For now // FIXME(nll-rfc#40): do more precise destructor tracking here. For now
@ -956,7 +925,7 @@ impl<'cx, 'gcx, 'tcx> MirBorrowckCtxt<'cx, 'gcx, 'tcx> {
// we'll have a memory leak) and assume that all statics have a destructor. // we'll have a memory leak) and assume that all statics have a destructor.
// //
// FIXME: allow thread-locals to borrow other thread locals? // FIXME: allow thread-locals to borrow other thread locals?
let (might_be_alive, will_be_dropped, local) = match root_place { let (might_be_alive, will_be_dropped) = match root_place {
Place::Static(statik) => { Place::Static(statik) => {
// Thread-locals might be dropped after the function exits, but // Thread-locals might be dropped after the function exits, but
// "true" statics will never be. // "true" statics will never be.
@ -965,12 +934,12 @@ impl<'cx, 'gcx, 'tcx> MirBorrowckCtxt<'cx, 'gcx, 'tcx> {
.iter() .iter()
.any(|attr| attr.check_name("thread_local")); .any(|attr| attr.check_name("thread_local"));
(true, is_thread_local, None) (true, is_thread_local)
} }
Place::Local(local) => { Place::Local(_) => {
// Locals are always dropped at function exit, and if they // Locals are always dropped at function exit, and if they
// have a destructor it would've been called already. // have a destructor it would've been called already.
(false, self.locals_are_invalidated_at_exit, Some(*local)) (false, self.locals_are_invalidated_at_exit)
} }
Place::Projection(..) => { Place::Projection(..) => {
bug!("root of {:?} is a projection ({:?})?", place, root_place) bug!("root of {:?} is a projection ({:?})?", place, root_place)
@ -982,33 +951,31 @@ impl<'cx, 'gcx, 'tcx> MirBorrowckCtxt<'cx, 'gcx, 'tcx> {
"place_is_invalidated_at_exit({:?}) - won't be dropped", "place_is_invalidated_at_exit({:?}) - won't be dropped",
place place
); );
return false; return;
} }
// FIXME: replace this with a proper borrow_conflicts_with_place when // FIXME: replace this with a proper borrow_conflicts_with_place when
// that is merged. // that is merged.
let prefix_set = if might_be_alive { let sd = if might_be_alive {
PrefixSet::Supporting Deep
} else { } else {
PrefixSet::Shallow Shallow(None)
}; };
let result = if self.places_conflict(place, root_place, sd) {
self.prefixes(place, prefix_set).any(|prefix| prefix == root_place); debug!("check_for_invalidation_at_exit({:?}): INVALID", place);
// FIXME: should be talking about the region lifetime instead
if result { // of just a span here.
if let Some(local) = local { self.report_borrowed_value_does_not_live_long_enough(
if let Some(_) = self.storage_dead_or_drop_error_reported.replace(local) { context,
debug!("place_is_invalidated_at_exit({:?}) - suppressed", place); borrow,
return false; span.end_point(),
flow_state.borrows.operator()
)
} }
} }
} }
result
}
}
impl<'cx, 'gcx, 'tcx> MirBorrowckCtxt<'cx, 'gcx, 'tcx> { impl<'cx, 'gcx, 'tcx> MirBorrowckCtxt<'cx, 'gcx, 'tcx> {
fn check_if_reassignment_to_immutable_state( fn check_if_reassignment_to_immutable_state(
&mut self, &mut self,
@ -1358,7 +1325,9 @@ impl<'cx, 'gcx, 'tcx> MirBorrowckCtxt<'cx, 'gcx, 'tcx> {
// Mutably borrowed data is mutable, but only if we have a // Mutably borrowed data is mutable, but only if we have a
// unique path to the `&mut` // unique path to the `&mut`
hir::MutMutable => { hir::MutMutable => {
let mode = match self.is_upvar_field_projection(&proj.base) { let mode = match
self.is_upvar_field_projection(&proj.base)
{
Some(field) if { Some(field) if {
self.mir.upvar_decls[field.index()].by_ref self.mir.upvar_decls[field.index()].by_ref
} => is_local_mutation_allowed, } => is_local_mutation_allowed,
@ -1485,10 +1454,18 @@ impl<'cx, 'gcx, 'tcx> MirBorrowckCtxt<'cx, 'gcx, 'tcx> {
Overlap::Disjoint Overlap::Disjoint
} }
} }
(Place::Static(..), Place::Static(..)) => { (Place::Static(static1), Place::Static(static2)) => {
// Borrows of statics do not have to be tracked here. if static1.def_id != static2.def_id {
debug!("place_element_conflict: IGNORED-STATIC"); debug!("place_element_conflict: DISJOINT-STATIC");
Overlap::Disjoint Overlap::Disjoint
} else if self.tcx.is_static_mut(static1.def_id) {
// We ignore mutable statics - they can only be unsafe code.
debug!("place_element_conflict: IGNORE-STATIC-MUT");
Overlap::Disjoint
} else {
debug!("place_element_conflict: DISJOINT-OR-EQ-STATIC");
Overlap::EqualOrDisjoint
}
} }
(Place::Local(_), Place::Static(_)) | (Place::Local(_), Place::Static(_)) |
(Place::Static(_), Place::Local(_)) => { (Place::Static(_), Place::Local(_)) => {

View File

@ -8,6 +8,9 @@
// option. This file may not be copied, modified, or distributed // option. This file may not be copied, modified, or distributed
// except according to those terms. // except according to those terms.
// revisions: ast mir
//[mir]compile-flags: -Z borrowck=mir
#![feature(thread_local)] #![feature(thread_local)]
#[thread_local] #[thread_local]
@ -15,11 +18,12 @@ static FOO: u8 = 3;
fn main() { fn main() {
let a = &FOO; let a = &FOO;
//~^ ERROR borrowed value does not live long enough //[ast]~^ ERROR borrowed value does not live long enough
//~| does not live long enough //[ast]~| does not live long enough
//~| NOTE borrowed value must be valid for the static lifetime //[ast]~| NOTE borrowed value must be valid for the static lifetime
std::thread::spawn(move || { std::thread::spawn(move || {
println!("{}", a); println!("{}", a);
}); });
} //~ temporary value only lives until here } //[ast]~ temporary value only lives until here
//[mir]~^ ERROR borrowed value does not live long enough