Skip to main content

forgekit_core/cfg/
paths.rs

1//! Execution paths through a CFG and the builder that enumerates them.
2//!
3//! Extracted/merged from `types.rs` + `path_builder.rs` (SPLIT-24).
4
5use crate::storage::UnifiedGraphStore;
6use crate::types::{BlockId, PathId, PathKind, SymbolId};
7use std::sync::Arc;
8
9use super::load_test_cfg;
10
11// ---------------------------------------------------------------------------
12// Path
13// ---------------------------------------------------------------------------
14
15#[derive(Clone, Debug, PartialEq, Eq)]
16pub struct Path {
17    pub id: PathId,
18    pub kind: PathKind,
19    pub blocks: Vec<BlockId>,
20    pub length: usize,
21}
22
23impl Path {
24    pub fn new(blocks: Vec<BlockId>) -> Self {
25        let length = blocks.len();
26        let mut hasher = blake3::Hasher::new();
27        for block in &blocks {
28            hasher.update(&block.0.to_le_bytes());
29        }
30        let hash = hasher.finalize();
31        let mut id = [0u8; 16];
32        id.copy_from_slice(&hash.as_bytes()[0..16]);
33
34        Self {
35            id: PathId(id),
36            kind: PathKind::Normal,
37            blocks,
38            length,
39        }
40    }
41
42    pub fn with_kind(blocks: Vec<BlockId>, kind: PathKind) -> Self {
43        let length = blocks.len();
44        let mut hasher = blake3::Hasher::new();
45        for block in &blocks {
46            hasher.update(&block.0.to_le_bytes());
47        }
48        let hash = hasher.finalize();
49        let mut id = [0u8; 16];
50        id.copy_from_slice(&hash.as_bytes()[0..16]);
51
52        Self {
53            id: PathId(id),
54            kind,
55            blocks,
56            length,
57        }
58    }
59
60    pub fn is_normal(&self) -> bool {
61        self.kind == PathKind::Normal
62    }
63
64    pub fn is_error(&self) -> bool {
65        self.kind == PathKind::Error
66    }
67
68    pub fn contains(&self, block: BlockId) -> bool {
69        self.blocks.contains(&block)
70    }
71
72    pub fn entry(&self) -> Option<BlockId> {
73        self.blocks.first().copied()
74    }
75
76    pub fn exit(&self) -> Option<BlockId> {
77        self.blocks.last().copied()
78    }
79}
80
81// ---------------------------------------------------------------------------
82// PathBuilder
83// ---------------------------------------------------------------------------
84
85#[derive(Clone, Default)]
86pub struct PathBuilder {
87    pub(super) function: Option<SymbolId>,
88    pub(super) store: Option<Arc<UnifiedGraphStore>>,
89    pub(super) normal_only: bool,
90    pub(super) error_only: bool,
91    pub(super) max_length: Option<usize>,
92    pub(super) limit: Option<usize>,
93}
94
95impl PathBuilder {
96    pub fn normal_only(mut self) -> Self {
97        self.normal_only = true;
98        self.error_only = false;
99        self
100    }
101
102    pub fn error_only(mut self) -> Self {
103        self.normal_only = false;
104        self.error_only = true;
105        self
106    }
107
108    pub fn max_length(mut self, n: usize) -> Self {
109        self.max_length = Some(n);
110        self
111    }
112
113    pub fn limit(mut self, n: usize) -> Self {
114        self.limit = Some(n);
115        self
116    }
117
118    pub async fn execute(self) -> crate::error::Result<Vec<Path>> {
119        if let (Some(symbol), Some(store)) = (&self.function, &self.store) {
120            if let Some(cfg) = load_test_cfg(&store.db_path, symbol.0)? {
121                let mut paths = cfg.enumerate_paths();
122                if let Some(max) = self.max_length {
123                    paths.retain(|p| p.blocks.len() <= max);
124                }
125                if let Some(limit) = self.limit {
126                    paths.truncate(limit);
127                }
128                return Ok(paths);
129            }
130            let _ = symbol;
131        }
132
133        if let Some(symbol) = &self.function {
134            let entry = BlockId(symbol.0);
135            Ok(vec![Path {
136                id: PathId([0; 16]),
137                kind: PathKind::Normal,
138                blocks: vec![entry],
139                length: 1,
140            }])
141        } else {
142            Ok(Vec::new())
143        }
144    }
145}
146
147#[cfg(test)]
148mod tests {
149    use super::*;
150
151    #[test]
152    fn test_path_creation() {
153        let blocks = vec![BlockId(0), BlockId(1), BlockId(2)];
154        let path = Path::new(blocks.clone());
155
156        assert_eq!(path.blocks, blocks);
157        assert_eq!(path.length, 3);
158        assert!(path.is_normal());
159        assert!(!path.is_error());
160    }
161
162    #[test]
163    fn test_path_with_kind() {
164        let blocks = vec![BlockId(0), BlockId(1)];
165        let path = Path::with_kind(blocks.clone(), PathKind::Error);
166
167        assert_eq!(path.blocks, blocks);
168        assert_eq!(path.kind, PathKind::Error);
169        assert!(!path.is_normal());
170        assert!(path.is_error());
171    }
172
173    #[test]
174    fn test_path_contains() {
175        let path = Path::new(vec![BlockId(0), BlockId(1), BlockId(2)]);
176
177        assert!(path.contains(BlockId(0)));
178        assert!(path.contains(BlockId(1)));
179        assert!(path.contains(BlockId(2)));
180        assert!(!path.contains(BlockId(3)));
181    }
182
183    #[test]
184    fn test_path_entry_exit() {
185        let path = Path::new(vec![BlockId(0), BlockId(1), BlockId(2)]);
186
187        assert_eq!(path.entry(), Some(BlockId(0)));
188        assert_eq!(path.exit(), Some(BlockId(2)));
189    }
190
191    #[test]
192    fn test_path_id_stability() {
193        let blocks = vec![BlockId(0), BlockId(1), BlockId(2)];
194        let path1 = Path::new(blocks.clone());
195        let path2 = Path::new(blocks);
196
197        assert_eq!(path1.id, path2.id);
198    }
199
200    #[test]
201    fn test_path_id_uniqueness() {
202        let blocks1 = vec![BlockId(0), BlockId(1), BlockId(2)];
203        let blocks2 = vec![BlockId(0), BlockId(1), BlockId(3)];
204        let path1 = Path::new(blocks1);
205        let path2 = Path::new(blocks2);
206
207        assert_ne!(path1.id, path2.id);
208    }
209}