diff --git a/crates/hir_def/src/body.rs b/crates/hir_def/src/body.rs index 505b07cc8a..8488b4f0d0 100644 --- a/crates/hir_def/src/body.rs +++ b/crates/hir_def/src/body.rs @@ -54,12 +54,6 @@ pub struct Expander { recursion_limit: usize, } -#[cfg(test)] -static EXPANSION_RECURSION_LIMIT: Limit = Limit::new(32); - -#[cfg(not(test))] -static EXPANSION_RECURSION_LIMIT: Limit = Limit::new(128); - impl CfgExpander { pub(crate) fn new( db: &dyn DefDatabase, @@ -101,7 +95,7 @@ impl Expander { db: &dyn DefDatabase, macro_call: ast::MacroCall, ) -> Result>, UnresolvedMacro> { - if EXPANSION_RECURSION_LIMIT.check(self.recursion_limit + 1).is_err() { + if self.recursion_limit(db).check(self.recursion_limit + 1).is_err() { cov_mark::hit!(your_stack_belongs_to_me); return Ok(ExpandResult::str_err( "reached recursion limit during macro expansion".into(), @@ -222,6 +216,17 @@ impl Expander { let file_local_id = self.ast_id_map.ast_id(item); AstId::new(self.current_file_id, file_local_id) } + + fn recursion_limit(&self, db: &dyn DefDatabase) -> Limit { + let limit = db.crate_limits(self.cfg_expander.krate).recursion_limit as _; + + #[cfg(not(test))] + return Limit::new(limit); + + // Without this, `body::tests::your_stack_belongs_to_me` stack-overflows in debug + #[cfg(test)] + return Limit::new(std::cmp::min(32, limit)); + } } #[derive(Debug)] diff --git a/crates/hir_def/src/body/tests.rs b/crates/hir_def/src/body/tests.rs index 673a753861..1d84da48fb 100644 --- a/crates/hir_def/src/body/tests.rs +++ b/crates/hir_def/src/body/tests.rs @@ -61,6 +61,24 @@ fn main() { n_nuple!(1,2,3); } ); } +#[test] +fn recursion_limit() { + cov_mark::check!(your_stack_belongs_to_me); + + lower( + r#" +#![recursion_limit = "2"] +macro_rules! n_nuple { + ($e:tt) => (); + ($first:tt $($rest:tt)*) => {{ + n_nuple!($($rest)*) + }}; +} +fn main() { n_nuple!(1,2,3); } +"#, + ); +} + #[test] fn macro_resolve() { // Regression test for a path resolution bug introduced with inner item handling. diff --git a/crates/hir_def/src/db.rs b/crates/hir_def/src/db.rs index 7946558311..f9dd935c4b 100644 --- a/crates/hir_def/src/db.rs +++ b/crates/hir_def/src/db.rs @@ -157,9 +157,26 @@ pub trait DefDatabase: InternDatabase + AstDatabase + Upcast { #[salsa::invoke(visibility::function_visibility_query)] fn function_visibility(&self, def: FunctionId) -> Visibility; + + #[salsa::transparent] + fn crate_limits(&self, crate_id: CrateId) -> CrateLimits; } fn crate_def_map_wait(db: &dyn DefDatabase, krate: CrateId) -> Arc { let _p = profile::span("crate_def_map:wait"); db.crate_def_map_query(krate) } + +pub struct CrateLimits { + /// The maximum depth for potentially infinitely-recursive compile-time operations like macro expansion or auto-dereference. + pub recursion_limit: u32, +} + +fn crate_limits(db: &dyn DefDatabase, crate_id: CrateId) -> CrateLimits { + let def_map = db.crate_def_map(crate_id); + + CrateLimits { + // 128 is the default in rustc. + recursion_limit: def_map.recursion_limit().unwrap_or(128), + } +} diff --git a/crates/hir_def/src/nameres.rs b/crates/hir_def/src/nameres.rs index 64929c02b4..ca4255c581 100644 --- a/crates/hir_def/src/nameres.rs +++ b/crates/hir_def/src/nameres.rs @@ -114,6 +114,7 @@ pub struct DefMap { registered_tools: Vec, edition: Edition, + recursion_limit: Option, diagnostics: Vec, } @@ -272,6 +273,7 @@ impl DefMap { block: None, krate, edition, + recursion_limit: None, extern_prelude: FxHashMap::default(), exported_proc_macros: FxHashMap::default(), prelude: None, @@ -461,6 +463,7 @@ impl DefMap { registered_tools, block: _, edition: _, + recursion_limit: _, krate: _, prelude: _, root: _, @@ -482,6 +485,10 @@ impl DefMap { pub fn diagnostics(&self) -> &[DefDiagnostic] { self.diagnostics.as_slice() } + + pub fn recursion_limit(&self) -> Option { + self.recursion_limit + } } impl ModuleData { diff --git a/crates/hir_def/src/nameres/collector.rs b/crates/hir_def/src/nameres/collector.rs index dc75dc4d91..7f7213f4c3 100644 --- a/crates/hir_def/src/nameres/collector.rs +++ b/crates/hir_def/src/nameres/collector.rs @@ -271,6 +271,17 @@ impl DefCollector<'_> { None => continue, }; + if *attr_name == hir_expand::name![recursion_limit] { + if let Some(input) = &attr.input { + if let AttrInput::Literal(limit) = &**input { + if let Ok(limit) = limit.parse() { + self.def_map.recursion_limit = Some(limit); + } + } + } + continue; + } + let attr_is_register_like = *attr_name == hir_expand::name![register_attr] || *attr_name == hir_expand::name![register_tool]; if !attr_is_register_like { diff --git a/crates/hir_expand/src/name.rs b/crates/hir_expand/src/name.rs index 4dcda0fcdc..c36bd09e2b 100644 --- a/crates/hir_expand/src/name.rs +++ b/crates/hir_expand/src/name.rs @@ -270,6 +270,7 @@ pub mod known { global_allocator, test, test_case, + recursion_limit, // Safe intrinsics abort, add_with_overflow,