Skip to main content

geographdb_core/algorithms/
slicing.rs

1//! Program Slicing - Backward and Forward Slicing
2//!
3//! Program slicing extracts all statements that affect or are affected by
4//! a particular program point (the slicing criterion).
5//!
6//! # Types
7//!
8//! - **Backward Slice**: All statements that affect the criterion
9//! - **Forward Slice**: All statements affected by the criterion
10//! - **Full Slice**: Union of backward and forward slices
11//!
12//! # Applications
13//!
14//! - Impact analysis (what breaks if I change this?)
15//! - Debugging (what code affects this variable?)
16//! - Dead code detection (unreachable from entry)
17//! - Optimization (remove unused code)
18
19use crate::algorithms::astar::CfgGraphNode;
20use std::collections::{HashMap, HashSet, VecDeque};
21
22/// Result of program slicing
23#[derive(Debug, Clone)]
24pub struct SliceResult {
25    /// Nodes in the slice
26    pub nodes: HashSet<u64>,
27    /// Edges within the slice (source, target)
28    pub edges: Vec<(u64, u64)>,
29    /// Slice direction
30    pub direction: SliceDirection,
31    /// Slicing criterion (target node)
32    pub criterion: u64,
33}
34
35/// Direction of program slice
36#[derive(Debug, Clone, Copy, PartialEq, Eq)]
37pub enum SliceDirection {
38    /// Backward slice (what affects criterion)
39    Backward,
40    /// Forward slice (what is affected by criterion)
41    Forward,
42}
43
44/// Compute backward slice from a criterion node
45///
46/// The backward slice contains all nodes that can reach the criterion.
47/// This answers: "What code affects this point?"
48///
49/// # Arguments
50/// * `nodes` - CFG nodes
51/// * `criterion` - Slicing criterion (target node)
52///
53/// # Returns
54/// SliceResult with all nodes and edges in the backward slice
55pub fn backward_slice(nodes: &[CfgGraphNode], criterion: u64) -> SliceResult {
56    // Build predecessor map
57    let mut predecessors: HashMap<u64, Vec<u64>> = HashMap::new();
58    let mut all_nodes: HashSet<u64> = HashSet::new();
59
60    for node in nodes {
61        all_nodes.insert(node.id);
62        for &succ in &node.successors {
63            predecessors.entry(succ).or_default().push(node.id);
64        }
65    }
66
67    // BFS backward from criterion
68    let mut slice_nodes: HashSet<u64> = HashSet::new();
69    let mut worklist: VecDeque<u64> = VecDeque::new();
70
71    if all_nodes.contains(&criterion) {
72        slice_nodes.insert(criterion);
73        worklist.push_back(criterion);
74    }
75
76    while let Some(node_id) = worklist.pop_front() {
77        if let Some(preds) = predecessors.get(&node_id) {
78            for &pred in preds {
79                if !slice_nodes.contains(&pred) {
80                    slice_nodes.insert(pred);
81                    worklist.push_back(pred);
82                }
83            }
84        }
85    }
86
87    // Collect edges within slice
88    let mut edges = Vec::new();
89    for node in nodes {
90        if slice_nodes.contains(&node.id) {
91            for &succ in &node.successors {
92                if slice_nodes.contains(&succ) {
93                    edges.push((node.id, succ));
94                }
95            }
96        }
97    }
98
99    SliceResult {
100        nodes: slice_nodes,
101        edges,
102        direction: SliceDirection::Backward,
103        criterion,
104    }
105}
106
107/// Compute forward slice from a criterion node
108///
109/// The forward slice contains all nodes reachable from the criterion.
110/// This answers: "What code is affected by this point?"
111///
112/// # Arguments
113/// * `nodes` - CFG nodes
114/// * `criterion` - Slicing criterion (source node)
115///
116/// # Returns
117/// SliceResult with all nodes and edges in the forward slice
118pub fn forward_slice(nodes: &[CfgGraphNode], criterion: u64) -> SliceResult {
119    let node_map: HashMap<u64, &CfgGraphNode> = nodes.iter().map(|n| (n.id, n)).collect();
120
121    // BFS forward from criterion
122    let mut slice_nodes: HashSet<u64> = HashSet::new();
123    let mut worklist: VecDeque<u64> = VecDeque::new();
124
125    if node_map.contains_key(&criterion) {
126        slice_nodes.insert(criterion);
127        worklist.push_back(criterion);
128    }
129
130    while let Some(node_id) = worklist.pop_front() {
131        if let Some(node) = node_map.get(&node_id) {
132            for &succ in &node.successors {
133                if !slice_nodes.contains(&succ) {
134                    slice_nodes.insert(succ);
135                    worklist.push_back(succ);
136                }
137            }
138        }
139    }
140
141    // Collect edges within slice
142    let mut edges = Vec::new();
143    for node in nodes {
144        if slice_nodes.contains(&node.id) {
145            for &succ in &node.successors {
146                if slice_nodes.contains(&succ) {
147                    edges.push((node.id, succ));
148                }
149            }
150        }
151    }
152
153    SliceResult {
154        nodes: slice_nodes,
155        edges,
156        direction: SliceDirection::Forward,
157        criterion,
158    }
159}
160
161/// Compute full slice (backward + forward) from a criterion
162pub fn full_slice(nodes: &[CfgGraphNode], criterion: u64) -> SliceResult {
163    let backward = backward_slice(nodes, criterion);
164    let forward = forward_slice(nodes, criterion);
165
166    let mut nodes = backward.nodes;
167    nodes.extend(forward.nodes);
168
169    let mut edges = backward.edges;
170    edges.extend(forward.edges);
171
172    SliceResult {
173        nodes,
174        edges,
175        direction: SliceDirection::Backward, // Default
176        criterion,
177    }
178}
179
180/// Get slice size (number of nodes)
181pub fn slice_size(slice: &SliceResult) -> usize {
182    slice.nodes.len()
183}
184
185/// Check if a node is in the slice
186pub fn node_in_slice(node_id: u64, slice: &SliceResult) -> bool {
187    slice.nodes.contains(&node_id)
188}
189
190/// Compute slice coverage (percentage of nodes in slice)
191pub fn slice_coverage(slice: &SliceResult, total_nodes: usize) -> f32 {
192    if total_nodes == 0 {
193        return 0.0;
194    }
195    (slice.nodes.len() as f32) / (total_nodes as f32) * 100.0
196}
197
198#[cfg(test)]
199mod tests {
200    use super::*;
201
202    #[test]
203    fn test_backward_slice_linear() {
204        // Linear: 0 -> 1 -> 2 -> 3
205        let nodes = vec![
206            CfgGraphNode {
207                id: 0,
208                x: 0.0,
209                y: 0.0,
210                z: 0.0,
211                successors: vec![1],
212            },
213            CfgGraphNode {
214                id: 1,
215                x: 1.0,
216                y: 0.0,
217                z: 0.0,
218                successors: vec![2],
219            },
220            CfgGraphNode {
221                id: 2,
222                x: 2.0,
223                y: 0.0,
224                z: 0.0,
225                successors: vec![3],
226            },
227            CfgGraphNode {
228                id: 3,
229                x: 3.0,
230                y: 0.0,
231                z: 0.0,
232                successors: vec![],
233            },
234        ];
235
236        // Backward slice from node 3 should include all nodes
237        let slice = backward_slice(&nodes, 3);
238        assert_eq!(slice.nodes.len(), 4);
239        assert!(slice.nodes.contains(&0));
240        assert!(slice.nodes.contains(&3));
241        assert_eq!(slice.direction, SliceDirection::Backward);
242    }
243
244    #[test]
245    fn test_backward_slice_partial() {
246        // Linear: 0 -> 1 -> 2 -> 3
247        let nodes = vec![
248            CfgGraphNode {
249                id: 0,
250                x: 0.0,
251                y: 0.0,
252                z: 0.0,
253                successors: vec![1],
254            },
255            CfgGraphNode {
256                id: 1,
257                x: 1.0,
258                y: 0.0,
259                z: 0.0,
260                successors: vec![2],
261            },
262            CfgGraphNode {
263                id: 2,
264                x: 2.0,
265                y: 0.0,
266                z: 0.0,
267                successors: vec![3],
268            },
269            CfgGraphNode {
270                id: 3,
271                x: 3.0,
272                y: 0.0,
273                z: 0.0,
274                successors: vec![],
275            },
276        ];
277
278        // Backward slice from node 1 should include 0 and 1
279        let slice = backward_slice(&nodes, 1);
280        assert_eq!(slice.nodes.len(), 2);
281        assert!(slice.nodes.contains(&0));
282        assert!(slice.nodes.contains(&1));
283    }
284
285    #[test]
286    fn test_forward_slice_linear() {
287        // Linear: 0 -> 1 -> 2 -> 3
288        let nodes = vec![
289            CfgGraphNode {
290                id: 0,
291                x: 0.0,
292                y: 0.0,
293                z: 0.0,
294                successors: vec![1],
295            },
296            CfgGraphNode {
297                id: 1,
298                x: 1.0,
299                y: 0.0,
300                z: 0.0,
301                successors: vec![2],
302            },
303            CfgGraphNode {
304                id: 2,
305                x: 2.0,
306                y: 0.0,
307                z: 0.0,
308                successors: vec![3],
309            },
310            CfgGraphNode {
311                id: 3,
312                x: 3.0,
313                y: 0.0,
314                z: 0.0,
315                successors: vec![],
316            },
317        ];
318
319        // Forward slice from node 0 should include all nodes
320        let slice = forward_slice(&nodes, 0);
321        assert_eq!(slice.nodes.len(), 4);
322        assert!(slice.nodes.contains(&0));
323        assert!(slice.nodes.contains(&3));
324        assert_eq!(slice.direction, SliceDirection::Forward);
325    }
326
327    #[test]
328    fn test_forward_slice_partial() {
329        // Linear: 0 -> 1 -> 2 -> 3
330        let nodes = vec![
331            CfgGraphNode {
332                id: 0,
333                x: 0.0,
334                y: 0.0,
335                z: 0.0,
336                successors: vec![1],
337            },
338            CfgGraphNode {
339                id: 1,
340                x: 1.0,
341                y: 0.0,
342                z: 0.0,
343                successors: vec![2],
344            },
345            CfgGraphNode {
346                id: 2,
347                x: 2.0,
348                y: 0.0,
349                z: 0.0,
350                successors: vec![3],
351            },
352            CfgGraphNode {
353                id: 3,
354                x: 3.0,
355                y: 0.0,
356                z: 0.0,
357                successors: vec![],
358            },
359        ];
360
361        // Forward slice from node 2 should include 2 and 3
362        let slice = forward_slice(&nodes, 2);
363        assert_eq!(slice.nodes.len(), 2);
364        assert!(slice.nodes.contains(&2));
365        assert!(slice.nodes.contains(&3));
366    }
367
368    #[test]
369    fn test_slice_coverage() {
370        let nodes = vec![
371            CfgGraphNode {
372                id: 0,
373                x: 0.0,
374                y: 0.0,
375                z: 0.0,
376                successors: vec![1],
377            },
378            CfgGraphNode {
379                id: 1,
380                x: 1.0,
381                y: 0.0,
382                z: 0.0,
383                successors: vec![],
384            },
385        ];
386
387        let slice = backward_slice(&nodes, 1);
388        let coverage = slice_coverage(&slice, 2);
389        assert!((coverage - 100.0).abs() < 0.001);
390    }
391
392    #[test]
393    fn test_node_in_slice() {
394        let nodes = vec![
395            CfgGraphNode {
396                id: 0,
397                x: 0.0,
398                y: 0.0,
399                z: 0.0,
400                successors: vec![1],
401            },
402            CfgGraphNode {
403                id: 1,
404                x: 1.0,
405                y: 0.0,
406                z: 0.0,
407                successors: vec![],
408            },
409        ];
410
411        let slice = backward_slice(&nodes, 1);
412        assert!(node_in_slice(0, &slice));
413        assert!(node_in_slice(1, &slice));
414    }
415
416    #[test]
417    fn test_backward_slice_branching() {
418        // Diamond: 0 -> 1 -> 3, 0 -> 2 -> 3
419        let nodes = vec![
420            CfgGraphNode {
421                id: 0,
422                x: 0.0,
423                y: 0.0,
424                z: 0.0,
425                successors: vec![1, 2],
426            },
427            CfgGraphNode {
428                id: 1,
429                x: 1.0,
430                y: 0.0,
431                z: 0.0,
432                successors: vec![3],
433            },
434            CfgGraphNode {
435                id: 2,
436                x: 2.0,
437                y: 0.0,
438                z: 0.0,
439                successors: vec![3],
440            },
441            CfgGraphNode {
442                id: 3,
443                x: 3.0,
444                y: 0.0,
445                z: 0.0,
446                successors: vec![],
447            },
448        ];
449
450        // Backward slice from node 3 should include all nodes
451        let slice = backward_slice(&nodes, 3);
452        assert_eq!(slice.nodes.len(), 4);
453    }
454
455    #[test]
456    fn test_slice_edges() {
457        let nodes = vec![
458            CfgGraphNode {
459                id: 0,
460                x: 0.0,
461                y: 0.0,
462                z: 0.0,
463                successors: vec![1],
464            },
465            CfgGraphNode {
466                id: 1,
467                x: 1.0,
468                y: 0.0,
469                z: 0.0,
470                successors: vec![2],
471            },
472            CfgGraphNode {
473                id: 2,
474                x: 2.0,
475                y: 0.0,
476                z: 0.0,
477                successors: vec![],
478            },
479        ];
480
481        let slice = backward_slice(&nodes, 2);
482        assert_eq!(slice.edges.len(), 2);
483        assert!(slice.edges.contains(&(0, 1)));
484        assert!(slice.edges.contains(&(1, 2)));
485    }
486}