@@ -14,8 +14,8 @@ use borrow_check::nll::region_infer::Cause;
1414use borrow_check:: { Context , MirBorrowckCtxt , WriteKind } ;
1515use rustc:: ty:: { self , Region , TyCtxt } ;
1616use rustc:: mir:: {
17- FakeReadCause , Local , Location , Mir , Operand , Place , Rvalue , Statement , StatementKind ,
18- TerminatorKind
17+ CastKind , FakeReadCause , Local , Location , Mir , Operand , Place , Projection , ProjectionElem ,
18+ Rvalue , Statement , StatementKind , TerminatorKind
1919} ;
2020use rustc_errors:: DiagnosticBuilder ;
2121use syntax_pos:: Span ;
@@ -65,7 +65,7 @@ impl<'tcx> BorrowExplanation<'tcx> {
6565 BorrowExplanation :: UsedLaterInLoop ( later_use_kind, var_or_use_span) => {
6666 let message = match later_use_kind {
6767 LaterUseKind :: TraitCapture =>
68- "borrow later captured here by trait object, in later iteration of loop" ,
68+ "borrow captured here by trait object, in later iteration of loop" ,
6969 LaterUseKind :: ClosureCapture =>
7070 "borrow captured here by closure, in later iteration of loop" ,
7171 LaterUseKind :: Call => "borrow used by call, in later iteration of loop" ,
@@ -373,20 +373,20 @@ impl<'cx, 'gcx, 'tcx> MirBorrowckCtxt<'cx, 'gcx, 'tcx> {
373373 }
374374 }
375375
376- /// Check if a borrowed value was captured by a trait object.
376+ /// Check if a borrowed value was captured by a trait object. We do this by
377+ /// looking forward in the MIR from the reserve location and checking if we see
378+ /// a unsized cast to a trait object on our data.
377379 fn was_captured_by_trait_object ( & self , borrow : & BorrowData < ' tcx > ) -> bool {
378- // In order to check if a value was captured by a trait object, we want to look through
379- // statements after the reserve location in the current block. We expect the reserve
380- // location to be a statement assigning to a local. We follow that local in the subsequent
381- // statements, checking for an assignment of our local (or something intermediate that
382- // it was assigned into) that results in a trait object.
380+ // Start at the reserve location, find the place that we want to see cast to a trait object.
383381 let location = borrow. reserve_location ;
384382 let block = & self . mir [ location. block ] ;
385383 let stmt = block. statements . get ( location. statement_index ) ;
386- debug ! (
387- "was_captured_by_trait_object: location={:?} block={:?} stmt={:?}" ,
388- location, block, stmt
389- ) ;
384+ debug ! ( "was_captured_by_trait_object: location={:?} stmt={:?}" , location, stmt) ;
385+
386+ // We make a `queue` vector that has the locations we want to visit. As of writing, this
387+ // will only ever have one item at any given time, but by using a vector, we can pop from
388+ // it which simplifies the termination logic.
389+ let mut queue = vec ! [ location] ;
390390 let mut target = if let Some ( & Statement {
391391 kind : StatementKind :: Assign ( Place :: Local ( local) , _) ,
392392 ..
@@ -396,61 +396,109 @@ impl<'cx, 'gcx, 'tcx> MirBorrowckCtxt<'cx, 'gcx, 'tcx> {
396396 return false ;
397397 } ;
398398
399- debug ! ( "was_captured_by_trait_object: target={:?}" , target) ;
400- for stmt in & block. statements [ location. statement_index + 1 ..] {
401- debug ! ( "was_captured_by_trait_object: stmt={:?}" , stmt) ;
402- // Simple case where our target is assigned into another local, and we start
403- // watching that local instead.
404- if let StatementKind :: Assign (
405- Place :: Local ( into) ,
406- box Rvalue :: Use ( operand) ,
407- ) = & stmt. kind {
408- debug ! ( "was_captured_by_trait_object: target={:?} operand={:?}" , target, operand) ;
409- match operand {
410- Operand :: Copy ( Place :: Local ( from) ) |
411- Operand :: Move ( Place :: Local ( from) ) if * from == target => target = * into,
412- _ => { } ,
413- }
414- }
415- }
416-
417- if let Some ( terminator) = & block. terminator {
418- if let TerminatorKind :: Call {
419- destination : Some ( ( Place :: Local ( dest) , _) ) ,
420- args,
421- ..
422- } = & terminator. kind {
423- debug ! (
424- "was_captured_by_trait_object: target={:?} dest={:?} args={:?}" ,
425- target, dest, args
426- ) ;
427- let mut found_target = false ;
428- for arg in args {
429- if let Operand :: Move ( Place :: Local ( potential) ) = arg {
430- if * potential == target {
431- found_target = true ;
432- }
399+ debug ! ( "was_captured_by_trait: target={:?} queue={:?}" , target, queue) ;
400+ while let Some ( current_location) = queue. pop ( ) {
401+ debug ! ( "was_captured_by_trait: target={:?}" , target) ;
402+ let block = & self . mir [ current_location. block ] ;
403+ // We need to check the current location to find out if it is a terminator.
404+ let is_terminator = current_location. statement_index == block. statements . len ( ) ;
405+ if !is_terminator {
406+ let stmt = & block. statements [ current_location. statement_index ] ;
407+ debug ! ( "was_captured_by_trait_object: stmt={:?}" , stmt) ;
408+
409+ // The only kind of statement that we care about is assignments...
410+ if let StatementKind :: Assign (
411+ place,
412+ box rvalue,
413+ ) = & stmt. kind {
414+ let into = match place {
415+ Place :: Local ( into) => into,
416+ Place :: Projection ( box Projection {
417+ base : Place :: Local ( into) ,
418+ elem : ProjectionElem :: Deref ,
419+ } ) => into,
420+ _ => {
421+ // Continue at the next location.
422+ queue. push ( current_location. successor_within_block ( ) ) ;
423+ continue ;
424+ } ,
425+ } ;
426+
427+ match rvalue {
428+ // If we see a use, we should check whether it is our data, and if so
429+ // update the place that we're looking for to that new place.
430+ Rvalue :: Use ( operand) => match operand {
431+ Operand :: Copy ( Place :: Local ( from) ) |
432+ Operand :: Move ( Place :: Local ( from) ) if * from == target => {
433+ target = * into;
434+ } ,
435+ _ => { } ,
436+ } ,
437+ // If we see a unsized cast, then if it is our data we should check
438+ // whether it is being cast to a trait object.
439+ Rvalue :: Cast ( CastKind :: Unsize , operand, ty) => match operand {
440+ Operand :: Copy ( Place :: Local ( from) ) |
441+ Operand :: Move ( Place :: Local ( from) ) if * from == target => {
442+ debug ! ( "was_captured_by_trait_object: ty={:?}" , ty) ;
443+ // Check the type for a trait object.
444+ match ty. sty {
445+ // `&dyn Trait`
446+ ty:: TyKind :: Ref ( _, ty, _) if ty. is_trait ( ) => return true ,
447+ // `Box<dyn Trait>`
448+ _ if ty. is_box ( ) && ty. boxed_ty ( ) . is_trait ( ) =>
449+ return true ,
450+ // `dyn Trait`
451+ _ if ty. is_trait ( ) => return true ,
452+ // Anything else.
453+ _ => return false ,
454+ }
455+ } ,
456+ _ => return false ,
457+ } ,
458+ _ => { } ,
433459 }
434460 }
435461
436- if found_target {
437- let local_decl_ty = & self . mir . local_decls [ * dest] . ty ;
438- debug ! ( "was_captured_by_trait_object: local_decl_ty={:?}" , local_decl_ty) ;
439- match local_decl_ty. sty {
440- // `&dyn Trait`
441- ty:: TyKind :: Ref ( _, ty, _) if ty. is_trait ( ) => return true ,
442- // `Box<dyn Trait>`
443- _ if local_decl_ty. is_box ( ) && local_decl_ty. boxed_ty ( ) . is_trait ( ) =>
444- return true ,
445- // `dyn Trait`
446- _ if local_decl_ty. is_trait ( ) => return true ,
447- // Anything else.
448- _ => return false ,
449- }
462+ // Continue at the next location.
463+ queue. push ( current_location. successor_within_block ( ) ) ;
464+ } else {
465+ // The only thing we need to do for terminators is progress to the next block.
466+ let terminator = block. terminator ( ) ;
467+ debug ! ( "was_captured_by_trait_object: terminator={:?}" , terminator) ;
468+
469+ match & terminator. kind {
470+ TerminatorKind :: Call {
471+ destination : Some ( ( Place :: Local ( dest) , block) ) ,
472+ args,
473+ ..
474+ } => {
475+ debug ! (
476+ "was_captured_by_trait_object: target={:?} dest={:?} args={:?}" ,
477+ target, dest, args
478+ ) ;
479+ // Check if one of the arguments to this function is the target place.
480+ let found_target = args. iter ( ) . any ( |arg| {
481+ if let Operand :: Move ( Place :: Local ( potential) ) = arg {
482+ * potential == target
483+ } else {
484+ false
485+ }
486+ } ) ;
487+
488+ // If it is, follow this to the next block and update the target.
489+ if found_target {
490+ target = * dest;
491+ queue. push ( block. start_location ( ) ) ;
492+ }
493+ } ,
494+ _ => { } ,
450495 }
451496 }
497+
498+ debug ! ( "was_captured_by_trait: queue={:?}" , queue) ;
452499 }
453500
501+ // We didn't find anything and ran out of locations to check.
454502 false
455503 }
456504}
0 commit comments