diff options
Diffstat (limited to 'crates/ra_hir')
-rw-r--r-- | crates/ra_hir/src/ids.rs | 42 | ||||
-rw-r--r-- | crates/ra_hir/src/nameres.rs | 16 | ||||
-rw-r--r-- | crates/ra_hir/src/nameres/collector.rs | 165 |
3 files changed, 202 insertions, 21 deletions
diff --git a/crates/ra_hir/src/ids.rs b/crates/ra_hir/src/ids.rs index 2a1ed9b81..c7849c995 100644 --- a/crates/ra_hir/src/ids.rs +++ b/crates/ra_hir/src/ids.rs | |||
@@ -63,11 +63,15 @@ impl HirFileId { | |||
63 | match file_id.0 { | 63 | match file_id.0 { |
64 | HirFileIdRepr::File(file_id) => db.parse(file_id), | 64 | HirFileIdRepr::File(file_id) => db.parse(file_id), |
65 | HirFileIdRepr::Macro(macro_call_id) => { | 65 | HirFileIdRepr::Macro(macro_call_id) => { |
66 | parse_macro(db, macro_call_id).unwrap_or_else(|| { | 66 | parse_macro(db, macro_call_id).unwrap_or_else(|err| { |
67 | // Note: | 67 | // Note: |
68 | // The final goal we would like to make all parse_macro success, | 68 | // The final goal we would like to make all parse_macro success, |
69 | // such that the following log will not call anyway. | 69 | // such that the following log will not call anyway. |
70 | log::warn!("fail on macro_parse: {}", macro_call_id.debug_dump(db)); | 70 | log::warn!( |
71 | "fail on macro_parse: (reason: {}) {}", | ||
72 | err, | ||
73 | macro_call_id.debug_dump(db) | ||
74 | ); | ||
71 | 75 | ||
72 | // returning an empty string looks fishy... | 76 | // returning an empty string looks fishy... |
73 | SourceFile::parse("") | 77 | SourceFile::parse("") |
@@ -77,14 +81,27 @@ impl HirFileId { | |||
77 | } | 81 | } |
78 | } | 82 | } |
79 | 83 | ||
80 | fn parse_macro(db: &impl DefDatabase, macro_call_id: MacroCallId) -> Option<TreeArc<SourceFile>> { | 84 | fn parse_macro( |
85 | db: &impl DefDatabase, | ||
86 | macro_call_id: MacroCallId, | ||
87 | ) -> Result<TreeArc<SourceFile>, String> { | ||
81 | let loc = macro_call_id.loc(db); | 88 | let loc = macro_call_id.loc(db); |
82 | let macro_call = loc.ast_id.to_node(db); | 89 | let macro_call = loc.ast_id.to_node(db); |
83 | let (macro_arg, _) = macro_call.token_tree().and_then(mbe::ast_to_token_tree)?; | 90 | let (macro_arg, _) = macro_call |
91 | .token_tree() | ||
92 | .and_then(mbe::ast_to_token_tree) | ||
93 | .ok_or("Fail to args in to tt::TokenTree")?; | ||
94 | |||
95 | let macro_rules = db.macro_def(loc.def).ok_or("Fail to find macro definition")?; | ||
96 | let tt = macro_rules.expand(¯o_arg).map_err(|err| format!("{:?}", err))?; | ||
97 | |||
98 | // Set a hard limit for the expanded tt | ||
99 | let count = tt.count(); | ||
100 | if count > 65536 { | ||
101 | return Err(format!("Total tokens count exceed limit : count = {}", count)); | ||
102 | } | ||
84 | 103 | ||
85 | let macro_rules = db.macro_def(loc.def)?; | 104 | Ok(mbe::token_tree_to_ast_item_list(&tt)) |
86 | let tt = macro_rules.expand(¯o_arg).ok()?; | ||
87 | Some(mbe::token_tree_to_ast_item_list(&tt)) | ||
88 | } | 105 | } |
89 | 106 | ||
90 | #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] | 107 | #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] |
@@ -311,11 +328,18 @@ impl MacroCallId { | |||
311 | pub fn debug_dump(&self, db: &impl DefDatabase) -> String { | 328 | pub fn debug_dump(&self, db: &impl DefDatabase) -> String { |
312 | let loc = self.clone().loc(db); | 329 | let loc = self.clone().loc(db); |
313 | let node = loc.ast_id.to_node(db); | 330 | let node = loc.ast_id.to_node(db); |
314 | let syntax_str = node.syntax().to_string(); | 331 | let syntax_str = node.syntax().text().chunks().collect::<Vec<_>>().join(" "); |
315 | 332 | ||
316 | // dump the file name | 333 | // dump the file name |
317 | let file_id: HirFileId = self.clone().into(); | 334 | let file_id: HirFileId = self.clone().into(); |
318 | let original = file_id.original_file(db); | 335 | let original = file_id.original_file(db); |
319 | format!("macro call [file: {:#?}] : {}", db.file_relative_path(original), syntax_str) | 336 | let macro_rules = db.macro_def(loc.def); |
337 | |||
338 | format!( | ||
339 | "macro call [file: {:#?}] : {}\nhas rules: {}", | ||
340 | db.file_relative_path(original), | ||
341 | syntax_str, | ||
342 | macro_rules.is_some() | ||
343 | ) | ||
320 | } | 344 | } |
321 | } | 345 | } |
diff --git a/crates/ra_hir/src/nameres.rs b/crates/ra_hir/src/nameres.rs index 39152360c..fbfff4fd7 100644 --- a/crates/ra_hir/src/nameres.rs +++ b/crates/ra_hir/src/nameres.rs | |||
@@ -55,7 +55,7 @@ mod tests; | |||
55 | 55 | ||
56 | use std::sync::Arc; | 56 | use std::sync::Arc; |
57 | 57 | ||
58 | use rustc_hash::FxHashMap; | 58 | use rustc_hash::{FxHashMap, FxHashSet}; |
59 | use ra_arena::{Arena, RawId, impl_arena_id}; | 59 | use ra_arena::{Arena, RawId, impl_arena_id}; |
60 | use ra_db::{FileId, Edition}; | 60 | use ra_db::{FileId, Edition}; |
61 | use test_utils::tested_by; | 61 | use test_utils::tested_by; |
@@ -91,6 +91,19 @@ pub struct CrateDefMap { | |||
91 | root: CrateModuleId, | 91 | root: CrateModuleId, |
92 | modules: Arena<CrateModuleId, ModuleData>, | 92 | modules: Arena<CrateModuleId, ModuleData>, |
93 | public_macros: FxHashMap<Name, MacroDefId>, | 93 | public_macros: FxHashMap<Name, MacroDefId>, |
94 | |||
95 | /// Some macros are not well-behavior, which leads to infinite loop | ||
96 | /// e.g. macro_rules! foo { ($ty:ty) => { foo!($ty); } } | ||
97 | /// We mark it down and skip it in collector | ||
98 | /// | ||
99 | /// FIXME: | ||
100 | /// Right now it only handle a poison macro in a single crate, | ||
101 | /// such that if other crate try to call that macro, | ||
102 | /// the whole process will do again until it became poisoned in that crate. | ||
103 | /// We should handle this macro set globally | ||
104 | /// However, do we want to put it as a global variable? | ||
105 | poison_macros: FxHashSet<MacroDefId>, | ||
106 | |||
94 | diagnostics: Vec<DefDiagnostic>, | 107 | diagnostics: Vec<DefDiagnostic>, |
95 | } | 108 | } |
96 | 109 | ||
@@ -195,6 +208,7 @@ impl CrateDefMap { | |||
195 | root, | 208 | root, |
196 | modules, | 209 | modules, |
197 | public_macros: FxHashMap::default(), | 210 | public_macros: FxHashMap::default(), |
211 | poison_macros: FxHashSet::default(), | ||
198 | diagnostics: Vec::new(), | 212 | diagnostics: Vec::new(), |
199 | } | 213 | } |
200 | }; | 214 | }; |
diff --git a/crates/ra_hir/src/nameres/collector.rs b/crates/ra_hir/src/nameres/collector.rs index 6147b3219..4590a5184 100644 --- a/crates/ra_hir/src/nameres/collector.rs +++ b/crates/ra_hir/src/nameres/collector.rs | |||
@@ -42,12 +42,40 @@ pub(super) fn collect_defs(db: &impl DefDatabase, mut def_map: CrateDefMap) -> C | |||
42 | unresolved_imports: Vec::new(), | 42 | unresolved_imports: Vec::new(), |
43 | unexpanded_macros: Vec::new(), | 43 | unexpanded_macros: Vec::new(), |
44 | global_macro_scope: FxHashMap::default(), | 44 | global_macro_scope: FxHashMap::default(), |
45 | marco_stack_count: 0, | 45 | macro_stack_monitor: MacroStackMonitor::default(), |
46 | }; | 46 | }; |
47 | collector.collect(); | 47 | collector.collect(); |
48 | collector.finish() | 48 | collector.finish() |
49 | } | 49 | } |
50 | 50 | ||
51 | #[derive(Default)] | ||
52 | struct MacroStackMonitor { | ||
53 | counts: FxHashMap<MacroDefId, u32>, | ||
54 | |||
55 | /// Mainly use for test | ||
56 | validator: Option<Box<dyn Fn(u32) -> bool>>, | ||
57 | } | ||
58 | |||
59 | impl MacroStackMonitor { | ||
60 | fn increase(&mut self, macro_def_id: MacroDefId) { | ||
61 | *self.counts.entry(macro_def_id).or_default() += 1; | ||
62 | } | ||
63 | |||
64 | fn decrease(&mut self, macro_def_id: MacroDefId) { | ||
65 | *self.counts.entry(macro_def_id).or_default() -= 1; | ||
66 | } | ||
67 | |||
68 | fn is_poison(&self, macro_def_id: MacroDefId) -> bool { | ||
69 | let cur = *self.counts.get(¯o_def_id).unwrap_or(&0); | ||
70 | |||
71 | if let Some(validator) = &self.validator { | ||
72 | validator(cur) | ||
73 | } else { | ||
74 | cur > 100 | ||
75 | } | ||
76 | } | ||
77 | } | ||
78 | |||
51 | /// Walks the tree of module recursively | 79 | /// Walks the tree of module recursively |
52 | struct DefCollector<DB> { | 80 | struct DefCollector<DB> { |
53 | db: DB, | 81 | db: DB, |
@@ -59,7 +87,7 @@ struct DefCollector<DB> { | |||
59 | 87 | ||
60 | /// Some macro use `$tt:tt which mean we have to handle the macro perfectly | 88 | /// Some macro use `$tt:tt which mean we have to handle the macro perfectly |
61 | /// To prevent stackoverflow, we add a deep counter here for prevent that. | 89 | /// To prevent stackoverflow, we add a deep counter here for prevent that. |
62 | marco_stack_count: u32, | 90 | macro_stack_monitor: MacroStackMonitor, |
63 | } | 91 | } |
64 | 92 | ||
65 | impl<'a, DB> DefCollector<&'a DB> | 93 | impl<'a, DB> DefCollector<&'a DB> |
@@ -317,30 +345,40 @@ where | |||
317 | let def_map = self.db.crate_def_map(krate); | 345 | let def_map = self.db.crate_def_map(krate); |
318 | if let Some(macro_id) = def_map.public_macros.get(&path.segments[1].name).cloned() { | 346 | if let Some(macro_id) = def_map.public_macros.get(&path.segments[1].name).cloned() { |
319 | let call_id = MacroCallLoc { def: macro_id, ast_id: *ast_id }.id(self.db); | 347 | let call_id = MacroCallLoc { def: macro_id, ast_id: *ast_id }.id(self.db); |
320 | resolved.push((*module_id, call_id)); | 348 | resolved.push((*module_id, call_id, macro_id)); |
321 | } | 349 | } |
322 | false | 350 | false |
323 | }); | 351 | }); |
324 | 352 | ||
325 | for (module_id, macro_call_id) in resolved { | 353 | for (module_id, macro_call_id, macro_def_id) in resolved { |
326 | self.collect_macro_expansion(module_id, macro_call_id); | 354 | self.collect_macro_expansion(module_id, macro_call_id, macro_def_id); |
327 | } | 355 | } |
328 | res | 356 | res |
329 | } | 357 | } |
330 | 358 | ||
331 | fn collect_macro_expansion(&mut self, module_id: CrateModuleId, macro_call_id: MacroCallId) { | 359 | fn collect_macro_expansion( |
332 | self.marco_stack_count += 1; | 360 | &mut self, |
361 | module_id: CrateModuleId, | ||
362 | macro_call_id: MacroCallId, | ||
363 | macro_def_id: MacroDefId, | ||
364 | ) { | ||
365 | if self.def_map.poison_macros.contains(¯o_def_id) { | ||
366 | return; | ||
367 | } | ||
368 | |||
369 | self.macro_stack_monitor.increase(macro_def_id); | ||
333 | 370 | ||
334 | if self.marco_stack_count < 300 { | 371 | if !self.macro_stack_monitor.is_poison(macro_def_id) { |
335 | let file_id: HirFileId = macro_call_id.into(); | 372 | let file_id: HirFileId = macro_call_id.into(); |
336 | let raw_items = self.db.raw_items(file_id); | 373 | let raw_items = self.db.raw_items(file_id); |
337 | ModCollector { def_collector: &mut *self, file_id, module_id, raw_items: &raw_items } | 374 | ModCollector { def_collector: &mut *self, file_id, module_id, raw_items: &raw_items } |
338 | .collect(raw_items.items()) | 375 | .collect(raw_items.items()); |
339 | } else { | 376 | } else { |
340 | log::error!("Too deep macro expansion: {}", macro_call_id.debug_dump(self.db)); | 377 | log::error!("Too deep macro expansion: {}", macro_call_id.debug_dump(self.db)); |
378 | self.def_map.poison_macros.insert(macro_def_id); | ||
341 | } | 379 | } |
342 | 380 | ||
343 | self.marco_stack_count -= 1; | 381 | self.macro_stack_monitor.decrease(macro_def_id); |
344 | } | 382 | } |
345 | 383 | ||
346 | fn finish(self) -> CrateDefMap { | 384 | fn finish(self) -> CrateDefMap { |
@@ -484,7 +522,7 @@ where | |||
484 | { | 522 | { |
485 | let macro_call_id = MacroCallLoc { def: macro_id, ast_id }.id(self.def_collector.db); | 523 | let macro_call_id = MacroCallLoc { def: macro_id, ast_id }.id(self.def_collector.db); |
486 | 524 | ||
487 | self.def_collector.collect_macro_expansion(self.module_id, macro_call_id); | 525 | self.def_collector.collect_macro_expansion(self.module_id, macro_call_id, macro_id); |
488 | return; | 526 | return; |
489 | } | 527 | } |
490 | 528 | ||
@@ -530,3 +568,108 @@ fn resolve_submodule( | |||
530 | None => Err(if is_dir_owner { file_mod } else { file_dir_mod }), | 568 | None => Err(if is_dir_owner { file_mod } else { file_dir_mod }), |
531 | } | 569 | } |
532 | } | 570 | } |
571 | |||
572 | #[cfg(test)] | ||
573 | mod tests { | ||
574 | use ra_db::SourceDatabase; | ||
575 | |||
576 | use crate::{Crate, mock::MockDatabase, DefDatabase}; | ||
577 | use ra_arena::{Arena}; | ||
578 | use super::*; | ||
579 | use rustc_hash::FxHashSet; | ||
580 | |||
581 | fn do_collect_defs( | ||
582 | db: &impl DefDatabase, | ||
583 | def_map: CrateDefMap, | ||
584 | monitor: MacroStackMonitor, | ||
585 | ) -> CrateDefMap { | ||
586 | let mut collector = DefCollector { | ||
587 | db, | ||
588 | def_map, | ||
589 | glob_imports: FxHashMap::default(), | ||
590 | unresolved_imports: Vec::new(), | ||
591 | unexpanded_macros: Vec::new(), | ||
592 | global_macro_scope: FxHashMap::default(), | ||
593 | macro_stack_monitor: monitor, | ||
594 | }; | ||
595 | collector.collect(); | ||
596 | collector.finish() | ||
597 | } | ||
598 | |||
599 | fn do_limited_resolve(code: &str, limit: u32, poison_limit: u32) -> CrateDefMap { | ||
600 | let (db, _source_root, _) = MockDatabase::with_single_file(&code); | ||
601 | let crate_id = db.crate_graph().iter().next().unwrap(); | ||
602 | let krate = Crate { crate_id }; | ||
603 | |||
604 | let def_map = { | ||
605 | let edition = krate.edition(&db); | ||
606 | let mut modules: Arena<CrateModuleId, ModuleData> = Arena::default(); | ||
607 | let root = modules.alloc(ModuleData::default()); | ||
608 | CrateDefMap { | ||
609 | krate, | ||
610 | edition, | ||
611 | extern_prelude: FxHashMap::default(), | ||
612 | prelude: None, | ||
613 | root, | ||
614 | modules, | ||
615 | public_macros: FxHashMap::default(), | ||
616 | poison_macros: FxHashSet::default(), | ||
617 | diagnostics: Vec::new(), | ||
618 | } | ||
619 | }; | ||
620 | |||
621 | let mut monitor = MacroStackMonitor::default(); | ||
622 | monitor.validator = Some(Box::new(move |count| { | ||
623 | assert!(count < limit); | ||
624 | count >= poison_limit | ||
625 | })); | ||
626 | |||
627 | do_collect_defs(&db, def_map, monitor) | ||
628 | } | ||
629 | |||
630 | #[test] | ||
631 | fn test_macro_expand_limit_width() { | ||
632 | do_limited_resolve( | ||
633 | r#" | ||
634 | macro_rules! foo { | ||
635 | ($($ty:ty)*) => { foo!($($ty)*, $($ty)*); } | ||
636 | } | ||
637 | foo!(KABOOM); | ||
638 | "#, | ||
639 | 16, | ||
640 | 1000, | ||
641 | ); | ||
642 | } | ||
643 | |||
644 | #[test] | ||
645 | fn test_macro_expand_poisoned() { | ||
646 | let def = do_limited_resolve( | ||
647 | r#" | ||
648 | macro_rules! foo { | ||
649 | ($ty:ty) => { foo!($ty); } | ||
650 | } | ||
651 | foo!(KABOOM); | ||
652 | "#, | ||
653 | 100, | ||
654 | 16, | ||
655 | ); | ||
656 | |||
657 | assert_eq!(def.poison_macros.len(), 1); | ||
658 | } | ||
659 | |||
660 | #[test] | ||
661 | fn test_macro_expand_normal() { | ||
662 | let def = do_limited_resolve( | ||
663 | r#" | ||
664 | macro_rules! foo { | ||
665 | ($ident:ident) => { struct $ident {} } | ||
666 | } | ||
667 | foo!(Bar); | ||
668 | "#, | ||
669 | 16, | ||
670 | 16, | ||
671 | ); | ||
672 | |||
673 | assert_eq!(def.poison_macros.len(), 0); | ||
674 | } | ||
675 | } | ||