@@ -19,7 +19,7 @@ use la_arena::{Arena, ArenaMap};
1919use limit:: Limit ;
2020use profile:: Count ;
2121use rustc_hash:: FxHashMap ;
22- use syntax:: { ast, AstPtr , SyntaxNodePtr } ;
22+ use syntax:: { ast, AstPtr , SyntaxNode , SyntaxNodePtr } ;
2323
2424use crate :: {
2525 attr:: Attrs ,
@@ -51,7 +51,8 @@ pub struct Expander {
5151 def_map : Arc < DefMap > ,
5252 current_file_id : HirFileId ,
5353 module : LocalModuleId ,
54- recursion_limit : usize ,
54+ /// `recursion_depth == usize::MAX` indicates that the recursion limit has been reached.
55+ recursion_depth : usize ,
5556}
5657
5758impl CfgExpander {
@@ -84,7 +85,7 @@ impl Expander {
8485 def_map,
8586 current_file_id,
8687 module : module. local_id ,
87- recursion_limit : 0 ,
88+ recursion_depth : 0 ,
8889 }
8990 }
9091
@@ -93,47 +94,52 @@ impl Expander {
9394 db : & dyn DefDatabase ,
9495 macro_call : ast:: MacroCall ,
9596 ) -> 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)
101127 }
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) )
121128 }
122129
123130 pub fn enter_expand_id < T : ast:: AstNode > (
124131 & mut self ,
125132 db : & dyn DefDatabase ,
126133 call_id : MacroCallId ,
127134 ) -> ExpandResult < Option < ( Mark , T ) > > {
128- self . enter_expand_inner ( db, call_id , None )
135+ self . within_limit ( db, |_this| ExpandResult :: ok ( Some ( call_id ) ) )
129136 }
130137
131- fn enter_expand_inner < T : ast:: AstNode > (
132- & mut self ,
138+ fn enter_expand_inner (
133139 db : & dyn DefDatabase ,
134140 call_id : MacroCallId ,
135141 mut err : Option < ExpandError > ,
136- ) -> ExpandResult < Option < ( Mark , T ) > > {
142+ ) -> ExpandResult < Option < ( HirFileId , SyntaxNode ) > > {
137143 if err. is_none ( ) {
138144 err = db. macro_expand_error ( call_id) ;
139145 }
@@ -154,29 +160,21 @@ impl Expander {
154160 }
155161 } ;
156162
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 }
174164 }
175165
176166 pub fn exit ( & mut self , db : & dyn DefDatabase , mut mark : Mark ) {
177167 self . cfg_expander . hygiene = Hygiene :: new ( db. upcast ( ) , mark. file_id ) ;
178168 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+ }
180178 mark. bomb . defuse ( ) ;
181179 }
182180
@@ -215,6 +213,50 @@ impl Expander {
215213 #[ cfg( test) ]
216214 return Limit :: new ( std:: cmp:: min ( 32 , limit) ) ;
217215 }
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+ }
218260}
219261
220262#[ derive( Debug ) ]
0 commit comments