11use rustc_hir:: def_id:: DefId ;
22use rustc_hir:: intravisit:: FnKind ;
33use rustc_index:: bit_set:: BitSet ;
4+ use rustc_index:: vec:: IndexVec ;
45use 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 } ;
67use rustc_middle:: ty:: subst:: InternalSubsts ;
78use rustc_middle:: ty:: { self , AssocItem , AssocItemContainer , Instance , TyCtxt } ;
89use rustc_session:: lint:: builtin:: UNCONDITIONAL_RECURSION ;
10+ use std:: collections:: VecDeque ;
911
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 ) {
1113 let hir_id = tcx. hir ( ) . as_local_hir_id ( def_id) . unwrap ( ) ;
1214
1315 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) {
1820fn check_fn_for_unconditional_recursion < ' tcx > (
1921 tcx : TyCtxt < ' tcx > ,
2022 fn_kind : FnKind < ' _ > ,
21- body : & Body < ' tcx > ,
23+ body : & ReadOnlyBodyAndCache < ' _ , ' tcx > ,
2224 def_id : DefId ,
2325) {
2426 if let FnKind :: Closure ( _) = fn_kind {
2527 // closures can't recur, so they don't matter.
2628 return ;
2729 }
2830
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 ( ) ;
6234
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.
7538 continue ;
7639 }
7740
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 ( & [ ] )
10553 }
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 ( & [ ] )
11062 }
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 ( ) ) ;
11377
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 ! [ ]
11682 }
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 ( ) ) ;
11792 }
11893 }
11994
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 ( ) {
125102 let hir_id = tcx. hir ( ) . as_local_hir_id ( def_id) . unwrap ( ) ;
126103 let sp = tcx. sess . source_map ( ) . guess_head_span ( tcx. hir ( ) . span ( hir_id) ) ;
127104 tcx. struct_span_lint_hir ( UNCONDITIONAL_RECURSION , hir_id, sp, |lint| {
128105 let mut db = lint. build ( "function cannot return without recursing" ) ;
129106 db. span_label ( sp, "cannot return without recursing" ) ;
130107 // 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" ) ;
133111 }
134112 db. help ( "a `loop` may express intention better if this is on purpose" ) ;
135113 db. emit ( ) ;
136114 } ) ;
137115 }
138116}
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