]> git.lizzy.rs Git - rust.git/blob - src/librustc_mir/borrow_check/nll/explain_borrow/mod.rs
Fix erroneous loop diagnostic in nll
[rust.git] / src / librustc_mir / borrow_check / nll / explain_borrow / mod.rs
1 use std::collections::VecDeque;
2
3 use crate::borrow_check::borrow_set::BorrowData;
4 use crate::borrow_check::error_reporting::UseSpans;
5 use crate::borrow_check::nll::ConstraintDescription;
6 use crate::borrow_check::nll::region_infer::{Cause, RegionName};
7 use crate::borrow_check::{Context, MirBorrowckCtxt, WriteKind};
8 use rustc::ty::{self, TyCtxt};
9 use rustc::mir::{
10     CastKind, ConstraintCategory, FakeReadCause, Local, Location, Mir, Operand,
11     Place, Projection, ProjectionElem, Rvalue, Statement, StatementKind,
12     TerminatorKind
13 };
14 use rustc_data_structures::fx::FxHashSet;
15 use rustc_errors::DiagnosticBuilder;
16 use syntax_pos::Span;
17
18 mod find_use;
19
20 pub(in crate::borrow_check) enum BorrowExplanation {
21     UsedLater(LaterUseKind, Span),
22     UsedLaterInLoop(LaterUseKind, Span),
23     UsedLaterWhenDropped {
24         drop_loc: Location,
25         dropped_local: Local,
26         should_note_order: bool,
27     },
28     MustBeValidFor {
29         category: ConstraintCategory,
30         from_closure: bool,
31         span: Span,
32         region_name: RegionName,
33         opt_place_desc: Option<String>,
34     },
35     Unexplained,
36 }
37
38 #[derive(Clone, Copy)]
39 pub(in crate::borrow_check) enum LaterUseKind {
40     TraitCapture,
41     ClosureCapture,
42     Call,
43     FakeLetRead,
44     Other,
45 }
46
47 impl BorrowExplanation {
48     pub(in crate::borrow_check) fn is_explained(&self) -> bool {
49         match self {
50             BorrowExplanation::Unexplained => false,
51             _ => true,
52         }
53     }
54     pub(in crate::borrow_check) fn add_explanation_to_diagnostic<'cx, 'gcx, 'tcx>(
55         &self,
56         tcx: TyCtxt<'cx, 'gcx, 'tcx>,
57         mir: &Mir<'tcx>,
58         err: &mut DiagnosticBuilder<'_>,
59         borrow_desc: &str,
60     ) {
61         match *self {
62             BorrowExplanation::UsedLater(later_use_kind, var_or_use_span) => {
63                 let message = match later_use_kind {
64                     LaterUseKind::TraitCapture => "borrow later captured here by trait object",
65                     LaterUseKind::ClosureCapture => "borrow later captured here by closure",
66                     LaterUseKind::Call =>  "borrow later used by call",
67                     LaterUseKind::FakeLetRead => "borrow later stored here",
68                     LaterUseKind::Other => "borrow later used here",
69                 };
70                 err.span_label(var_or_use_span, format!("{}{}", borrow_desc, message));
71             },
72             BorrowExplanation::UsedLaterInLoop(later_use_kind, var_or_use_span) => {
73                 let message = match later_use_kind {
74                     LaterUseKind::TraitCapture =>
75                         "borrow captured here by trait object, in later iteration of loop",
76                     LaterUseKind::ClosureCapture =>
77                         "borrow captured here by closure, in later iteration of loop",
78                     LaterUseKind::Call =>  "borrow used by call, in later iteration of loop",
79                     LaterUseKind::FakeLetRead => "borrow later stored here",
80                     LaterUseKind::Other => "borrow used here, in later iteration of loop",
81                 };
82                 err.span_label(var_or_use_span, format!("{}{}", borrow_desc, message));
83             },
84             BorrowExplanation::UsedLaterWhenDropped { drop_loc, dropped_local,
85                                                       should_note_order } =>
86             {
87                 let local_decl = &mir.local_decls[dropped_local];
88                 let (dtor_desc, type_desc) = match local_decl.ty.sty {
89                     // If type is an ADT that implements Drop, then
90                     // simplify output by reporting just the ADT name.
91                     ty::Adt(adt, _substs) if adt.has_dtor(tcx) && !adt.is_box() =>
92                         ("`Drop` code", format!("type `{}`", tcx.item_path_str(adt.did))),
93
94                     // Otherwise, just report the whole type (and use
95                     // the intentionally fuzzy phrase "destructor")
96                     ty::Closure(..) =>
97                         ("destructor", "closure".to_owned()),
98                     ty::Generator(..) =>
99                         ("destructor", "generator".to_owned()),
100
101                     _ => ("destructor", format!("type `{}`", local_decl.ty)),
102                 };
103
104                 match local_decl.name {
105                     Some(local_name) => {
106                         let message =
107                             format!("{B}borrow might be used here, when `{LOC}` is dropped \
108                                      and runs the {DTOR} for {TYPE}",
109                                     B=borrow_desc, LOC=local_name, TYPE=type_desc, DTOR=dtor_desc);
110                         err.span_label(mir.source_info(drop_loc).span, message);
111
112                         if should_note_order {
113                             err.note(
114                                 "values in a scope are dropped \
115                                  in the opposite order they are defined",
116                             );
117                         }
118                     }
119                     None => {
120                         err.span_label(local_decl.source_info.span,
121                                        format!("a temporary with access to the {B}borrow \
122                                                 is created here ...",
123                                                B=borrow_desc));
124                         let message =
125                             format!("... and the {B}borrow might be used here, \
126                                      when that temporary is dropped \
127                                      and runs the {DTOR} for {TYPE}",
128                                     B=borrow_desc, TYPE=type_desc, DTOR=dtor_desc);
129                         err.span_label(mir.source_info(drop_loc).span, message);
130
131                         if let Some(info) = &local_decl.is_block_tail {
132                             // FIXME: use span_suggestion instead, highlighting the
133                             // whole block tail expression.
134                             let msg = if info.tail_result_is_ignored {
135                                 "The temporary is part of an expression at the end of a block. \
136                                  Consider adding semicolon after the expression so its temporaries \
137                                  are dropped sooner, before the local variables declared by the \
138                                  block are dropped."
139                             } else {
140                                 "The temporary is part of an expression at the end of a block. \
141                                  Consider forcing this temporary to be dropped sooner, before \
142                                  the block's local variables are dropped. \
143                                  For example, you could save the expression's value in a new \
144                                  local variable `x` and then make `x` be the expression \
145                                  at the end of the block."
146                             };
147
148                             err.note(msg);
149                         }
150                     }
151                 }
152             },
153             BorrowExplanation::MustBeValidFor {
154                 category,
155                 span,
156                 ref region_name,
157                 ref opt_place_desc,
158                 from_closure: _,
159             } => {
160                 region_name.highlight_region_name(err);
161
162                 if let Some(desc) = opt_place_desc {
163                     err.span_label(span, format!(
164                         "{}requires that `{}` is borrowed for `{}`",
165                         category.description(), desc, region_name,
166                     ));
167                 } else {
168                     err.span_label(span, format!(
169                         "{}requires that {}borrow lasts for `{}`",
170                         category.description(), borrow_desc, region_name,
171                     ));
172                 };
173             },
174             _ => {},
175         }
176     }
177 }
178
179 impl<'cx, 'gcx, 'tcx> MirBorrowckCtxt<'cx, 'gcx, 'tcx> {
180     /// Returns structured explanation for *why* the borrow contains the
181     /// point from `context`. This is key for the "3-point errors"
182     /// [described in the NLL RFC][d].
183     ///
184     /// # Parameters
185     ///
186     /// - `borrow`: the borrow in question
187     /// - `context`: where the borrow occurs
188     /// - `kind_place`: if Some, this describes the statement that triggered the error.
189     ///   - first half is the kind of write, if any, being performed
190     ///   - second half is the place being accessed
191     ///
192     /// [d]: https://rust-lang.github.io/rfcs/2094-nll.html#leveraging-intuition-framing-errors-in-terms-of-points
193     pub(in crate::borrow_check) fn explain_why_borrow_contains_point(
194         &self,
195         context: Context,
196         borrow: &BorrowData<'tcx>,
197         kind_place: Option<(WriteKind, &Place<'tcx>)>,
198     ) -> BorrowExplanation {
199         debug!(
200             "explain_why_borrow_contains_point(context={:?}, borrow={:?}, kind_place={:?})",
201             context, borrow, kind_place
202         );
203
204         let regioncx = &self.nonlexical_regioncx;
205         let mir = self.mir;
206         let tcx = self.infcx.tcx;
207
208         let borrow_region_vid = borrow.region;
209         debug!(
210             "explain_why_borrow_contains_point: borrow_region_vid={:?}",
211             borrow_region_vid
212         );
213
214         let region_sub = regioncx.find_sub_region_live_at(borrow_region_vid, context.loc);
215         debug!(
216             "explain_why_borrow_contains_point: region_sub={:?}",
217             region_sub
218         );
219
220          match find_use::find(mir, regioncx, tcx, region_sub, context.loc) {
221             Some(Cause::LiveVar(local, location)) => {
222                 let span = mir.source_info(location).span;
223                 let spans = self.move_spans(&Place::Local(local), location)
224                     .or_else(|| self.borrow_spans(span, location));
225
226                 let borrow_location = context.loc;
227                 if self.is_use_in_later_iteration_of_loop(borrow_location, location) {
228                     let later_use = self.later_use_kind(borrow, spans, location);
229                     BorrowExplanation::UsedLaterInLoop(later_use.0, later_use.1)
230                 } else {
231                     // Check if the location represents a `FakeRead`, and adapt the error
232                     // message to the `FakeReadCause` it is from: in particular,
233                     // the ones inserted in optimized `let var = <expr>` patterns.
234                     let later_use = self.later_use_kind(borrow, spans, location);
235                     BorrowExplanation::UsedLater(later_use.0, later_use.1)
236                 }
237             }
238
239              Some(Cause::DropVar(local, location)) => {
240                  let mut should_note_order = false;
241                  if mir.local_decls[local].name.is_some() {
242                      if let Some((WriteKind::StorageDeadOrDrop, place)) = kind_place {
243                          if let Place::Local(borrowed_local) = place {
244                              let dropped_local_scope = mir.local_decls[local].visibility_scope;
245                              let borrowed_local_scope =
246                                  mir.local_decls[*borrowed_local].visibility_scope;
247
248                              if mir.is_sub_scope(borrowed_local_scope, dropped_local_scope)
249                                  && local != *borrowed_local
250                              {
251                                  should_note_order = true;
252                              }
253                          }
254                      }
255                  }
256
257                  BorrowExplanation::UsedLaterWhenDropped {
258                      drop_loc: location,
259                      dropped_local: local,
260                      should_note_order,
261                  }
262             }
263
264             None => if let Some(region) = regioncx.to_error_region_vid(borrow_region_vid) {
265                 let (category, from_closure, span, region_name) = self
266                     .nonlexical_regioncx
267                     .free_region_constraint_info(
268                         self.mir,
269                         self.mir_def_id,
270                         self.infcx,
271                         borrow_region_vid,
272                         region,
273                     );
274                 if let Some(region_name) = region_name {
275                     let opt_place_desc = self.describe_place(&borrow.borrowed_place);
276                     BorrowExplanation::MustBeValidFor {
277                         category,
278                         from_closure,
279                         span,
280                         region_name,
281                         opt_place_desc,
282                     }
283                 } else {
284                     BorrowExplanation::Unexplained
285                 }
286             } else {
287                 BorrowExplanation::Unexplained
288             }
289         }
290     }
291
292     /// true if `borrow_location` can reach `use_location` by going through a loop and
293     /// `use_location` is also inside of that loop
294     fn is_use_in_later_iteration_of_loop(
295         &self,
296         borrow_location: Location,
297         use_location: Location,
298     ) -> bool {
299         let back_edge = self.reach_through_backedge(borrow_location, use_location);
300         back_edge.map_or(false, |back_edge| {
301             self.can_reach_head_of_loop(use_location, back_edge)
302         })
303     }
304
305     /// Returns the outmost back edge if `from` location can reach `to` location passing through
306     /// that back edge
307     fn reach_through_backedge(&self, from: Location, to: Location) -> Option<Location> {
308         let mut visited_locations = FxHashSet::default();
309         let mut pending_locations = VecDeque::new();
310         visited_locations.insert(from);
311         pending_locations.push_back(from);
312         debug!("reach_through_backedge: from={:?} to={:?}", from, to,);
313
314         let mut outmost_back_edge = None;
315         while let Some(location) = pending_locations.pop_front() {
316             debug!(
317                 "reach_through_backedge: location={:?} outmost_back_edge={:?}
318                    pending_locations={:?} visited_locations={:?}",
319                 location, outmost_back_edge, pending_locations, visited_locations
320             );
321
322             if location == to && outmost_back_edge.is_some() {
323                 // We've managed to reach the use location
324                 debug!("reach_through_backedge: found!");
325                 return outmost_back_edge;
326             }
327
328             let block = &self.mir.basic_blocks()[location.block];
329
330             if location.statement_index < block.statements.len() {
331                 let successor = location.successor_within_block();
332                 if visited_locations.insert(successor) {
333                     pending_locations.push_back(successor);
334                 }
335             } else {
336                 pending_locations.extend(
337                     block
338                         .terminator()
339                         .successors()
340                         .map(|bb| Location {
341                             statement_index: 0,
342                             block: *bb,
343                         })
344                         .filter(|s| visited_locations.insert(*s))
345                         .map(|s| {
346                             if self.is_back_edge(location, s) {
347                                 match outmost_back_edge {
348                                     None => {
349                                         outmost_back_edge = Some(location);
350                                     }
351
352                                     Some(back_edge)
353                                         if location.dominates(back_edge, &self.dominators) =>
354                                     {
355                                         outmost_back_edge = Some(location);
356                                     }
357
358                                     Some(_) => {}
359                                 }
360                             }
361
362                             s
363                         }),
364                 );
365             }
366         }
367
368         None
369     }
370
371     /// true if `from` location can reach `loop_head` location and `loop_head` dominates all the
372     /// intermediate nodes
373     fn can_reach_head_of_loop(&self, from: Location, loop_head: Location) -> bool {
374         self.find_loop_head_dfs(from, loop_head, &mut FxHashSet::default())
375     }
376
377     fn find_loop_head_dfs(
378         &self,
379         from: Location,
380         loop_head: Location,
381         visited_locations: &mut FxHashSet<Location>,
382     ) -> bool {
383         visited_locations.insert(from);
384
385         if from == loop_head {
386             return true;
387         }
388
389         if loop_head.dominates(from, &self.dominators) {
390             let block = &self.mir.basic_blocks()[from.block];
391
392             if from.statement_index < block.statements.len() {
393                 let successor = from.successor_within_block();
394
395                 if !visited_locations.contains(&successor)
396                     && self.find_loop_head_dfs(successor, loop_head, visited_locations)
397                 {
398                     return true;
399                 }
400             } else {
401                 for bb in block.terminator().successors() {
402                     let successor = Location {
403                         statement_index: 0,
404                         block: *bb,
405                     };
406
407                     if !visited_locations.contains(&successor)
408                         && self.find_loop_head_dfs(successor, loop_head, visited_locations)
409                     {
410                         return true;
411                     }
412                 }
413             }
414         }
415
416         false
417     }
418
419     /// True if an edge `source -> target` is a backedge -- in other words, if the target
420     /// dominates the source.
421     fn is_back_edge(&self, source: Location, target: Location) -> bool {
422         target.dominates(source, &self.mir.dominators())
423     }
424
425     /// Determine how the borrow was later used.
426     fn later_use_kind(
427         &self,
428         borrow: &BorrowData<'tcx>,
429         use_spans: UseSpans,
430         location: Location
431     ) -> (LaterUseKind, Span) {
432         match use_spans {
433             UseSpans::ClosureUse { var_span, .. } => {
434                 // Used in a closure.
435                 (LaterUseKind::ClosureCapture, var_span)
436             },
437             UseSpans::OtherUse(span) => {
438                 let block = &self.mir.basic_blocks()[location.block];
439
440                 let kind = if let Some(&Statement {
441                     kind: StatementKind::FakeRead(FakeReadCause::ForLet, _),
442                     ..
443                 }) = block.statements.get(location.statement_index) {
444                     LaterUseKind::FakeLetRead
445                 } else if self.was_captured_by_trait_object(borrow) {
446                     LaterUseKind::TraitCapture
447                 } else if location.statement_index == block.statements.len() {
448                     if let TerminatorKind::Call {
449                         ref func, from_hir_call: true, ..
450                     } = block.terminator().kind {
451                         // Just point to the function, to reduce the chance of overlapping spans.
452                         let function_span = match func {
453                             Operand::Constant(c) => c.span,
454                             Operand::Copy(Place::Local(l)) | Operand::Move(Place::Local(l)) => {
455                                 let local_decl = &self.mir.local_decls[*l];
456                                 if local_decl.name.is_none() {
457                                     local_decl.source_info.span
458                                 } else {
459                                     span
460                                 }
461                             },
462                             _ => span,
463                         };
464                         return (LaterUseKind::Call, function_span);
465                     } else {
466                         LaterUseKind::Other
467                     }
468                 } else {
469                     LaterUseKind::Other
470                 };
471
472                 (kind, span)
473             }
474         }
475     }
476
477     /// Checks if a borrowed value was captured by a trait object. We do this by
478     /// looking forward in the MIR from the reserve location and checking if we see
479     /// a unsized cast to a trait object on our data.
480     fn was_captured_by_trait_object(&self, borrow: &BorrowData<'tcx>) -> bool {
481         // Start at the reserve location, find the place that we want to see cast to a trait object.
482         let location = borrow.reserve_location;
483         let block = &self.mir[location.block];
484         let stmt = block.statements.get(location.statement_index);
485         debug!("was_captured_by_trait_object: location={:?} stmt={:?}", location, stmt);
486
487         // We make a `queue` vector that has the locations we want to visit. As of writing, this
488         // will only ever have one item at any given time, but by using a vector, we can pop from
489         // it which simplifies the termination logic.
490         let mut queue = vec![location];
491         let mut target = if let Some(&Statement {
492             kind: StatementKind::Assign(Place::Local(local), _),
493             ..
494         }) = stmt {
495             local
496         } else {
497             return false;
498         };
499
500         debug!("was_captured_by_trait: target={:?} queue={:?}", target, queue);
501         while let Some(current_location) = queue.pop() {
502             debug!("was_captured_by_trait: target={:?}", target);
503             let block = &self.mir[current_location.block];
504             // We need to check the current location to find out if it is a terminator.
505             let is_terminator = current_location.statement_index == block.statements.len();
506             if !is_terminator {
507                 let stmt = &block.statements[current_location.statement_index];
508                 debug!("was_captured_by_trait_object: stmt={:?}", stmt);
509
510                 // The only kind of statement that we care about is assignments...
511                 if let StatementKind::Assign(
512                     place,
513                     box rvalue,
514                 ) = &stmt.kind {
515                     let into = match place {
516                         Place::Local(into) => into,
517                         Place::Projection(box Projection {
518                             base: Place::Local(into),
519                             elem: ProjectionElem::Deref,
520                         }) => into,
521                         _ =>  {
522                             // Continue at the next location.
523                             queue.push(current_location.successor_within_block());
524                             continue;
525                         },
526                     };
527
528                     match rvalue {
529                         // If we see a use, we should check whether it is our data, and if so
530                         // update the place that we're looking for to that new place.
531                         Rvalue::Use(operand) => match operand {
532                             Operand::Copy(Place::Local(from)) |
533                             Operand::Move(Place::Local(from)) if *from == target => {
534                                 target = *into;
535                             },
536                             _ => {},
537                         },
538                         // If we see a unsized cast, then if it is our data we should check
539                         // whether it is being cast to a trait object.
540                         Rvalue::Cast(CastKind::Unsize, operand, ty) => match operand {
541                             Operand::Copy(Place::Local(from)) |
542                             Operand::Move(Place::Local(from)) if *from == target => {
543                                 debug!("was_captured_by_trait_object: ty={:?}", ty);
544                                 // Check the type for a trait object.
545                                 return match ty.sty {
546                                     // `&dyn Trait`
547                                     ty::TyKind::Ref(_, ty, _) if ty.is_trait() => true,
548                                     // `Box<dyn Trait>`
549                                     _ if ty.is_box() && ty.boxed_ty().is_trait() =>
550                                         true,
551                                     // `dyn Trait`
552                                     _ if ty.is_trait() => true,
553                                     // Anything else.
554                                     _ => false,
555                                 };
556                             },
557                             _ => return false,
558                         },
559                         _ => {},
560                     }
561                 }
562
563                 // Continue at the next location.
564                 queue.push(current_location.successor_within_block());
565             } else {
566                 // The only thing we need to do for terminators is progress to the next block.
567                 let terminator = block.terminator();
568                 debug!("was_captured_by_trait_object: terminator={:?}", terminator);
569
570                 if let TerminatorKind::Call {
571                     destination: Some((Place::Local(dest), block)),
572                     args,
573                     ..
574                 } = &terminator.kind {
575                     debug!(
576                         "was_captured_by_trait_object: target={:?} dest={:?} args={:?}",
577                         target, dest, args
578                     );
579                     // Check if one of the arguments to this function is the target place.
580                     let found_target = args.iter().any(|arg| {
581                         if let Operand::Move(Place::Local(potential)) = arg {
582                             *potential == target
583                         } else {
584                             false
585                         }
586                     });
587
588                     // If it is, follow this to the next block and update the target.
589                     if found_target {
590                         target = *dest;
591                         queue.push(block.start_location());
592                     }
593                 }
594             }
595
596             debug!("was_captured_by_trait: queue={:?}", queue);
597         }
598
599         // We didn't find anything and ran out of locations to check.
600         false
601     }
602 }