1
1
use rustc_hir:: def_id:: DefId ;
2
2
use rustc_hir:: intravisit:: FnKind ;
3
3
use rustc_index:: bit_set:: BitSet ;
4
+ use rustc_index:: vec:: IndexVec ;
4
5
use rustc_middle:: hir:: map:: blocks:: FnLikeNode ;
5
- use rustc_middle:: mir:: { self , Body , TerminatorKind } ;
6
+ use rustc_middle:: mir:: { BasicBlock , Body , ReadOnlyBodyAndCache , TerminatorKind , START_BLOCK } ;
6
7
use rustc_middle:: ty:: subst:: InternalSubsts ;
7
8
use rustc_middle:: ty:: { self , AssocItem , AssocItemContainer , Instance , TyCtxt } ;
8
9
use rustc_session:: lint:: builtin:: UNCONDITIONAL_RECURSION ;
10
+ use std:: collections:: VecDeque ;
9
11
10
- crate fn check < ' tcx > ( tcx : TyCtxt < ' tcx > , body : & Body < ' tcx > , def_id : DefId ) {
12
+ crate fn check < ' tcx > ( tcx : TyCtxt < ' tcx > , body : & ReadOnlyBodyAndCache < ' _ , ' tcx > , def_id : DefId ) {
11
13
let hir_id = tcx. hir ( ) . as_local_hir_id ( def_id) . unwrap ( ) ;
12
14
13
15
if let Some ( fn_like_node) = FnLikeNode :: from_node ( tcx. hir ( ) . get ( hir_id) ) {
@@ -18,121 +20,141 @@ crate fn check<'tcx>(tcx: TyCtxt<'tcx>, body: &Body<'tcx>, def_id: DefId) {
18
20
fn check_fn_for_unconditional_recursion < ' tcx > (
19
21
tcx : TyCtxt < ' tcx > ,
20
22
fn_kind : FnKind < ' _ > ,
21
- body : & Body < ' tcx > ,
23
+ body : & ReadOnlyBodyAndCache < ' _ , ' tcx > ,
22
24
def_id : DefId ,
23
25
) {
24
26
if let FnKind :: Closure ( _) = fn_kind {
25
27
// closures can't recur, so they don't matter.
26
28
return ;
27
29
}
28
30
29
- //FIXME(#54444) rewrite this lint to use the dataflow framework
30
-
31
- // Walk through this function (say `f`) looking to see if
32
- // every possible path references itself, i.e., the function is
33
- // called recursively unconditionally. This is done by trying
34
- // to find a path from the entry node to the exit node that
35
- // *doesn't* call `f` by traversing from the entry while
36
- // pretending that calls of `f` are sinks (i.e., ignoring any
37
- // exit edges from them).
38
- //
39
- // NB. this has an edge case with non-returning statements,
40
- // like `loop {}` or `panic!()`: control flow never reaches
41
- // the exit node through these, so one can have a function
42
- // that never actually calls itself but is still picked up by
43
- // this lint:
44
- //
45
- // fn f(cond: bool) {
46
- // if !cond { panic!() } // could come from `assert!(cond)`
47
- // f(false)
48
- // }
49
- //
50
- // In general, functions of that form may be able to call
51
- // itself a finite number of times and then diverge. The lint
52
- // considers this to be an error for two reasons, (a) it is
53
- // easier to implement, and (b) it seems rare to actually want
54
- // to have behaviour like the above, rather than
55
- // e.g., accidentally recursing after an assert.
56
-
57
- let basic_blocks = body. basic_blocks ( ) ;
58
- let mut reachable_without_self_call_queue = vec ! [ mir:: START_BLOCK ] ;
59
- let mut reached_exit_without_self_call = false ;
60
- let mut self_call_locations = vec ! [ ] ;
61
- let mut visited = BitSet :: new_empty ( basic_blocks. len ( ) ) ;
31
+ let self_calls = find_blocks_calling_self ( tcx, & body, def_id) ;
32
+ let mut results = IndexVec :: from_elem_n ( vec ! [ ] , body. basic_blocks ( ) . len ( ) ) ;
33
+ let mut queue: VecDeque < _ > = self_calls. iter ( ) . collect ( ) ;
62
34
63
- let param_env = tcx. param_env ( def_id) ;
64
- let trait_substs_count = match tcx. opt_associated_item ( def_id) {
65
- Some ( AssocItem { container : AssocItemContainer :: TraitContainer ( trait_def_id) , .. } ) => {
66
- tcx. generics_of ( trait_def_id) . count ( )
67
- }
68
- _ => 0 ,
69
- } ;
70
- let caller_substs = & InternalSubsts :: identity_for_item ( tcx, def_id) [ ..trait_substs_count] ;
71
-
72
- while let Some ( bb) = reachable_without_self_call_queue. pop ( ) {
73
- if !visited. insert ( bb) {
74
- //already done
35
+ while let Some ( bb) = queue. pop_front ( ) {
36
+ if !results[ bb] . is_empty ( ) {
37
+ // Already propagated.
75
38
continue ;
76
39
}
77
40
78
- let block = & basic_blocks[ bb] ;
79
-
80
- if let Some ( ref terminator) = block. terminator {
81
- match terminator. kind {
82
- TerminatorKind :: Call { ref func, .. } => {
83
- let func_ty = func. ty ( body, tcx) ;
84
-
85
- if let ty:: FnDef ( fn_def_id, substs) = func_ty. kind {
86
- let ( call_fn_id, call_substs) = if let Some ( instance) =
87
- Instance :: resolve ( tcx, param_env, fn_def_id, substs)
88
- {
89
- ( instance. def_id ( ) , instance. substs )
90
- } else {
91
- ( fn_def_id, substs)
92
- } ;
93
-
94
- let is_self_call = call_fn_id == def_id
95
- && & call_substs[ ..caller_substs. len ( ) ] == caller_substs;
96
-
97
- if is_self_call {
98
- self_call_locations. push ( terminator. source_info ) ;
99
-
100
- //this is a self call so we shouldn't explore
101
- //further down this path
102
- continue ;
103
- }
104
- }
41
+ let locations = if self_calls. contains ( bb) {
42
+ // `bb` *is* a self-call.
43
+ vec ! [ bb]
44
+ } else {
45
+ // If *all* successors of `bb` lead to a self-call, emit notes at all of their
46
+ // locations.
47
+
48
+ // Converging successors without unwind paths.
49
+ let terminator = body[ bb] . terminator ( ) ;
50
+ let relevant_successors = match & terminator. kind {
51
+ TerminatorKind :: Call { destination : Some ( ( _, dest) ) , .. } => {
52
+ Some ( dest) . into_iter ( ) . chain ( & [ ] )
105
53
}
106
- TerminatorKind :: Abort | TerminatorKind :: Return => {
107
- //found a path!
108
- reached_exit_without_self_call = true ;
109
- break ;
54
+ TerminatorKind :: Call { destination : None , .. } => None . into_iter ( ) . chain ( & [ ] ) ,
55
+ TerminatorKind :: SwitchInt { targets, .. } => None . into_iter ( ) . chain ( targets) ,
56
+ TerminatorKind :: Goto { target }
57
+ | TerminatorKind :: Drop { target, .. }
58
+ | TerminatorKind :: DropAndReplace { target, .. }
59
+ | TerminatorKind :: Assert { target, .. } => Some ( target) . into_iter ( ) . chain ( & [ ] ) ,
60
+ TerminatorKind :: Yield { .. } | TerminatorKind :: GeneratorDrop => {
61
+ None . into_iter ( ) . chain ( & [ ] )
110
62
}
111
- _ => { }
112
- }
63
+ TerminatorKind :: FalseEdges { real_target, .. }
64
+ | TerminatorKind :: FalseUnwind { real_target, .. } => {
65
+ Some ( real_target) . into_iter ( ) . chain ( & [ ] )
66
+ }
67
+ TerminatorKind :: Resume
68
+ | TerminatorKind :: Abort
69
+ | TerminatorKind :: Return
70
+ | TerminatorKind :: Unreachable => {
71
+ unreachable ! ( "unexpected terminator {:?}" , terminator. kind)
72
+ }
73
+ } ;
74
+
75
+ let all_are_self_calls =
76
+ relevant_successors. clone ( ) . all ( |& succ| !results[ succ] . is_empty ( ) ) ;
113
77
114
- for successor in terminator. successors ( ) {
115
- reachable_without_self_call_queue. push ( * successor) ;
78
+ if all_are_self_calls {
79
+ relevant_successors. flat_map ( |& succ| results[ succ] . iter ( ) . copied ( ) ) . collect ( )
80
+ } else {
81
+ vec ! [ ]
116
82
}
83
+ } ;
84
+
85
+ if !locations. is_empty ( ) {
86
+ // This is a newly confirmed-to-always-reach-self-call block.
87
+ results[ bb] = locations;
88
+
89
+ // Propagate backwards through the CFG.
90
+ debug ! ( "propagate loc={:?} in {:?} -> {:?}" , results[ bb] , bb, body. predecessors( ) [ bb] ) ;
91
+ queue. extend ( body. predecessors ( ) [ bb] . iter ( ) . copied ( ) ) ;
117
92
}
118
93
}
119
94
120
- // Check the number of self calls because a function that
121
- // doesn't return (e.g., calls a `-> !` function or `loop { /*
122
- // no break */ }`) shouldn't be linted unless it actually
123
- // recurs.
124
- if !reached_exit_without_self_call && !self_call_locations. is_empty ( ) {
95
+ debug ! ( "unconditional recursion results: {:?}" , results) ;
96
+
97
+ let self_call_locations = & mut results[ START_BLOCK ] ;
98
+ self_call_locations. sort ( ) ;
99
+ self_call_locations. dedup ( ) ;
100
+
101
+ if !self_call_locations. is_empty ( ) {
125
102
let hir_id = tcx. hir ( ) . as_local_hir_id ( def_id) . unwrap ( ) ;
126
103
let sp = tcx. sess . source_map ( ) . guess_head_span ( tcx. hir ( ) . span ( hir_id) ) ;
127
104
tcx. struct_span_lint_hir ( UNCONDITIONAL_RECURSION , hir_id, sp, |lint| {
128
105
let mut db = lint. build ( "function cannot return without recursing" ) ;
129
106
db. span_label ( sp, "cannot return without recursing" ) ;
130
107
// offer some help to the programmer.
131
- for location in & self_call_locations {
132
- db. span_label ( location. span , "recursive call site" ) ;
108
+ for bb in self_call_locations {
109
+ let span = body. basic_blocks ( ) [ * bb] . terminator ( ) . source_info . span ;
110
+ db. span_label ( span, "recursive call site" ) ;
133
111
}
134
112
db. help ( "a `loop` may express intention better if this is on purpose" ) ;
135
113
db. emit ( ) ;
136
114
} ) ;
137
115
}
138
116
}
117
+
118
+ /// Finds blocks with `Call` terminators that would end up calling back into the same method.
119
+ fn find_blocks_calling_self < ' tcx > (
120
+ tcx : TyCtxt < ' tcx > ,
121
+ body : & Body < ' tcx > ,
122
+ def_id : DefId ,
123
+ ) -> BitSet < BasicBlock > {
124
+ let param_env = tcx. param_env ( def_id) ;
125
+ let trait_substs_count = match tcx. opt_associated_item ( def_id) {
126
+ Some ( AssocItem { container : AssocItemContainer :: TraitContainer ( trait_def_id) , .. } ) => {
127
+ tcx. generics_of ( trait_def_id) . count ( )
128
+ }
129
+ _ => 0 ,
130
+ } ;
131
+ let caller_substs = & InternalSubsts :: identity_for_item ( tcx, def_id) [ ..trait_substs_count] ;
132
+
133
+ let mut self_calls = BitSet :: new_empty ( body. basic_blocks ( ) . len ( ) ) ;
134
+
135
+ for ( bb, data) in body. basic_blocks ( ) . iter_enumerated ( ) {
136
+ if let TerminatorKind :: Call { func, .. } = & data. terminator ( ) . kind {
137
+ let func_ty = func. ty ( body, tcx) ;
138
+
139
+ if let ty:: FnDef ( fn_def_id, substs) = func_ty. kind {
140
+ let ( call_fn_id, call_substs) =
141
+ if let Some ( instance) = Instance :: resolve ( tcx, param_env, fn_def_id, substs) {
142
+ ( instance. def_id ( ) , instance. substs )
143
+ } else {
144
+ ( fn_def_id, substs)
145
+ } ;
146
+
147
+ // FIXME(#57965): Make this work across function boundaries
148
+
149
+ let is_self_call =
150
+ call_fn_id == def_id && & call_substs[ ..caller_substs. len ( ) ] == caller_substs;
151
+
152
+ if is_self_call {
153
+ self_calls. insert ( bb) ;
154
+ }
155
+ }
156
+ }
157
+ }
158
+
159
+ self_calls
160
+ }
0 commit comments