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