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