@@ -14,8 +14,8 @@ use borrow_check::nll::region_infer::Cause;
14
14
use borrow_check:: { Context , MirBorrowckCtxt , WriteKind } ;
15
15
use rustc:: ty:: { self , Region , TyCtxt } ;
16
16
use 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
19
19
} ;
20
20
use rustc_errors:: DiagnosticBuilder ;
21
21
use syntax_pos:: Span ;
@@ -65,7 +65,7 @@ impl<'tcx> BorrowExplanation<'tcx> {
65
65
BorrowExplanation :: UsedLaterInLoop ( later_use_kind, var_or_use_span) => {
66
66
let message = match later_use_kind {
67
67
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" ,
69
69
LaterUseKind :: ClosureCapture =>
70
70
"borrow captured here by closure, in later iteration of loop" ,
71
71
LaterUseKind :: Call => "borrow used by call, in later iteration of loop" ,
@@ -373,20 +373,20 @@ impl<'cx, 'gcx, 'tcx> MirBorrowckCtxt<'cx, 'gcx, 'tcx> {
373
373
}
374
374
}
375
375
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.
377
379
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.
383
381
let location = borrow. reserve_location ;
384
382
let block = & self . mir [ location. block ] ;
385
383
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] ;
390
390
let mut target = if let Some ( & Statement {
391
391
kind : StatementKind :: Assign ( Place :: Local ( local) , _) ,
392
392
..
@@ -396,61 +396,109 @@ impl<'cx, 'gcx, 'tcx> MirBorrowckCtxt<'cx, 'gcx, 'tcx> {
396
396
return false ;
397
397
} ;
398
398
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
+ _ => { } ,
433
459
}
434
460
}
435
461
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
+ _ => { } ,
450
495
}
451
496
}
497
+
498
+ debug ! ( "was_captured_by_trait: queue={:?}" , queue) ;
452
499
}
453
500
501
+ // We didn't find anything and ran out of locations to check.
454
502
false
455
503
}
456
504
}
0 commit comments