| use std::collections::VecDeque; |
| |
| use crate::borrow_check::borrow_set::BorrowData; |
| use crate::borrow_check::error_reporting::UseSpans; |
| use crate::borrow_check::nll::region_infer::{Cause, RegionName}; |
| use crate::borrow_check::nll::ConstraintDescription; |
| use crate::borrow_check::{MirBorrowckCtxt, WriteKind}; |
| use rustc::mir::{ |
| CastKind, ConstraintCategory, FakeReadCause, Local, Location, Body, Operand, Place, PlaceBase, |
| Rvalue, Statement, StatementKind, TerminatorKind, |
| }; |
| use rustc::ty::{self, TyCtxt}; |
| use rustc::ty::adjustment::{PointerCast}; |
| use rustc_data_structures::fx::FxHashSet; |
| use rustc_errors::DiagnosticBuilder; |
| use syntax_pos::Span; |
| |
| mod find_use; |
| |
| pub(in crate::borrow_check) enum BorrowExplanation { |
| UsedLater(LaterUseKind, Span), |
| UsedLaterInLoop(LaterUseKind, Span), |
| UsedLaterWhenDropped { |
| drop_loc: Location, |
| dropped_local: Local, |
| should_note_order: bool, |
| }, |
| MustBeValidFor { |
| category: ConstraintCategory, |
| from_closure: bool, |
| span: Span, |
| region_name: RegionName, |
| opt_place_desc: Option<String>, |
| }, |
| Unexplained, |
| } |
| |
| #[derive(Clone, Copy)] |
| pub(in crate::borrow_check) enum LaterUseKind { |
| TraitCapture, |
| ClosureCapture, |
| Call, |
| FakeLetRead, |
| Other, |
| } |
| |
| impl BorrowExplanation { |
| pub(in crate::borrow_check) fn is_explained(&self) -> bool { |
| match self { |
| BorrowExplanation::Unexplained => false, |
| _ => true, |
| } |
| } |
| pub(in crate::borrow_check) fn add_explanation_to_diagnostic<'tcx>( |
| &self, |
| tcx: TyCtxt<'tcx>, |
| body: &Body<'tcx>, |
| err: &mut DiagnosticBuilder<'_>, |
| borrow_desc: &str, |
| borrow_span: Option<Span>, |
| ) { |
| match *self { |
| BorrowExplanation::UsedLater(later_use_kind, var_or_use_span) => { |
| let message = match later_use_kind { |
| LaterUseKind::TraitCapture => "captured here by trait object", |
| LaterUseKind::ClosureCapture => "captured here by closure", |
| LaterUseKind::Call => "used by call", |
| LaterUseKind::FakeLetRead => "stored here", |
| LaterUseKind::Other => "used here", |
| }; |
| if !borrow_span.map(|sp| sp.overlaps(var_or_use_span)).unwrap_or(false) { |
| err.span_label( |
| var_or_use_span, |
| format!("{}borrow later {}", borrow_desc, message), |
| ); |
| } |
| } |
| BorrowExplanation::UsedLaterInLoop(later_use_kind, var_or_use_span) => { |
| let message = match later_use_kind { |
| LaterUseKind::TraitCapture => { |
| "borrow captured here by trait object, in later iteration of loop" |
| } |
| LaterUseKind::ClosureCapture => { |
| "borrow captured here by closure, in later iteration of loop" |
| } |
| LaterUseKind::Call => "borrow used by call, in later iteration of loop", |
| LaterUseKind::FakeLetRead => "borrow later stored here", |
| LaterUseKind::Other => "borrow used here, in later iteration of loop", |
| }; |
| err.span_label(var_or_use_span, format!("{}{}", borrow_desc, message)); |
| } |
| BorrowExplanation::UsedLaterWhenDropped { |
| drop_loc, |
| dropped_local, |
| should_note_order, |
| } => { |
| let local_decl = &body.local_decls[dropped_local]; |
| let (dtor_desc, type_desc) = match local_decl.ty.sty { |
| // If type is an ADT that implements Drop, then |
| // simplify output by reporting just the ADT name. |
| ty::Adt(adt, _substs) if adt.has_dtor(tcx) && !adt.is_box() => ( |
| "`Drop` code", |
| format!("type `{}`", tcx.def_path_str(adt.did)), |
| ), |
| |
| // Otherwise, just report the whole type (and use |
| // the intentionally fuzzy phrase "destructor") |
| ty::Closure(..) => ("destructor", "closure".to_owned()), |
| ty::Generator(..) => ("destructor", "generator".to_owned()), |
| |
| _ => ("destructor", format!("type `{}`", local_decl.ty)), |
| }; |
| |
| match local_decl.name { |
| Some(local_name) if !local_decl.from_compiler_desugaring() => { |
| let message = format!( |
| "{B}borrow might be used here, when `{LOC}` is dropped \ |
| and runs the {DTOR} for {TYPE}", |
| B = borrow_desc, |
| LOC = local_name, |
| TYPE = type_desc, |
| DTOR = dtor_desc |
| ); |
| err.span_label(body.source_info(drop_loc).span, message); |
| |
| if should_note_order { |
| err.note( |
| "values in a scope are dropped \ |
| in the opposite order they are defined", |
| ); |
| } |
| } |
| _ => { |
| err.span_label( |
| local_decl.source_info.span, |
| format!( |
| "a temporary with access to the {B}borrow \ |
| is created here ...", |
| B = borrow_desc |
| ), |
| ); |
| let message = format!( |
| "... and the {B}borrow might be used here, \ |
| when that temporary is dropped \ |
| and runs the {DTOR} for {TYPE}", |
| B = borrow_desc, |
| TYPE = type_desc, |
| DTOR = dtor_desc |
| ); |
| err.span_label(body.source_info(drop_loc).span, message); |
| |
| if let Some(info) = &local_decl.is_block_tail { |
| // FIXME: use span_suggestion instead, highlighting the |
| // whole block tail expression. |
| let msg = if info.tail_result_is_ignored { |
| "The temporary is part of an expression at the end of a block. \ |
| Consider adding semicolon after the expression so its temporaries \ |
| are dropped sooner, before the local variables declared by the \ |
| block are dropped." |
| } else { |
| "The temporary is part of an expression at the end of a block. \ |
| Consider forcing this temporary to be dropped sooner, before \ |
| the block's local variables are dropped. \ |
| For example, you could save the expression's value in a new \ |
| local variable `x` and then make `x` be the expression \ |
| at the end of the block." |
| }; |
| |
| err.note(msg); |
| } |
| } |
| } |
| } |
| BorrowExplanation::MustBeValidFor { |
| category, |
| span, |
| ref region_name, |
| ref opt_place_desc, |
| from_closure: _, |
| } => { |
| region_name.highlight_region_name(err); |
| |
| if let Some(desc) = opt_place_desc { |
| err.span_label( |
| span, |
| format!( |
| "{}requires that `{}` is borrowed for `{}`", |
| category.description(), |
| desc, |
| region_name, |
| ), |
| ); |
| } else { |
| err.span_label( |
| span, |
| format!( |
| "{}requires that {}borrow lasts for `{}`", |
| category.description(), |
| borrow_desc, |
| region_name, |
| ), |
| ); |
| }; |
| } |
| _ => {} |
| } |
| } |
| } |
| |
| impl<'cx, 'tcx> MirBorrowckCtxt<'cx, 'tcx> { |
| /// Returns structured explanation for *why* the borrow contains the |
| /// point from `location`. This is key for the "3-point errors" |
| /// [described in the NLL RFC][d]. |
| /// |
| /// # Parameters |
| /// |
| /// - `borrow`: the borrow in question |
| /// - `location`: where the borrow occurs |
| /// - `kind_place`: if Some, this describes the statement that triggered the error. |
| /// - first half is the kind of write, if any, being performed |
| /// - second half is the place being accessed |
| /// |
| /// [d]: https://rust-lang.github.io/rfcs/2094-nll.html#leveraging-intuition-framing-errors-in-terms-of-points |
| pub(in crate::borrow_check) fn explain_why_borrow_contains_point( |
| &self, |
| location: Location, |
| borrow: &BorrowData<'tcx>, |
| kind_place: Option<(WriteKind, &Place<'tcx>)>, |
| ) -> BorrowExplanation { |
| debug!( |
| "explain_why_borrow_contains_point(location={:?}, borrow={:?}, kind_place={:?})", |
| location, borrow, kind_place |
| ); |
| |
| let regioncx = &self.nonlexical_regioncx; |
| let body = self.body; |
| let tcx = self.infcx.tcx; |
| |
| let borrow_region_vid = borrow.region; |
| debug!( |
| "explain_why_borrow_contains_point: borrow_region_vid={:?}", |
| borrow_region_vid |
| ); |
| |
| let region_sub = regioncx.find_sub_region_live_at(borrow_region_vid, location); |
| debug!( |
| "explain_why_borrow_contains_point: region_sub={:?}", |
| region_sub |
| ); |
| |
| match find_use::find(body, regioncx, tcx, region_sub, location) { |
| Some(Cause::LiveVar(local, location)) => { |
| let span = body.source_info(location).span; |
| let spans = self |
| .move_spans(Place::from(local).as_ref(), location) |
| .or_else(|| self.borrow_spans(span, location)); |
| |
| let borrow_location = location; |
| if self.is_use_in_later_iteration_of_loop(borrow_location, location) { |
| let later_use = self.later_use_kind(borrow, spans, location); |
| BorrowExplanation::UsedLaterInLoop(later_use.0, later_use.1) |
| } else { |
| // Check if the location represents a `FakeRead`, and adapt the error |
| // message to the `FakeReadCause` it is from: in particular, |
| // the ones inserted in optimized `let var = <expr>` patterns. |
| let later_use = self.later_use_kind(borrow, spans, location); |
| BorrowExplanation::UsedLater(later_use.0, later_use.1) |
| } |
| } |
| |
| Some(Cause::DropVar(local, location)) => { |
| let mut should_note_order = false; |
| if body.local_decls[local].name.is_some() { |
| if let Some((WriteKind::StorageDeadOrDrop, place)) = kind_place { |
| if let Place { |
| base: PlaceBase::Local(borrowed_local), |
| projection: box [], |
| } = place { |
| if body.local_decls[*borrowed_local].name.is_some() |
| && local != *borrowed_local |
| { |
| should_note_order = true; |
| } |
| } |
| } |
| } |
| |
| BorrowExplanation::UsedLaterWhenDropped { |
| drop_loc: location, |
| dropped_local: local, |
| should_note_order, |
| } |
| } |
| |
| None => { |
| if let Some(region) = regioncx.to_error_region_vid(borrow_region_vid) { |
| let (category, from_closure, span, region_name) = |
| self.nonlexical_regioncx.free_region_constraint_info( |
| self.body, |
| &self.upvars, |
| self.mir_def_id, |
| self.infcx, |
| borrow_region_vid, |
| region, |
| ); |
| if let Some(region_name) = region_name { |
| let opt_place_desc = |
| self.describe_place(borrow.borrowed_place.as_ref()); |
| BorrowExplanation::MustBeValidFor { |
| category, |
| from_closure, |
| span, |
| region_name, |
| opt_place_desc, |
| } |
| } else { |
| debug!("explain_why_borrow_contains_point: \ |
| Could not generate a region name"); |
| BorrowExplanation::Unexplained |
| } |
| } else { |
| debug!("explain_why_borrow_contains_point: \ |
| Could not generate an error region vid"); |
| BorrowExplanation::Unexplained |
| } |
| } |
| } |
| } |
| |
| /// true if `borrow_location` can reach `use_location` by going through a loop and |
| /// `use_location` is also inside of that loop |
| fn is_use_in_later_iteration_of_loop( |
| &self, |
| borrow_location: Location, |
| use_location: Location, |
| ) -> bool { |
| let back_edge = self.reach_through_backedge(borrow_location, use_location); |
| back_edge.map_or(false, |back_edge| { |
| self.can_reach_head_of_loop(use_location, back_edge) |
| }) |
| } |
| |
| /// Returns the outmost back edge if `from` location can reach `to` location passing through |
| /// that back edge |
| fn reach_through_backedge(&self, from: Location, to: Location) -> Option<Location> { |
| let mut visited_locations = FxHashSet::default(); |
| let mut pending_locations = VecDeque::new(); |
| visited_locations.insert(from); |
| pending_locations.push_back(from); |
| debug!("reach_through_backedge: from={:?} to={:?}", from, to,); |
| |
| let mut outmost_back_edge = None; |
| while let Some(location) = pending_locations.pop_front() { |
| debug!( |
| "reach_through_backedge: location={:?} outmost_back_edge={:?} |
| pending_locations={:?} visited_locations={:?}", |
| location, outmost_back_edge, pending_locations, visited_locations |
| ); |
| |
| if location == to && outmost_back_edge.is_some() { |
| // We've managed to reach the use location |
| debug!("reach_through_backedge: found!"); |
| return outmost_back_edge; |
| } |
| |
| let block = &self.body.basic_blocks()[location.block]; |
| |
| if location.statement_index < block.statements.len() { |
| let successor = location.successor_within_block(); |
| if visited_locations.insert(successor) { |
| pending_locations.push_back(successor); |
| } |
| } else { |
| pending_locations.extend( |
| block |
| .terminator() |
| .successors() |
| .map(|bb| Location { |
| statement_index: 0, |
| block: *bb, |
| }) |
| .filter(|s| visited_locations.insert(*s)) |
| .map(|s| { |
| if self.is_back_edge(location, s) { |
| match outmost_back_edge { |
| None => { |
| outmost_back_edge = Some(location); |
| } |
| |
| Some(back_edge) |
| if location.dominates(back_edge, &self.dominators) => |
| { |
| outmost_back_edge = Some(location); |
| } |
| |
| Some(_) => {} |
| } |
| } |
| |
| s |
| }), |
| ); |
| } |
| } |
| |
| None |
| } |
| |
| /// true if `from` location can reach `loop_head` location and `loop_head` dominates all the |
| /// intermediate nodes |
| fn can_reach_head_of_loop(&self, from: Location, loop_head: Location) -> bool { |
| self.find_loop_head_dfs(from, loop_head, &mut FxHashSet::default()) |
| } |
| |
| fn find_loop_head_dfs( |
| &self, |
| from: Location, |
| loop_head: Location, |
| visited_locations: &mut FxHashSet<Location>, |
| ) -> bool { |
| visited_locations.insert(from); |
| |
| if from == loop_head { |
| return true; |
| } |
| |
| if loop_head.dominates(from, &self.dominators) { |
| let block = &self.body.basic_blocks()[from.block]; |
| |
| if from.statement_index < block.statements.len() { |
| let successor = from.successor_within_block(); |
| |
| if !visited_locations.contains(&successor) |
| && self.find_loop_head_dfs(successor, loop_head, visited_locations) |
| { |
| return true; |
| } |
| } else { |
| for bb in block.terminator().successors() { |
| let successor = Location { |
| statement_index: 0, |
| block: *bb, |
| }; |
| |
| if !visited_locations.contains(&successor) |
| && self.find_loop_head_dfs(successor, loop_head, visited_locations) |
| { |
| return true; |
| } |
| } |
| } |
| } |
| |
| false |
| } |
| |
| /// True if an edge `source -> target` is a backedge -- in other words, if the target |
| /// dominates the source. |
| fn is_back_edge(&self, source: Location, target: Location) -> bool { |
| target.dominates(source, &self.body.dominators()) |
| } |
| |
| /// Determine how the borrow was later used. |
| fn later_use_kind( |
| &self, |
| borrow: &BorrowData<'tcx>, |
| use_spans: UseSpans, |
| location: Location, |
| ) -> (LaterUseKind, Span) { |
| match use_spans { |
| UseSpans::ClosureUse { var_span, .. } => { |
| // Used in a closure. |
| (LaterUseKind::ClosureCapture, var_span) |
| } |
| UseSpans::OtherUse(span) => { |
| let block = &self.body.basic_blocks()[location.block]; |
| |
| let kind = if let Some(&Statement { |
| kind: StatementKind::FakeRead(FakeReadCause::ForLet, _), |
| .. |
| }) = block.statements.get(location.statement_index) |
| { |
| LaterUseKind::FakeLetRead |
| } else if self.was_captured_by_trait_object(borrow) { |
| LaterUseKind::TraitCapture |
| } else if location.statement_index == block.statements.len() { |
| if let TerminatorKind::Call { |
| ref func, |
| from_hir_call: true, |
| .. |
| } = block.terminator().kind |
| { |
| // Just point to the function, to reduce the chance of overlapping spans. |
| let function_span = match func { |
| Operand::Constant(c) => c.span, |
| Operand::Copy(Place { |
| base: PlaceBase::Local(l), |
| projection: box [], |
| }) | |
| Operand::Move(Place { |
| base: PlaceBase::Local(l), |
| projection: box [], |
| }) => { |
| let local_decl = &self.body.local_decls[*l]; |
| if local_decl.name.is_none() { |
| local_decl.source_info.span |
| } else { |
| span |
| } |
| } |
| _ => span, |
| }; |
| return (LaterUseKind::Call, function_span); |
| } else { |
| LaterUseKind::Other |
| } |
| } else { |
| LaterUseKind::Other |
| }; |
| |
| (kind, span) |
| } |
| } |
| } |
| |
| /// Checks if a borrowed value was captured by a trait object. We do this by |
| /// looking forward in the MIR from the reserve location and checking if we see |
| /// a unsized cast to a trait object on our data. |
| fn was_captured_by_trait_object(&self, borrow: &BorrowData<'tcx>) -> bool { |
| // Start at the reserve location, find the place that we want to see cast to a trait object. |
| let location = borrow.reserve_location; |
| let block = &self.body[location.block]; |
| let stmt = block.statements.get(location.statement_index); |
| debug!( |
| "was_captured_by_trait_object: location={:?} stmt={:?}", |
| location, stmt |
| ); |
| |
| // We make a `queue` vector that has the locations we want to visit. As of writing, this |
| // will only ever have one item at any given time, but by using a vector, we can pop from |
| // it which simplifies the termination logic. |
| let mut queue = vec![location]; |
| let mut target = if let Some(&Statement { |
| kind: StatementKind::Assign(box(Place { |
| base: PlaceBase::Local(local), |
| projection: box [], |
| }, _)), |
| .. |
| }) = stmt |
| { |
| local |
| } else { |
| return false; |
| }; |
| |
| debug!( |
| "was_captured_by_trait: target={:?} queue={:?}", |
| target, queue |
| ); |
| while let Some(current_location) = queue.pop() { |
| debug!("was_captured_by_trait: target={:?}", target); |
| let block = &self.body[current_location.block]; |
| // We need to check the current location to find out if it is a terminator. |
| let is_terminator = current_location.statement_index == block.statements.len(); |
| if !is_terminator { |
| let stmt = &block.statements[current_location.statement_index]; |
| debug!("was_captured_by_trait_object: stmt={:?}", stmt); |
| |
| // The only kind of statement that we care about is assignments... |
| if let StatementKind::Assign(box(place, rvalue)) = &stmt.kind { |
| let into = match place.local_or_deref_local() { |
| Some(into) => into, |
| None => { |
| // Continue at the next location. |
| queue.push(current_location.successor_within_block()); |
| continue; |
| } |
| }; |
| |
| match rvalue { |
| // If we see a use, we should check whether it is our data, and if so |
| // update the place that we're looking for to that new place. |
| Rvalue::Use(operand) => match operand { |
| Operand::Copy(Place { |
| base: PlaceBase::Local(from), |
| projection: box [], |
| }) |
| | Operand::Move(Place { |
| base: PlaceBase::Local(from), |
| projection: box [], |
| }) |
| if *from == target => |
| { |
| target = into; |
| } |
| _ => {} |
| }, |
| // If we see a unsized cast, then if it is our data we should check |
| // whether it is being cast to a trait object. |
| Rvalue::Cast( |
| CastKind::Pointer(PointerCast::Unsize), operand, ty |
| ) => match operand { |
| Operand::Copy(Place { |
| base: PlaceBase::Local(from), |
| projection: box [], |
| }) |
| | Operand::Move(Place { |
| base: PlaceBase::Local(from), |
| projection: box [], |
| }) |
| if *from == target => |
| { |
| debug!("was_captured_by_trait_object: ty={:?}", ty); |
| // Check the type for a trait object. |
| return match ty.sty { |
| // `&dyn Trait` |
| ty::Ref(_, ty, _) if ty.is_trait() => true, |
| // `Box<dyn Trait>` |
| _ if ty.is_box() && ty.boxed_ty().is_trait() => true, |
| // `dyn Trait` |
| _ if ty.is_trait() => true, |
| // Anything else. |
| _ => false, |
| }; |
| } |
| _ => return false, |
| }, |
| _ => {} |
| } |
| } |
| |
| // Continue at the next location. |
| queue.push(current_location.successor_within_block()); |
| } else { |
| // The only thing we need to do for terminators is progress to the next block. |
| let terminator = block.terminator(); |
| debug!("was_captured_by_trait_object: terminator={:?}", terminator); |
| |
| if let TerminatorKind::Call { |
| destination: Some((Place { |
| base: PlaceBase::Local(dest), |
| projection: box [], |
| }, block)), |
| args, |
| .. |
| } = &terminator.kind |
| { |
| debug!( |
| "was_captured_by_trait_object: target={:?} dest={:?} args={:?}", |
| target, dest, args |
| ); |
| // Check if one of the arguments to this function is the target place. |
| let found_target = args.iter().any(|arg| { |
| if let Operand::Move(Place { |
| base: PlaceBase::Local(potential), |
| projection: box [], |
| }) = arg { |
| *potential == target |
| } else { |
| false |
| } |
| }); |
| |
| // If it is, follow this to the next block and update the target. |
| if found_target { |
| target = *dest; |
| queue.push(block.start_location()); |
| } |
| } |
| } |
| |
| debug!("was_captured_by_trait: queue={:?}", queue); |
| } |
| |
| // We didn't find anything and ran out of locations to check. |
| false |
| } |
| } |