1+ use std:: collections:: VecDeque ;
2+
13use crate :: borrow_check:: borrow_set:: BorrowData ;
24use crate :: borrow_check:: error_reporting:: UseSpans ;
35use crate :: borrow_check:: nll:: ConstraintDescription ;
@@ -9,6 +11,7 @@ use rustc::mir::{
911 Place , Projection , ProjectionElem , Rvalue , Statement , StatementKind ,
1012 TerminatorKind
1113} ;
14+ use rustc_data_structures:: fx:: FxHashSet ;
1215use rustc_errors:: DiagnosticBuilder ;
1316use syntax_pos:: Span ;
1417
@@ -220,7 +223,8 @@ impl<'cx, 'gcx, 'tcx> MirBorrowckCtxt<'cx, 'gcx, 'tcx> {
220223 let spans = self . move_spans ( & Place :: Local ( local) , location)
221224 . or_else ( || self . borrow_spans ( span, location) ) ;
222225
223- if self . is_borrow_location_in_loop ( context. loc ) {
226+ let borrow_location = context. loc ;
227+ if self . is_use_in_later_iteration_of_loop ( borrow_location, location) {
224228 let later_use = self . later_use_kind ( borrow, spans, location) ;
225229 BorrowExplanation :: UsedLaterInLoop ( later_use. 0 , later_use. 1 )
226230 } else {
@@ -285,76 +289,139 @@ impl<'cx, 'gcx, 'tcx> MirBorrowckCtxt<'cx, 'gcx, 'tcx> {
285289 }
286290 }
287291
288- /// Checks if a borrow location is within a loop.
289- fn is_borrow_location_in_loop (
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 (
290295 & self ,
291296 borrow_location : Location ,
297+ use_location : Location ,
292298 ) -> bool {
293- let mut visited_locations = Vec :: new ( ) ;
294- let mut pending_locations = vec ! [ borrow_location ] ;
295- debug ! ( "is_in_loop: borrow_location={:?}" , borrow_location) ;
296-
297- while let Some ( location) = pending_locations. pop ( ) {
298- debug ! ( "is_in_loop: location={:?} pending_locations={:?} visited_locations={:?}" ,
299- location, pending_locations, visited_locations) ;
300- if location == borrow_location && visited_locations. contains ( & borrow_location) {
301- // We've managed to return to where we started (and this isn't the start of the
302- // search).
303- debug ! ( "is_in_loop: found!" ) ;
304- return true ;
305- }
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+ }
306304
307- // Skip locations we've been.
308- if visited_locations. contains ( & location) { continue ; }
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+ }
309327
310328 let block = & self . mir . basic_blocks ( ) [ location. block ] ;
311- if location. statement_index == block. statements . len ( ) {
312- // Add start location of the next blocks to pending locations.
313- match block. terminator ( ) . kind {
314- TerminatorKind :: Goto { target } => {
315- pending_locations. push ( target. start_location ( ) ) ;
316- } ,
317- TerminatorKind :: SwitchInt { ref targets, .. } => {
318- pending_locations. extend (
319- targets. into_iter ( ) . map ( |target| target. start_location ( ) ) ) ;
320- } ,
321- TerminatorKind :: Drop { target, unwind, .. } |
322- TerminatorKind :: DropAndReplace { target, unwind, .. } |
323- TerminatorKind :: Assert { target, cleanup : unwind, .. } |
324- TerminatorKind :: Yield { resume : target, drop : unwind, .. } |
325- TerminatorKind :: FalseUnwind { real_target : target, unwind, .. } => {
326- pending_locations. push ( target. start_location ( ) ) ;
327- if let Some ( unwind) = unwind {
328- pending_locations. push ( unwind. start_location ( ) ) ;
329- }
330- } ,
331- TerminatorKind :: Call { ref destination, cleanup, .. } => {
332- if let Some ( ( _, destination) ) = destination {
333- pending_locations. push ( destination. start_location ( ) ) ;
334- }
335- if let Some ( cleanup) = cleanup {
336- pending_locations. push ( cleanup. start_location ( ) ) ;
337- }
338- } ,
339- TerminatorKind :: FalseEdges { real_target, ref imaginary_targets, .. } => {
340- pending_locations. push ( real_target. start_location ( ) ) ;
341- pending_locations. extend (
342- imaginary_targets. into_iter ( ) . map ( |target| target. start_location ( ) ) ) ;
343- } ,
344- _ => { } ,
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) ;
345334 }
346335 } else {
347- // Add the next statement to pending locations.
348- pending_locations. push ( location. successor_within_block ( ) ) ;
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+ ) ;
349365 }
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+ }
350376
351- // Keep track of where we have visited.
352- visited_locations. push ( location) ;
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+ }
353414 }
354415
355416 false
356417 }
357418
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+
358425 /// Determine how the borrow was later used.
359426 fn later_use_kind (
360427 & self ,
0 commit comments