@@ -19,7 +19,7 @@ use la_arena::{Arena, ArenaMap};
19
19
use limit:: Limit ;
20
20
use profile:: Count ;
21
21
use rustc_hash:: FxHashMap ;
22
- use syntax:: { ast, AstPtr , SyntaxNodePtr } ;
22
+ use syntax:: { ast, AstPtr , SyntaxNode , SyntaxNodePtr } ;
23
23
24
24
use crate :: {
25
25
attr:: Attrs ,
@@ -51,7 +51,8 @@ pub struct Expander {
51
51
def_map : Arc < DefMap > ,
52
52
current_file_id : HirFileId ,
53
53
module : LocalModuleId ,
54
- recursion_limit : usize ,
54
+ /// `recursion_depth == usize::MAX` indicates that the recursion limit has been reached.
55
+ recursion_depth : usize ,
55
56
}
56
57
57
58
impl CfgExpander {
@@ -84,7 +85,7 @@ impl Expander {
84
85
def_map,
85
86
current_file_id,
86
87
module : module. local_id ,
87
- recursion_limit : 0 ,
88
+ recursion_depth : 0 ,
88
89
}
89
90
}
90
91
@@ -93,47 +94,52 @@ impl Expander {
93
94
db : & dyn DefDatabase ,
94
95
macro_call : ast:: MacroCall ,
95
96
) -> Result < ExpandResult < Option < ( Mark , T ) > > , UnresolvedMacro > {
96
- if self . recursion_limit ( db) . check ( self . recursion_limit + 1 ) . is_err ( ) {
97
- cov_mark:: hit!( your_stack_belongs_to_me) ;
98
- return Ok ( ExpandResult :: only_err ( ExpandError :: Other (
99
- "reached recursion limit during macro expansion" . into ( ) ,
100
- ) ) ) ;
97
+ let mut unresolved_macro_err = None ;
98
+
99
+ let result = self . within_limit ( db, |this| {
100
+ let macro_call = InFile :: new ( this. current_file_id , & macro_call) ;
101
+
102
+ let resolver =
103
+ |path| this. resolve_path_as_macro ( db, & path) . map ( |it| macro_id_to_def_id ( db, it) ) ;
104
+
105
+ let mut err = None ;
106
+ let call_id = match macro_call. as_call_id_with_errors (
107
+ db,
108
+ this. def_map . krate ( ) ,
109
+ resolver,
110
+ & mut |e| {
111
+ err. get_or_insert ( e) ;
112
+ } ,
113
+ ) {
114
+ Ok ( call_id) => call_id,
115
+ Err ( resolve_err) => {
116
+ unresolved_macro_err = Some ( resolve_err) ;
117
+ return ExpandResult { value : None , err : None } ;
118
+ }
119
+ } ;
120
+ ExpandResult { value : call_id. ok ( ) , err }
121
+ } ) ;
122
+
123
+ if let Some ( err) = unresolved_macro_err {
124
+ Err ( err)
125
+ } else {
126
+ Ok ( result)
101
127
}
102
-
103
- let macro_call = InFile :: new ( self . current_file_id , & macro_call) ;
104
-
105
- let resolver =
106
- |path| self . resolve_path_as_macro ( db, & path) . map ( |it| macro_id_to_def_id ( db, it) ) ;
107
-
108
- let mut err = None ;
109
- let call_id =
110
- macro_call. as_call_id_with_errors ( db, self . def_map . krate ( ) , resolver, & mut |e| {
111
- err. get_or_insert ( e) ;
112
- } ) ?;
113
- let call_id = match call_id {
114
- Ok ( it) => it,
115
- Err ( _) => {
116
- return Ok ( ExpandResult { value : None , err } ) ;
117
- }
118
- } ;
119
-
120
- Ok ( self . enter_expand_inner ( db, call_id, err) )
121
128
}
122
129
123
130
pub fn enter_expand_id < T : ast:: AstNode > (
124
131
& mut self ,
125
132
db : & dyn DefDatabase ,
126
133
call_id : MacroCallId ,
127
134
) -> ExpandResult < Option < ( Mark , T ) > > {
128
- self . enter_expand_inner ( db, call_id , None )
135
+ self . within_limit ( db, |_this| ExpandResult :: ok ( Some ( call_id ) ) )
129
136
}
130
137
131
- fn enter_expand_inner < T : ast:: AstNode > (
132
- & mut self ,
138
+ fn enter_expand_inner (
133
139
db : & dyn DefDatabase ,
134
140
call_id : MacroCallId ,
135
141
mut err : Option < ExpandError > ,
136
- ) -> ExpandResult < Option < ( Mark , T ) > > {
142
+ ) -> ExpandResult < Option < ( HirFileId , SyntaxNode ) > > {
137
143
if err. is_none ( ) {
138
144
err = db. macro_expand_error ( call_id) ;
139
145
}
@@ -154,29 +160,21 @@ impl Expander {
154
160
}
155
161
} ;
156
162
157
- let node = match T :: cast ( raw_node) {
158
- Some ( it) => it,
159
- None => {
160
- // This can happen without being an error, so only forward previous errors.
161
- return ExpandResult { value : None , err } ;
162
- }
163
- } ;
164
-
165
- tracing:: debug!( "macro expansion {:#?}" , node. syntax( ) ) ;
166
-
167
- self . recursion_limit += 1 ;
168
- let mark =
169
- Mark { file_id : self . current_file_id , bomb : DropBomb :: new ( "expansion mark dropped" ) } ;
170
- self . cfg_expander . hygiene = Hygiene :: new ( db. upcast ( ) , file_id) ;
171
- self . current_file_id = file_id;
172
-
173
- ExpandResult { value : Some ( ( mark, node) ) , err }
163
+ ExpandResult { value : Some ( ( file_id, raw_node) ) , err }
174
164
}
175
165
176
166
pub fn exit ( & mut self , db : & dyn DefDatabase , mut mark : Mark ) {
177
167
self . cfg_expander . hygiene = Hygiene :: new ( db. upcast ( ) , mark. file_id ) ;
178
168
self . current_file_id = mark. file_id ;
179
- self . recursion_limit -= 1 ;
169
+ if self . recursion_depth == usize:: MAX {
170
+ // Recursion limit has been reached somewhere in the macro expansion tree. Reset the
171
+ // depth only when we get out of the tree.
172
+ if !self . current_file_id . is_macro ( ) {
173
+ self . recursion_depth = 0 ;
174
+ }
175
+ } else {
176
+ self . recursion_depth -= 1 ;
177
+ }
180
178
mark. bomb . defuse ( ) ;
181
179
}
182
180
@@ -215,6 +213,50 @@ impl Expander {
215
213
#[ cfg( test) ]
216
214
return Limit :: new ( std:: cmp:: min ( 32 , limit) ) ;
217
215
}
216
+
217
+ fn within_limit < F , T : ast:: AstNode > (
218
+ & mut self ,
219
+ db : & dyn DefDatabase ,
220
+ op : F ,
221
+ ) -> ExpandResult < Option < ( Mark , T ) > >
222
+ where
223
+ F : FnOnce ( & mut Self ) -> ExpandResult < Option < MacroCallId > > ,
224
+ {
225
+ if self . recursion_depth == usize:: MAX {
226
+ // Recursion limit has been reached somewhere in the macro expansion tree. We should
227
+ // stop expanding other macro calls in this tree, or else this may result in
228
+ // exponential number of macro expansions, leading to a hang.
229
+ //
230
+ // The overflow error should have been reported when it occurred (see the next branch),
231
+ // so don't return overflow error here to avoid diagnostics duplication.
232
+ cov_mark:: hit!( overflow_but_not_me) ;
233
+ return ExpandResult :: only_err ( ExpandError :: RecursionOverflowPosioned ) ;
234
+ } else if self . recursion_limit ( db) . check ( self . recursion_depth + 1 ) . is_err ( ) {
235
+ self . recursion_depth = usize:: MAX ;
236
+ cov_mark:: hit!( your_stack_belongs_to_me) ;
237
+ return ExpandResult :: only_err ( ExpandError :: Other (
238
+ "reached recursion limit during macro expansion" . into ( ) ,
239
+ ) ) ;
240
+ }
241
+
242
+ let ExpandResult { value, err } = op ( self ) ;
243
+ let Some ( call_id) = value else {
244
+ return ExpandResult { value : None , err } ;
245
+ } ;
246
+
247
+ Self :: enter_expand_inner ( db, call_id, err) . map ( |value| {
248
+ value. and_then ( |( new_file_id, node) | {
249
+ let node = T :: cast ( node) ?;
250
+
251
+ self . recursion_depth += 1 ;
252
+ self . cfg_expander . hygiene = Hygiene :: new ( db. upcast ( ) , new_file_id) ;
253
+ let old_file_id = std:: mem:: replace ( & mut self . current_file_id , new_file_id) ;
254
+ let mark =
255
+ Mark { file_id : old_file_id, bomb : DropBomb :: new ( "expansion mark dropped" ) } ;
256
+ Some ( ( mark, node) )
257
+ } )
258
+ } )
259
+ }
218
260
}
219
261
220
262
#[ derive( Debug ) ]
0 commit comments