1use std::sync::Arc;
6use std::collections::{HashMap, HashSet, VecDeque};
7use crate::storage::UnifiedGraphStore;
8use crate::error::Result;
9use crate::types::{SymbolId, BlockId, PathId, PathKind};
10
11#[derive(Clone)]
30pub struct CfgModule {
31 store: Arc<UnifiedGraphStore>,
32}
33
34#[derive(Clone, Debug)]
36pub struct FunctionCfg {
37 pub symbol_id: SymbolId,
38 pub name: String,
39 pub cfg: TestCfg,
40}
41
42impl CfgModule {
43 pub(crate) fn new(store: Arc<UnifiedGraphStore>) -> Self {
44 Self { store }
45 }
46
47 pub async fn index(&self) -> Result<()> {
55 self.index_source_files().await
57 }
58
59 async fn index_source_files(&self) -> Result<()> {
61 {
62
63
64
65 Self::index_directory(
66 &self.store.codebase_path,
67 &self.store.codebase_path,
68 ).await?;
69 }
70
71 Ok(())
72 }
73
74 async fn index_directory(
75 root: &std::path::Path,
76 dir: &std::path::Path,
77 ) -> Result<()> {
78 use crate::treesitter::CfgExtractor;
79 use tokio::fs;
80
81 let mut entries = fs::read_dir(dir).await
82 .map_err(|e| crate::error::ForgeError::DatabaseError(
83 format!("Failed to read dir: {}", e)
84 ))?;
85
86 while let Some(entry) = entries.next_entry().await
87 .map_err(|e| crate::error::ForgeError::DatabaseError(
88 format!("Failed to read entry: {}", e)
89 ))?
90 {
91 let path = entry.path();
92 if path.is_dir() {
93 Box::pin(Self::index_directory(root, &path)).await?;
94 } else if let Some(lang) = CfgExtractor::detect_language(&path) {
95 if let Ok(content) = fs::read_to_string(&path).await {
97 let _ = CfgExtractor::extract(&content, lang);
98 }
100 }
101 }
102
103 Ok(())
104 }
105
106 pub async fn extract_function_cfg(
108 &self,
109 file_path: &std::path::Path,
110 function_name: &str,
111 ) -> Result<Option<TestCfg>> {
112 use crate::treesitter::CfgExtractor;
113 use tokio::fs;
114
115 if let Some(lang) = CfgExtractor::detect_language(file_path) {
116 let content = fs::read_to_string(file_path).await
117 .map_err(|e| crate::error::ForgeError::DatabaseError(
118 format!("Failed to read file: {}", e)
119 ))?;
120
121 let functions = CfgExtractor::extract(&content, lang)?;
122
123 for func in functions {
124 if func.name == function_name {
125 return Ok(Some(func.cfg));
126 }
127 }
128 }
129
130 Ok(None)
131 }
132
133 pub fn paths(&self, _function: SymbolId) -> PathBuilder {
139 PathBuilder::default()
140 }
141
142 pub async fn dominators(&self, _function: SymbolId) -> Result<DominatorTree> {
151 let cfg = TestCfg::chain(0, 5);
154 let dom_tree = cfg.compute_dominators();
155
156 Ok(dom_tree)
157 }
158
159 pub async fn loops(&self, _function: SymbolId) -> Result<Vec<Loop>> {
168 let cfg = TestCfg::simple_loop();
171 let loops = cfg.detect_loops();
172
173 Ok(loops)
174 }
175}
176
177#[derive(Clone, Default)]
183pub struct PathBuilder {
184 normal_only: bool,
185 error_only: bool,
186 max_length: Option<usize>,
187 limit: Option<usize>,
188}
189
190impl PathBuilder {
191 pub fn normal_only(mut self) -> Self {
193 self.normal_only = true;
194 self.error_only = false;
195 self
196 }
197
198 pub fn error_only(mut self) -> Self {
200 self.normal_only = false;
201 self.error_only = true;
202 self
203 }
204
205 pub fn max_length(mut self, n: usize) -> Self {
211 self.max_length = Some(n);
212 self
213 }
214
215 pub fn limit(mut self, n: usize) -> Self {
221 self.limit = Some(n);
222 self
223 }
224
225 pub async fn execute(self) -> Result<Vec<Path>> {
234 let path = Path::new(vec![BlockId(0)]);
237 Ok(vec![path])
238 }
239}
240
241#[derive(Clone, Debug, PartialEq, Eq)]
243pub struct DominatorTree {
244 pub root: BlockId,
246 pub dominators: HashMap<BlockId, BlockId>,
248}
249
250impl DominatorTree {
251 pub fn new(root: BlockId) -> Self {
253 Self {
254 root,
255 dominators: HashMap::new(),
256 }
257 }
258
259 pub fn immediate_dominator(&self, block: BlockId) -> Option<BlockId> {
261 self.dominators.get(&block).copied()
262 }
263
264 pub fn dominates(&self, dominator: BlockId, block: BlockId) -> bool {
266 if dominator == block {
267 return true;
268 }
269 if dominator == self.root {
270 return true;
271 }
272 let mut current = block;
273 while let Some(idom) = self.dominators.get(¤t) {
274 if *idom == dominator {
275 return true;
276 }
277 current = *idom;
278 }
279 false
280 }
281
282 pub fn insert(&mut self, block: BlockId, dominator: BlockId) {
284 self.dominators.insert(block, dominator);
285 }
286
287 pub fn len(&self) -> usize {
289 self.dominators.len() + 1
290 }
291
292 pub fn is_empty(&self) -> bool {
294 self.dominators.is_empty()
295 }
296}
297
298#[derive(Clone, Debug, PartialEq, Eq)]
300pub struct Loop {
301 pub header: BlockId,
303 pub blocks: Vec<BlockId>,
305 pub depth: usize,
307}
308
309impl Loop {
310 pub fn new(header: BlockId) -> Self {
312 Self {
313 header,
314 blocks: Vec::new(),
315 depth: 0,
316 }
317 }
318
319 pub fn with_blocks(header: BlockId, blocks: Vec<BlockId>) -> Self {
321 Self {
322 header,
323 blocks,
324 depth: 0,
325 }
326 }
327
328 pub fn with_depth(header: BlockId, blocks: Vec<BlockId>, depth: usize) -> Self {
330 Self {
331 header,
332 blocks,
333 depth,
334 }
335 }
336
337 pub fn contains(&self, block: BlockId) -> bool {
339 self.header == block || self.blocks.contains(&block)
340 }
341
342 pub fn len(&self) -> usize {
344 self.blocks.len() + 1
345 }
346
347 pub fn is_empty(&self) -> bool {
349 self.blocks.is_empty()
350 }
351}
352
353#[derive(Clone, Debug, PartialEq, Eq)]
355pub struct Path {
356 pub id: PathId,
358 pub kind: PathKind,
360 pub blocks: Vec<BlockId>,
362 pub length: usize,
364}
365
366impl Path {
367 pub fn new(blocks: Vec<BlockId>) -> Self {
369 let length = blocks.len();
370 let mut hasher = blake3::Hasher::new();
371 for block in &blocks {
372 hasher.update(&block.0.to_le_bytes());
373 }
374 let hash = hasher.finalize();
375 let mut id = [0u8; 16];
376 id.copy_from_slice(&hash.as_bytes()[0..16]);
377
378 Self {
379 id: PathId(id),
380 kind: PathKind::Normal,
381 blocks,
382 length,
383 }
384 }
385
386 pub fn with_kind(blocks: Vec<BlockId>, kind: PathKind) -> Self {
388 let length = blocks.len();
389 let mut hasher = blake3::Hasher::new();
390 for block in &blocks {
391 hasher.update(&block.0.to_le_bytes());
392 }
393 let hash = hasher.finalize();
394 let mut id = [0u8; 16];
395 id.copy_from_slice(&hash.as_bytes()[0..16]);
396
397 Self {
398 id: PathId(id),
399 kind,
400 blocks,
401 length,
402 }
403 }
404
405 pub fn is_normal(&self) -> bool {
407 self.kind == PathKind::Normal
408 }
409
410 pub fn is_error(&self) -> bool {
412 self.kind == PathKind::Error
413 }
414
415 pub fn contains(&self, block: BlockId) -> bool {
417 self.blocks.contains(&block)
418 }
419
420 pub fn entry(&self) -> Option<BlockId> {
422 self.blocks.first().copied()
423 }
424
425 pub fn exit(&self) -> Option<BlockId> {
427 self.blocks.last().copied()
428 }
429}
430
431#[derive(Clone, Debug)]
433pub struct TestCfg {
434 pub entry: BlockId,
436 pub exits: HashSet<BlockId>,
438 pub error_blocks: HashSet<BlockId>,
440 pub successors: HashMap<BlockId, Vec<BlockId>>,
442 pub predecessors: HashMap<BlockId, Vec<BlockId>>,
444}
445
446impl TestCfg {
447 pub fn new(entry: BlockId) -> Self {
449 Self {
450 entry,
451 exits: HashSet::new(),
452 error_blocks: HashSet::new(),
453 successors: HashMap::new(),
454 predecessors: HashMap::new(),
455 }
456 }
457
458 pub fn add_edge(&mut self, from: BlockId, to: BlockId) -> &mut Self {
460 self.successors.entry(from).or_default().push(to);
461 self.predecessors.entry(to).or_default().push(from);
462 self
463 }
464
465 pub fn add_exit(&mut self, block: BlockId) -> &mut Self {
467 self.exits.insert(block);
468 self
469 }
470
471 pub fn add_error(&mut self, block: BlockId) -> &mut Self {
473 self.error_blocks.insert(block);
474 self
475 }
476
477 pub fn chain(start: i64, count: usize) -> Self {
479 let mut cfg = Self::new(BlockId(start));
480 for i in start..(start + count as i64 - 1) {
481 cfg.add_edge(BlockId(i), BlockId(i + 1));
482 }
483 cfg.add_exit(BlockId(start + count as i64 - 1));
484 cfg
485 }
486
487 pub fn if_else() -> Self {
489 let mut cfg = Self::new(BlockId(0));
490 cfg.add_edge(BlockId(0), BlockId(1))
491 .add_edge(BlockId(0), BlockId(2))
492 .add_edge(BlockId(1), BlockId(3))
493 .add_edge(BlockId(2), BlockId(3))
494 .add_exit(BlockId(3));
495 cfg
496 }
497
498 pub fn simple_loop() -> Self {
500 let mut cfg = Self::new(BlockId(0));
501 cfg.add_edge(BlockId(0), BlockId(1))
502 .add_edge(BlockId(1), BlockId(2))
503 .add_edge(BlockId(2), BlockId(1))
504 .add_edge(BlockId(1), BlockId(3))
505 .add_exit(BlockId(3));
506 cfg
507 }
508
509 pub fn enumerate_paths(&self) -> Vec<Path> {
511 let mut paths = Vec::new();
512 let mut current = vec![self.entry];
513 let mut visited = HashSet::new();
514 self.dfs(&mut paths, &mut current, &mut visited, self.entry);
515 paths
516 }
517
518 fn dfs(&self, paths: &mut Vec<Path>, current: &mut Vec<BlockId>, visited: &mut HashSet<BlockId>, block: BlockId) {
519 if self.exits.contains(&block) {
520 paths.push(Path::new(current.clone()));
521 return;
522 }
523 if visited.contains(&block) {
524 return;
525 }
526 visited.insert(block);
527 if let Some(successors) = self.successors.get(&block) {
528 for &succ in successors {
529 current.push(succ);
530 self.dfs(paths, current, visited, succ);
531 current.pop();
532 }
533 }
534 visited.remove(&block);
535 }
536
537 pub fn compute_dominators(&self) -> DominatorTree {
539 let mut blocks: HashSet<BlockId> = HashSet::new();
540 blocks.insert(self.entry);
541 for (from, tos) in &self.successors {
542 blocks.insert(*from);
543 for to in tos {
544 blocks.insert(*to);
545 }
546 }
547
548 if blocks.is_empty() {
549 return DominatorTree::new(self.entry);
550 }
551
552 let block_list: Vec<BlockId> = blocks.iter().copied().collect();
553 let mut dom: HashMap<BlockId, HashSet<BlockId>> = HashMap::new();
554
555 for &block in &block_list {
556 if block == self.entry {
557 dom.insert(block, HashSet::from([self.entry]));
558 } else {
559 dom.insert(block, blocks.clone());
560 }
561 }
562
563 let mut changed = true;
564 while changed {
565 changed = false;
566 for &block in &block_list {
567 if block == self.entry {
568 continue;
569 }
570 let preds = self.predecessors.get(&block);
571 if preds.is_none() || preds.unwrap().is_empty() {
572 continue;
573 }
574 let mut new_dom: HashSet<BlockId> = dom.get(&preds.unwrap()[0]).cloned().unwrap_or_default();
575 for pred in &preds.unwrap()[1..] {
576 if let Some(pred_dom) = dom.get(pred) {
577 new_dom = new_dom.intersection(pred_dom).copied().collect();
578 }
579 }
580 new_dom.insert(block);
581 if dom.get(&block) != Some(&new_dom) {
582 dom.insert(block, new_dom);
583 changed = true;
584 }
585 }
586 }
587
588 let mut idom: HashMap<BlockId, BlockId> = HashMap::new();
591 for &block in &block_list {
592 if block == self.entry {
593 continue;
594 }
595 if let Some(doms) = dom.get(&block) {
596 let mut best_candidate: Option<BlockId> = None;
598 let mut best_size = 0;
599
600 for &candidate in doms {
601 if candidate == block {
602 continue;
603 }
604 if let Some(candidate_doms) = dom.get(&candidate) {
605 if candidate_doms.len() > best_size {
606 best_size = candidate_doms.len();
607 best_candidate = Some(candidate);
608 }
609 }
610 }
611
612 if let Some(candidate) = best_candidate {
613 idom.insert(block, candidate);
614 }
615 }
616 }
617
618 DominatorTree {
619 root: self.entry,
620 dominators: idom,
621 }
622 }
623
624 pub fn detect_loops(&self) -> Vec<Loop> {
626 let dom = self.compute_dominators();
627 let mut loops = Vec::new();
628
629 for (from, tos) in &self.successors {
630 for to in tos {
631 if dom.dominates(*to, *from) {
632 let header = *to;
633 let mut loop_blocks = HashSet::new();
634 loop_blocks.insert(header);
635 let mut worklist = VecDeque::new();
636 worklist.push_back(*from);
637
638 while let Some(block) = worklist.pop_front() {
639 if loop_blocks.contains(&block) {
640 continue;
641 }
642 if dom.dominates(header, block) || block == header {
643 loop_blocks.insert(block);
644 if let Some(preds) = self.predecessors.get(&block) {
645 for &pred in preds {
646 if !loop_blocks.contains(&pred) {
647 worklist.push_back(pred);
648 }
649 }
650 }
651 }
652 }
653
654 let mut blocks: Vec<BlockId> = loop_blocks.into_iter().filter(|&b| b != header).collect();
655 blocks.sort();
656 loops.push(Loop::with_depth(header, blocks, 0));
657 }
658 }
659 }
660
661 loops
662 }
663}
664
665#[cfg(test)]
666mod tests {
667 use super::*;
668 use crate::storage::BackendKind;
669
670 #[tokio::test]
671 async fn test_cfg_module_creation() {
672 let store = Arc::new(UnifiedGraphStore::open(
673 tempfile::tempdir().unwrap().path(), BackendKind::SQLite
674 ).await.unwrap());
675 let module = CfgModule::new(store.clone());
676
677 assert_eq!(module.store.db_path(), store.db_path());
678 }
679
680 #[tokio::test]
681 async fn test_path_builder_filters() {
682 let store = Arc::new(UnifiedGraphStore::open(
683 std::env::current_dir().unwrap(),
684 BackendKind::SQLite
685 ).await.unwrap());
686
687 let dummy_module = CfgModule {
688 store: store.clone(),
689 };
690
691 let builder = dummy_module.paths(SymbolId(1))
692 .normal_only()
693 .max_length(10);
694
695 assert!(builder.normal_only);
696 assert_eq!(builder.max_length, Some(10));
697 }
698
699 #[tokio::test]
700 async fn test_dominators_basic() {
701 let store = Arc::new(UnifiedGraphStore::open(
702 tempfile::tempdir().unwrap().path(), BackendKind::SQLite
703 ).await.unwrap());
704 let module = CfgModule::new(store);
705
706 let doms = module.dominators(SymbolId(1)).await.unwrap();
707 assert_eq!(doms.root, BlockId(0));
708 assert_eq!(doms.dominators.len(), 4);
710 }
711
712 #[tokio::test]
713 async fn test_loops_detection() {
714 let store = Arc::new(UnifiedGraphStore::open(
715 tempfile::tempdir().unwrap().path(), BackendKind::SQLite
716 ).await.unwrap());
717 let module = CfgModule::new(store);
718
719 let loops = module.loops(SymbolId(1)).await.unwrap();
720 assert_eq!(loops.len(), 1);
722 }
723
724 #[tokio::test]
725 async fn test_paths_execute_returns_placeholder() {
726 let store = Arc::new(UnifiedGraphStore::open(
727 tempfile::tempdir().unwrap().path(), BackendKind::SQLite
728 ).await.unwrap());
729 let module = CfgModule::new(store);
730
731 let paths = module.paths(SymbolId(1)).execute().await.unwrap();
732 assert_eq!(paths.len(), 1);
734 assert_eq!(paths[0].blocks.len(), 1);
735 }
736
737 #[test]
738 fn test_dominator_tree_creation() {
739 let tree = DominatorTree::new(BlockId(0));
740 assert_eq!(tree.root, BlockId(0));
741 assert!(tree.is_empty());
742 assert_eq!(tree.len(), 1);
743 }
744
745 #[test]
746 fn test_dominator_tree_insert() {
747 let mut tree = DominatorTree::new(BlockId(0));
748 tree.insert(BlockId(1), BlockId(0));
749 tree.insert(BlockId(2), BlockId(1));
750
751 assert_eq!(tree.len(), 3);
752 assert_eq!(tree.immediate_dominator(BlockId(1)), Some(BlockId(0)));
753 assert_eq!(tree.immediate_dominator(BlockId(2)), Some(BlockId(1)));
754 assert_eq!(tree.immediate_dominator(BlockId(0)), None);
755 }
756
757 #[test]
758 fn test_dominator_tree_dominates() {
759 let mut tree = DominatorTree::new(BlockId(0));
760 tree.insert(BlockId(1), BlockId(0));
761 tree.insert(BlockId(2), BlockId(1));
762
763 assert!(tree.dominates(BlockId(0), BlockId(0)));
764 assert!(tree.dominates(BlockId(0), BlockId(1)));
765 assert!(tree.dominates(BlockId(0), BlockId(2)));
766 assert!(tree.dominates(BlockId(1), BlockId(1)));
767 assert!(!tree.dominates(BlockId(1), BlockId(0)));
768 }
769
770 #[test]
771 fn test_loop_creation() {
772 let loop_ = Loop::new(BlockId(1));
773 assert_eq!(loop_.header, BlockId(1));
774 assert!(loop_.is_empty());
775 assert_eq!(loop_.len(), 1);
776 assert_eq!(loop_.depth, 0);
777 }
778
779 #[test]
780 fn test_loop_with_blocks() {
781 let blocks = vec![BlockId(2), BlockId(3)];
782 let loop_ = Loop::with_blocks(BlockId(1), blocks.clone());
783
784 assert_eq!(loop_.header, BlockId(1));
785 assert_eq!(loop_.blocks, blocks);
786 assert!(!loop_.is_empty());
787 assert_eq!(loop_.len(), 3);
788 }
789
790 #[test]
791 fn test_loop_contains() {
792 let loop_ = Loop::with_blocks(BlockId(1), vec![BlockId(2), BlockId(3)]);
793
794 assert!(loop_.contains(BlockId(1)));
795 assert!(loop_.contains(BlockId(2)));
796 assert!(loop_.contains(BlockId(3)));
797 assert!(!loop_.contains(BlockId(4)));
798 }
799
800 #[test]
801 fn test_path_creation() {
802 let blocks = vec![BlockId(0), BlockId(1), BlockId(2)];
803 let path = Path::new(blocks.clone());
804
805 assert_eq!(path.blocks, blocks);
806 assert_eq!(path.length, 3);
807 assert!(path.is_normal());
808 assert!(!path.is_error());
809 }
810
811 #[test]
812 fn test_path_with_kind() {
813 let blocks = vec![BlockId(0), BlockId(1)];
814 let path = Path::with_kind(blocks.clone(), PathKind::Error);
815
816 assert_eq!(path.blocks, blocks);
817 assert_eq!(path.kind, PathKind::Error);
818 assert!(!path.is_normal());
819 assert!(path.is_error());
820 }
821
822 #[test]
823 fn test_path_contains() {
824 let path = Path::new(vec![BlockId(0), BlockId(1), BlockId(2)]);
825
826 assert!(path.contains(BlockId(0)));
827 assert!(path.contains(BlockId(1)));
828 assert!(path.contains(BlockId(2)));
829 assert!(!path.contains(BlockId(3)));
830 }
831
832 #[test]
833 fn test_path_entry_exit() {
834 let path = Path::new(vec![BlockId(0), BlockId(1), BlockId(2)]);
835
836 assert_eq!(path.entry(), Some(BlockId(0)));
837 assert_eq!(path.exit(), Some(BlockId(2)));
838 }
839
840 #[test]
841 fn test_path_id_stability() {
842 let blocks = vec![BlockId(0), BlockId(1), BlockId(2)];
843 let path1 = Path::new(blocks.clone());
844 let path2 = Path::new(blocks);
845
846 assert_eq!(path1.id, path2.id);
847 }
848
849 #[test]
850 fn test_path_id_uniqueness() {
851 let blocks1 = vec![BlockId(0), BlockId(1), BlockId(2)];
852 let blocks2 = vec![BlockId(0), BlockId(1), BlockId(3)];
853 let path1 = Path::new(blocks1);
854 let path2 = Path::new(blocks2);
855
856 assert_ne!(path1.id, path2.id);
857 }
858
859 #[test]
860 fn test_test_cfg_chain() {
861 let cfg = TestCfg::chain(0, 5);
862
863 assert_eq!(cfg.entry, BlockId(0));
864 assert!(cfg.exits.contains(&BlockId(4)));
865
866 assert_eq!(cfg.successors.get(&BlockId(0)), Some(&vec![BlockId(1)]));
868 assert_eq!(cfg.successors.get(&BlockId(1)), Some(&vec![BlockId(2)]));
869 assert_eq!(cfg.successors.get(&BlockId(2)), Some(&vec![BlockId(3)]));
870 assert_eq!(cfg.successors.get(&BlockId(3)), Some(&vec![BlockId(4)]));
871 }
872
873 #[test]
874 fn test_test_cfg_if_else() {
875 let cfg = TestCfg::if_else();
876
877 assert_eq!(cfg.entry, BlockId(0));
878 assert!(cfg.exits.contains(&BlockId(3)));
879
880 let succ0 = cfg.successors.get(&BlockId(0)).unwrap();
881 assert!(succ0.contains(&BlockId(1)));
882 assert!(succ0.contains(&BlockId(2)));
883 assert_eq!(cfg.successors.get(&BlockId(1)), Some(&vec![BlockId(3)]));
884 assert_eq!(cfg.successors.get(&BlockId(2)), Some(&vec![BlockId(3)]));
885 }
886
887 #[test]
888 fn test_paths_simple_chain() {
889 let cfg = TestCfg::chain(0, 4);
890 let paths = cfg.enumerate_paths();
891
892 assert_eq!(paths.len(), 1);
893 assert_eq!(paths[0].blocks, vec![BlockId(0), BlockId(1), BlockId(2), BlockId(3)]);
894 assert!(paths[0].is_normal());
895 }
896
897 #[test]
898 fn test_paths_if_else() {
899 let cfg = TestCfg::if_else();
900 let paths = cfg.enumerate_paths();
901
902 assert_eq!(paths.len(), 2);
903
904 assert_eq!(paths[0].entry(), Some(BlockId(0)));
905 assert_eq!(paths[0].exit(), Some(BlockId(3)));
906 assert_eq!(paths[1].entry(), Some(BlockId(0)));
907 assert_eq!(paths[1].exit(), Some(BlockId(3)));
908
909 let paths_set: HashSet<_> = paths.iter().map(|p| p.blocks.clone()).collect();
910
911 assert!(paths_set.contains(&vec![BlockId(0), BlockId(1), BlockId(3)]));
912 assert!(paths_set.contains(&vec![BlockId(0), BlockId(2), BlockId(3)]));
913 }
914
915 #[test]
916 fn test_dominators_chain() {
917 let cfg = TestCfg::chain(0, 5);
918 let dom = cfg.compute_dominators();
919
920 assert_eq!(dom.root, BlockId(0));
921 assert_eq!(dom.immediate_dominator(BlockId(1)), Some(BlockId(0)));
922 assert_eq!(dom.immediate_dominator(BlockId(2)), Some(BlockId(1)));
923 assert_eq!(dom.immediate_dominator(BlockId(3)), Some(BlockId(2)));
924 assert_eq!(dom.immediate_dominator(BlockId(4)), Some(BlockId(3)));
925 }
926
927 #[test]
928 fn test_dominators_if_else() {
929 let cfg = TestCfg::if_else();
930 let dom = cfg.compute_dominators();
931
932 assert!(dom.dominates(BlockId(0), BlockId(0)));
933 assert!(dom.dominates(BlockId(0), BlockId(1)));
934 assert!(dom.dominates(BlockId(0), BlockId(2)));
935 assert!(dom.dominates(BlockId(0), BlockId(3)));
936 assert_eq!(dom.immediate_dominator(BlockId(3)), Some(BlockId(0)));
937 }
938
939 #[test]
940 fn test_loops_simple_loop() {
941 let cfg = TestCfg::simple_loop();
942 let loops = cfg.detect_loops();
943
944 assert_eq!(loops.len(), 1);
945 assert_eq!(loops[0].header, BlockId(1));
946 assert!(!loops[0].blocks.is_empty());
947 assert!(loops[0].blocks.contains(&BlockId(2)));
948 }
949
950 #[test]
951 fn test_loops_none_in_chain() {
952 let cfg = TestCfg::chain(0, 5);
953 let loops = cfg.detect_loops();
954
955 assert_eq!(loops.len(), 0);
956 }
957
958 #[test]
959 fn test_loops_none_in_if_else() {
960 let cfg = TestCfg::if_else();
961 let loops = cfg.detect_loops();
962
963 assert_eq!(loops.len(), 0);
964 }
965}