@@ -3,14 +3,18 @@ use rustc_data_structures::graph::iterate::{
3
3
NodeStatus , TriColorDepthFirstSearch , TriColorVisitor ,
4
4
} ;
5
5
use rustc_hir:: def:: DefKind ;
6
- use rustc_middle:: mir:: { self , BasicBlock , BasicBlocks , Body , Operand , TerminatorKind } ;
7
- use rustc_middle:: ty:: { self , Instance , TyCtxt } ;
6
+ use rustc_middle:: mir:: { self , BasicBlock , BasicBlocks , Body , Terminator , TerminatorKind } ;
7
+ use rustc_middle:: ty:: { self , Instance , Ty , TyCtxt } ;
8
8
use rustc_middle:: ty:: { GenericArg , GenericArgs } ;
9
9
use rustc_session:: lint:: builtin:: UNCONDITIONAL_RECURSION ;
10
10
use rustc_span:: Span ;
11
11
use std:: ops:: ControlFlow ;
12
12
13
13
pub ( crate ) fn check < ' tcx > ( tcx : TyCtxt < ' tcx > , body : & Body < ' tcx > ) {
14
+ check_call_recursion ( tcx, body) ;
15
+ }
16
+
17
+ fn check_call_recursion < ' tcx > ( tcx : TyCtxt < ' tcx > , body : & Body < ' tcx > ) {
14
18
let def_id = body. source . def_id ( ) . expect_local ( ) ;
15
19
16
20
if let DefKind :: Fn | DefKind :: AssocFn = tcx. def_kind ( def_id) {
@@ -23,7 +27,19 @@ pub(crate) fn check<'tcx>(tcx: TyCtxt<'tcx>, body: &Body<'tcx>) {
23
27
_ => & [ ] ,
24
28
} ;
25
29
26
- let mut vis = Search { tcx, body, reachable_recursive_calls : vec ! [ ] , trait_args } ;
30
+ check_recursion ( tcx, body, CallRecursion { trait_args } )
31
+ }
32
+ }
33
+
34
+ fn check_recursion < ' tcx > (
35
+ tcx : TyCtxt < ' tcx > ,
36
+ body : & Body < ' tcx > ,
37
+ classifier : impl TerminatorClassifier < ' tcx > ,
38
+ ) {
39
+ let def_id = body. source . def_id ( ) . expect_local ( ) ;
40
+
41
+ if let DefKind :: Fn | DefKind :: AssocFn = tcx. def_kind ( def_id) {
42
+ let mut vis = Search { tcx, body, classifier, reachable_recursive_calls : vec ! [ ] } ;
27
43
if let Some ( NonRecursive ) =
28
44
TriColorDepthFirstSearch :: new ( & body. basic_blocks ) . run_from_start ( & mut vis)
29
45
{
@@ -46,20 +62,66 @@ pub(crate) fn check<'tcx>(tcx: TyCtxt<'tcx>, body: &Body<'tcx>) {
46
62
}
47
63
}
48
64
65
+ /// Requires drop elaboration to have been performed first.
66
+ pub fn check_drop_recursion < ' tcx > ( tcx : TyCtxt < ' tcx > , body : & Body < ' tcx > ) {
67
+ let def_id = body. source . def_id ( ) . expect_local ( ) ;
68
+
69
+ // First check if `body` is an `fn drop()` of `Drop`
70
+ if let DefKind :: AssocFn = tcx. def_kind ( def_id) &&
71
+ let Some ( trait_ref) = tcx. impl_of_method ( def_id. to_def_id ( ) ) . and_then ( |def_id| tcx. impl_trait_ref ( def_id) ) &&
72
+ let Some ( drop_trait) = tcx. lang_items ( ) . drop_trait ( ) && drop_trait == trait_ref. instantiate_identity ( ) . def_id {
73
+
74
+ // It was. Now figure out for what type `Drop` is implemented and then
75
+ // check for recursion.
76
+ if let ty:: Ref ( _, dropped_ty, _) = tcx. liberate_late_bound_regions (
77
+ def_id. to_def_id ( ) ,
78
+ tcx. fn_sig ( def_id) . instantiate_identity ( ) . input ( 0 ) ,
79
+ ) . kind ( ) {
80
+ check_recursion ( tcx, body, RecursiveDrop { drop_for : * dropped_ty } ) ;
81
+ }
82
+ }
83
+ }
84
+
85
+ trait TerminatorClassifier < ' tcx > {
86
+ fn is_recursive_terminator (
87
+ & self ,
88
+ tcx : TyCtxt < ' tcx > ,
89
+ body : & Body < ' tcx > ,
90
+ terminator : & Terminator < ' tcx > ,
91
+ ) -> bool ;
92
+ }
93
+
49
94
struct NonRecursive ;
50
95
51
- struct Search < ' mir , ' tcx > {
96
+ struct Search < ' mir , ' tcx , C : TerminatorClassifier < ' tcx > > {
52
97
tcx : TyCtxt < ' tcx > ,
53
98
body : & ' mir Body < ' tcx > ,
54
- trait_args : & ' tcx [ GenericArg < ' tcx > ] ,
99
+ classifier : C ,
55
100
56
101
reachable_recursive_calls : Vec < Span > ,
57
102
}
58
103
59
- impl < ' mir , ' tcx > Search < ' mir , ' tcx > {
104
+ struct CallRecursion < ' tcx > {
105
+ trait_args : & ' tcx [ GenericArg < ' tcx > ] ,
106
+ }
107
+
108
+ struct RecursiveDrop < ' tcx > {
109
+ /// The type that `Drop` is implemented for.
110
+ drop_for : Ty < ' tcx > ,
111
+ }
112
+
113
+ impl < ' tcx > TerminatorClassifier < ' tcx > for CallRecursion < ' tcx > {
60
114
/// Returns `true` if `func` refers to the function we are searching in.
61
- fn is_recursive_call ( & self , func : & Operand < ' tcx > , args : & [ Operand < ' tcx > ] ) -> bool {
62
- let Search { tcx, body, trait_args, .. } = * self ;
115
+ fn is_recursive_terminator (
116
+ & self ,
117
+ tcx : TyCtxt < ' tcx > ,
118
+ body : & Body < ' tcx > ,
119
+ terminator : & Terminator < ' tcx > ,
120
+ ) -> bool {
121
+ let TerminatorKind :: Call { func, args, .. } = & terminator. kind else {
122
+ return false ;
123
+ } ;
124
+
63
125
// Resolving function type to a specific instance that is being called is expensive. To
64
126
// avoid the cost we check the number of arguments first, which is sufficient to reject
65
127
// most of calls as non-recursive.
@@ -86,14 +148,30 @@ impl<'mir, 'tcx> Search<'mir, 'tcx> {
86
148
// calling into an entirely different method (for example, a call from the default
87
149
// method in the trait to `<A as Trait<B>>::method`, where `A` and/or `B` are
88
150
// specific types).
89
- return callee == caller && & call_args[ ..trait_args. len ( ) ] == trait_args;
151
+ return callee == caller && & call_args[ ..self . trait_args . len ( ) ] == self . trait_args ;
90
152
}
91
153
92
154
false
93
155
}
94
156
}
95
157
96
- impl < ' mir , ' tcx > TriColorVisitor < BasicBlocks < ' tcx > > for Search < ' mir , ' tcx > {
158
+ impl < ' tcx > TerminatorClassifier < ' tcx > for RecursiveDrop < ' tcx > {
159
+ fn is_recursive_terminator (
160
+ & self ,
161
+ tcx : TyCtxt < ' tcx > ,
162
+ body : & Body < ' tcx > ,
163
+ terminator : & Terminator < ' tcx > ,
164
+ ) -> bool {
165
+ let TerminatorKind :: Drop { place, .. } = & terminator. kind else { return false } ;
166
+
167
+ let dropped_ty = place. ty ( body, tcx) . ty ;
168
+ dropped_ty == self . drop_for
169
+ }
170
+ }
171
+
172
+ impl < ' mir , ' tcx , C : TerminatorClassifier < ' tcx > > TriColorVisitor < BasicBlocks < ' tcx > >
173
+ for Search < ' mir , ' tcx , C >
174
+ {
97
175
type BreakVal = NonRecursive ;
98
176
99
177
fn node_examined (
@@ -138,26 +216,23 @@ impl<'mir, 'tcx> TriColorVisitor<BasicBlocks<'tcx>> for Search<'mir, 'tcx> {
138
216
fn node_settled ( & mut self , bb : BasicBlock ) -> ControlFlow < Self :: BreakVal > {
139
217
// When we examine a node for the last time, remember it if it is a recursive call.
140
218
let terminator = self . body [ bb] . terminator ( ) ;
141
- if let TerminatorKind :: Call { func, args, .. } = & terminator. kind {
142
- if self . is_recursive_call ( func, args) {
143
- self . reachable_recursive_calls . push ( terminator. source_info . span ) ;
144
- }
219
+ if self . classifier . is_recursive_terminator ( self . tcx , self . body , terminator) {
220
+ self . reachable_recursive_calls . push ( terminator. source_info . span ) ;
145
221
}
146
222
147
223
ControlFlow :: Continue ( ( ) )
148
224
}
149
225
150
226
fn ignore_edge ( & mut self , bb : BasicBlock , target : BasicBlock ) -> bool {
151
227
let terminator = self . body [ bb] . terminator ( ) ;
152
- if terminator. unwind ( ) == Some ( & mir:: UnwindAction :: Cleanup ( target) )
153
- && terminator. successors ( ) . count ( ) > 1
228
+ let ignore_unwind = terminator. unwind ( ) == Some ( & mir:: UnwindAction :: Cleanup ( target) )
229
+ && terminator. successors ( ) . count ( ) > 1 ;
230
+ if ignore_unwind || self . classifier . is_recursive_terminator ( self . tcx , self . body , terminator)
154
231
{
155
232
return true ;
156
233
}
157
- // Don't traverse successors of recursive calls or false CFG edges.
158
- match self . body [ bb] . terminator ( ) . kind {
159
- TerminatorKind :: Call { ref func, ref args, .. } => self . is_recursive_call ( func, args) ,
160
- TerminatorKind :: FalseEdge { imaginary_target, .. } => imaginary_target == target,
234
+ match & terminator. kind {
235
+ TerminatorKind :: FalseEdge { imaginary_target, .. } => imaginary_target == & target,
161
236
_ => false ,
162
237
}
163
238
}
0 commit comments