1
+ use itertools:: Itertools ;
1
2
use reexport:: * ;
2
3
use rustc:: hir:: * ;
3
4
use rustc:: hir:: def:: Def ;
@@ -15,10 +16,29 @@ use syntax::ast;
15
16
use utils:: sugg;
16
17
17
18
use utils:: { get_enclosing_block, get_parent_expr, higher, in_external_macro, is_integer_literal, is_refutable,
18
- last_path_segment, match_trait_method, match_type, multispan_sugg, snippet, span_help_and_lint , span_lint ,
19
- span_lint_and_sugg, span_lint_and_then} ;
19
+ last_path_segment, match_trait_method, match_type, multispan_sugg, snippet, snippet_opt ,
20
+ span_help_and_lint , span_lint , span_lint_and_sugg, span_lint_and_then} ;
20
21
use utils:: paths;
21
22
23
+ /// **What it does:** Checks for for loops that manually copy items between
24
+ /// slices that could be optimized by having a memcpy.
25
+ ///
26
+ /// **Why is this bad?** It is not as fast as a memcpy.
27
+ ///
28
+ /// **Known problems:** None.
29
+ ///
30
+ /// **Example:**
31
+ /// ```rust
32
+ /// for i in 0..src.len() {
33
+ /// dst[i + 64] = src[i];
34
+ /// }
35
+ /// ```
36
+ declare_lint ! {
37
+ pub MANUAL_MEMCPY ,
38
+ Warn ,
39
+ "manually copying items between slices"
40
+ }
41
+
22
42
/// **What it does:** Checks for looping over the range of `0..len` of some
23
43
/// collection just to get the values by index.
24
44
///
@@ -314,6 +334,7 @@ pub struct Pass;
314
334
impl LintPass for Pass {
315
335
fn get_lints ( & self ) -> LintArray {
316
336
lint_array ! (
337
+ MANUAL_MEMCPY ,
317
338
NEEDLESS_RANGE_LOOP ,
318
339
EXPLICIT_ITER_LOOP ,
319
340
EXPLICIT_INTO_ITER_LOOP ,
@@ -570,6 +591,249 @@ fn check_for_loop<'a, 'tcx>(
570
591
check_for_loop_arg ( cx, pat, arg, expr) ;
571
592
check_for_loop_explicit_counter ( cx, arg, body, expr) ;
572
593
check_for_loop_over_map_kv ( cx, pat, arg, body, expr) ;
594
+ detect_manual_memcpy ( cx, pat, arg, body, expr) ;
595
+ }
596
+
597
+ fn same_var < ' a , ' tcx > ( cx : & LateContext < ' a , ' tcx > , expr : & Expr , var : DefId ) -> bool {
598
+ if_let_chain ! { [
599
+ let ExprPath ( ref qpath) = expr. node,
600
+ let QPath :: Resolved ( None , ref path) = * qpath,
601
+ path. segments. len( ) == 1 ,
602
+ // our variable!
603
+ cx. tables. qpath_def( qpath, expr. hir_id) . def_id( ) == var
604
+ ] , {
605
+ return true ;
606
+ } }
607
+
608
+ false
609
+ }
610
+
611
+ struct Offset {
612
+ value : String ,
613
+ negate : bool ,
614
+ }
615
+
616
+ impl Offset {
617
+ fn negative ( s : String ) -> Self {
618
+ Self {
619
+ value : s,
620
+ negate : true ,
621
+ }
622
+ }
623
+
624
+ fn positive ( s : String ) -> Self {
625
+ Self {
626
+ value : s,
627
+ negate : false ,
628
+ }
629
+ }
630
+ }
631
+
632
+ struct FixedOffsetVar {
633
+ var_name : String ,
634
+ offset : Offset ,
635
+ }
636
+
637
+ fn is_slice_like < ' a , ' tcx > ( cx : & LateContext < ' a , ' tcx > , ty : Ty ) -> bool {
638
+ let is_slice = match ty. sty {
639
+ ty:: TyRef ( _, ref subty) => is_slice_like ( cx, subty. ty ) ,
640
+ ty:: TySlice ( ..) | ty:: TyArray ( ..) => true ,
641
+ _ => false ,
642
+ } ;
643
+
644
+ is_slice || match_type ( cx, ty, & paths:: VEC ) || match_type ( cx, ty, & paths:: VEC_DEQUE )
645
+ }
646
+
647
+ fn get_fixed_offset_var < ' a , ' tcx > ( cx : & LateContext < ' a , ' tcx > , expr : & Expr , var : DefId ) -> Option < FixedOffsetVar > {
648
+ fn extract_offset < ' a , ' tcx > ( cx : & LateContext < ' a , ' tcx > , e : & Expr , var : DefId ) -> Option < String > {
649
+ match e. node {
650
+ ExprLit ( ref l) => match l. node {
651
+ ast:: LitKind :: Int ( x, _ty) => Some ( x. to_string ( ) ) ,
652
+ _ => None ,
653
+ } ,
654
+ ExprPath ( ..) if !same_var ( cx, e, var) => Some ( snippet_opt ( cx, e. span ) . unwrap_or_else ( || "??" . into ( ) ) ) ,
655
+ _ => None ,
656
+ }
657
+ }
658
+
659
+ if let ExprIndex ( ref seqexpr, ref idx) = expr. node {
660
+ let ty = cx. tables . expr_ty ( seqexpr) ;
661
+ if !is_slice_like ( cx, ty) {
662
+ return None ;
663
+ }
664
+
665
+ let offset = match idx. node {
666
+ ExprBinary ( op, ref lhs, ref rhs) => match op. node {
667
+ BinOp_ :: BiAdd => {
668
+ let offset_opt = if same_var ( cx, lhs, var) {
669
+ extract_offset ( cx, rhs, var)
670
+ } else if same_var ( cx, rhs, var) {
671
+ extract_offset ( cx, lhs, var)
672
+ } else {
673
+ None
674
+ } ;
675
+
676
+ offset_opt. map ( Offset :: positive)
677
+ } ,
678
+ BinOp_ :: BiSub if same_var ( cx, lhs, var) => extract_offset ( cx, rhs, var) . map ( Offset :: negative) ,
679
+ _ => None ,
680
+ } ,
681
+ ExprPath ( ..) => if same_var ( cx, idx, var) {
682
+ Some ( Offset :: positive ( "0" . into ( ) ) )
683
+ } else {
684
+ None
685
+ } ,
686
+ _ => None ,
687
+ } ;
688
+
689
+ offset. map ( |o| {
690
+ FixedOffsetVar {
691
+ var_name : snippet_opt ( cx, seqexpr. span ) . unwrap_or_else ( || "???" . into ( ) ) ,
692
+ offset : o,
693
+ }
694
+ } )
695
+ } else {
696
+ None
697
+ }
698
+ }
699
+
700
+ fn get_indexed_assignments < ' a , ' tcx > (
701
+ cx : & LateContext < ' a , ' tcx > ,
702
+ body : & Expr ,
703
+ var : DefId ,
704
+ ) -> Vec < ( FixedOffsetVar , FixedOffsetVar ) > {
705
+ fn get_assignment < ' a , ' tcx > (
706
+ cx : & LateContext < ' a , ' tcx > ,
707
+ e : & Expr ,
708
+ var : DefId ,
709
+ ) -> Option < ( FixedOffsetVar , FixedOffsetVar ) > {
710
+ if let Expr_ :: ExprAssign ( ref lhs, ref rhs) = e. node {
711
+ match ( get_fixed_offset_var ( cx, lhs, var) , get_fixed_offset_var ( cx, rhs, var) ) {
712
+ ( Some ( offset_left) , Some ( offset_right) ) => Some ( ( offset_left, offset_right) ) ,
713
+ _ => None ,
714
+ }
715
+ } else {
716
+ None
717
+ }
718
+ }
719
+
720
+ if let Expr_ :: ExprBlock ( ref b) = body. node {
721
+ let Block {
722
+ ref stmts,
723
+ ref expr,
724
+ ..
725
+ } = * * b;
726
+
727
+ stmts
728
+ . iter ( )
729
+ . map ( |stmt| match stmt. node {
730
+ Stmt_ :: StmtDecl ( ..) => None ,
731
+ Stmt_ :: StmtExpr ( ref e, _node_id) | Stmt_ :: StmtSemi ( ref e, _node_id) => Some ( get_assignment ( cx, e, var) ) ,
732
+ } )
733
+ . chain (
734
+ expr. as_ref ( )
735
+ . into_iter ( )
736
+ . map ( |e| Some ( get_assignment ( cx, & * e, var) ) ) ,
737
+ )
738
+ . filter_map ( |op| op)
739
+ . collect :: < Option < Vec < _ > > > ( )
740
+ . unwrap_or_else ( || vec ! [ ] )
741
+ } else {
742
+ get_assignment ( cx, body, var) . into_iter ( ) . collect ( )
743
+ }
744
+ }
745
+
746
+ /// Check for for loops that sequentially copy items from one slice-like
747
+ /// object to another.
748
+ fn detect_manual_memcpy < ' a , ' tcx > (
749
+ cx : & LateContext < ' a , ' tcx > ,
750
+ pat : & ' tcx Pat ,
751
+ arg : & ' tcx Expr ,
752
+ body : & ' tcx Expr ,
753
+ expr : & ' tcx Expr ,
754
+ ) {
755
+ if let Some ( higher:: Range {
756
+ start : Some ( start) ,
757
+ ref end,
758
+ limits,
759
+ } ) = higher:: range ( arg)
760
+ {
761
+ // the var must be a single name
762
+ if let PatKind :: Binding ( _, def_id, _, _) = pat. node {
763
+ let print_sum = |arg1 : & Offset , arg2 : & Offset | -> String {
764
+ match ( & arg1. value [ ..] , arg1. negate , & arg2. value [ ..] , arg2. negate ) {
765
+ ( "0" , _, "0" , _) => "" . into ( ) ,
766
+ ( "0" , _, x, false ) | ( x, false , "0" , false ) => x. into ( ) ,
767
+ ( "0" , _, x, true ) | ( x, false , "0" , true ) => format ! ( "-{}" , x) ,
768
+ ( x, false , y, false ) => format ! ( "({} + {})" , x, y) ,
769
+ ( x, false , y, true ) => format ! ( "({} - {})" , x, y) ,
770
+ ( x, true , y, false ) => format ! ( "({} - {})" , y, x) ,
771
+ ( x, true , y, true ) => format ! ( "-({} + {})" , x, y) ,
772
+ }
773
+ } ;
774
+
775
+ let print_limit = |end : & Option < & Expr > , offset : Offset , var_name : & str | if let Some ( end) = * end {
776
+ if_let_chain ! { [
777
+ let ExprMethodCall ( ref method, _, ref len_args) = end. node,
778
+ method. name == "len" ,
779
+ len_args. len( ) == 1 ,
780
+ let Some ( arg) = len_args. get( 0 ) ,
781
+ snippet( cx, arg. span, "??" ) == var_name,
782
+ ] , {
783
+ return if offset. negate {
784
+ format!( "({} - {})" , snippet( cx, end. span, "<src>.len()" ) , offset. value)
785
+ } else {
786
+ "" . to_owned( )
787
+ } ;
788
+ } }
789
+
790
+ let end_str = match limits {
791
+ ast:: RangeLimits :: Closed => {
792
+ let end = sugg:: Sugg :: hir ( cx, end, "<count>" ) ;
793
+ format ! ( "{}" , end + sugg:: ONE )
794
+ } ,
795
+ ast:: RangeLimits :: HalfOpen => format ! ( "{}" , snippet( cx, end. span, ".." ) ) ,
796
+ } ;
797
+
798
+ print_sum ( & Offset :: positive ( end_str) , & offset)
799
+ } else {
800
+ ".." . into ( )
801
+ } ;
802
+
803
+ // The only statements in the for loops can be indexed assignments from
804
+ // indexed retrievals.
805
+ let manual_copies = get_indexed_assignments ( cx, body, def_id) ;
806
+
807
+ let big_sugg = manual_copies
808
+ . into_iter ( )
809
+ . map ( |( dst_var, src_var) | {
810
+ let start_str = Offset :: positive ( snippet_opt ( cx, start. span ) . unwrap_or_else ( || "" . into ( ) ) ) ;
811
+ let dst_offset = print_sum ( & start_str, & dst_var. offset ) ;
812
+ let dst_limit = print_limit ( end, dst_var. offset , & dst_var. var_name ) ;
813
+ let src_offset = print_sum ( & start_str, & src_var. offset ) ;
814
+ let src_limit = print_limit ( end, src_var. offset , & src_var. var_name ) ;
815
+ let dst = if dst_offset == "" && dst_limit == "" {
816
+ dst_var. var_name
817
+ } else {
818
+ format ! ( "{}[{}..{}]" , dst_var. var_name, dst_offset, dst_limit)
819
+ } ;
820
+
821
+ format ! ( "{}.clone_from_slice(&{}[{}..{}])" , dst, src_var. var_name, src_offset, src_limit)
822
+ } )
823
+ . join ( "\n " ) ;
824
+
825
+ if !big_sugg. is_empty ( ) {
826
+ span_lint_and_sugg (
827
+ cx,
828
+ MANUAL_MEMCPY ,
829
+ expr. span ,
830
+ "it looks like you're manually copying between slices" ,
831
+ "try replacing the loop by" ,
832
+ big_sugg,
833
+ ) ;
834
+ }
835
+ }
836
+ }
573
837
}
574
838
575
839
/// Check for looping over a range and then indexing a sequence with it.
@@ -1024,9 +1288,29 @@ impl<'tcx> Visitor<'tcx> for UsedVisitor {
1024
1288
fn visit_expr ( & mut self , expr : & ' tcx Expr ) {
1025
1289
if match_var ( expr, self . var ) {
1026
1290
self . used = true ;
1027
- return ;
1291
+ } else {
1292
+ walk_expr ( self , expr) ;
1293
+ }
1294
+ }
1295
+
1296
+ fn nested_visit_map < ' this > ( & ' this mut self ) -> NestedVisitorMap < ' this , ' tcx > {
1297
+ NestedVisitorMap :: None
1298
+ }
1299
+ }
1300
+
1301
+ struct DefIdUsedVisitor < ' a , ' tcx : ' a > {
1302
+ cx : & ' a LateContext < ' a , ' tcx > ,
1303
+ def_id : DefId ,
1304
+ used : bool ,
1305
+ }
1306
+
1307
+ impl < ' a , ' tcx : ' a > Visitor < ' tcx > for DefIdUsedVisitor < ' a , ' tcx > {
1308
+ fn visit_expr ( & mut self , expr : & ' tcx Expr ) {
1309
+ if same_var ( self . cx , expr, self . def_id ) {
1310
+ self . used = true ;
1311
+ } else {
1312
+ walk_expr ( self , expr) ;
1028
1313
}
1029
- walk_expr ( self , expr) ;
1030
1314
}
1031
1315
1032
1316
fn nested_visit_map < ' this > ( & ' this mut self ) -> NestedVisitorMap < ' this , ' tcx > {
@@ -1054,40 +1338,46 @@ impl<'a, 'tcx> Visitor<'tcx> for VarVisitor<'a, 'tcx> {
1054
1338
if_let_chain ! { [
1055
1339
// an index op
1056
1340
let ExprIndex ( ref seqexpr, ref idx) = expr. node,
1057
- // directly indexing a variable
1058
- let ExprPath ( ref qpath) = idx. node,
1059
- let QPath :: Resolved ( None , ref path) = * qpath,
1060
- path. segments. len( ) == 1 ,
1061
- // our variable!
1062
- self . cx. tables. qpath_def( qpath, expr. hir_id) . def_id( ) == self . var,
1063
1341
// the indexed container is referenced by a name
1064
1342
let ExprPath ( ref seqpath) = seqexpr. node,
1065
1343
let QPath :: Resolved ( None , ref seqvar) = * seqpath,
1066
1344
seqvar. segments. len( ) == 1 ,
1067
1345
] , {
1068
- let def = self . cx. tables. qpath_def( seqpath, seqexpr. hir_id) ;
1069
- match def {
1070
- Def :: Local ( ..) | Def :: Upvar ( ..) => {
1071
- let def_id = def. def_id( ) ;
1072
- let node_id = self . cx. tcx. hir. as_local_node_id( def_id) . expect( "local/upvar are local nodes" ) ;
1073
- let hir_id = self . cx. tcx. hir. node_to_hir_id( node_id) ;
1074
-
1075
- let parent_id = self . cx. tcx. hir. get_parent( expr. id) ;
1076
- let parent_def_id = self . cx. tcx. hir. local_def_id( parent_id) ;
1077
- let extent = self . cx. tcx. region_scope_tree( parent_def_id) . var_scope( hir_id. local_id) ;
1078
- self . indexed. insert( seqvar. segments[ 0 ] . name, Some ( extent) ) ;
1079
- return ; // no need to walk further
1080
- }
1081
- Def :: Static ( ..) | Def :: Const ( ..) => {
1082
- self . indexed. insert( seqvar. segments[ 0 ] . name, None ) ;
1083
- return ; // no need to walk further
1346
+ let index_used = same_var( self . cx, idx, self . var) || {
1347
+ let mut used_visitor = DefIdUsedVisitor {
1348
+ cx: self . cx,
1349
+ def_id: self . var,
1350
+ used: false ,
1351
+ } ;
1352
+ walk_expr( & mut used_visitor, idx) ;
1353
+ used_visitor. used
1354
+ } ;
1355
+
1356
+ if index_used {
1357
+ let def = self . cx. tables. qpath_def( seqpath, seqexpr. hir_id) ;
1358
+ match def {
1359
+ Def :: Local ( ..) | Def :: Upvar ( ..) => {
1360
+ let def_id = def. def_id( ) ;
1361
+ let node_id = self . cx. tcx. hir. as_local_node_id( def_id) . expect( "local/upvar are local nodes" ) ;
1362
+ let hir_id = self . cx. tcx. hir. node_to_hir_id( node_id) ;
1363
+
1364
+ let parent_id = self . cx. tcx. hir. get_parent( expr. id) ;
1365
+ let parent_def_id = self . cx. tcx. hir. local_def_id( parent_id) ;
1366
+ let extent = self . cx. tcx. region_scope_tree( parent_def_id) . var_scope( hir_id. local_id) ;
1367
+ self . indexed. insert( seqvar. segments[ 0 ] . name, Some ( extent) ) ;
1368
+ return ; // no need to walk further *on the variable*
1369
+ }
1370
+ Def :: Static ( ..) | Def :: Const ( ..) => {
1371
+ self . indexed. insert( seqvar. segments[ 0 ] . name, None ) ;
1372
+ return ; // no need to walk further *on the variable*
1373
+ }
1374
+ _ => ( ) ,
1084
1375
}
1085
- _ => ( ) ,
1086
1376
}
1087
1377
} }
1088
1378
1089
1379
if_let_chain ! { [
1090
- // directly indexing a variable
1380
+ // directly using a variable
1091
1381
let ExprPath ( ref qpath) = expr. node,
1092
1382
let QPath :: Resolved ( None , ref path) = * qpath,
1093
1383
path. segments. len( ) == 1 ,
0 commit comments