use std::collections::VecDeque;
use ahash::AHashSet;
use crate::errors::SqliteGraphError;
use crate::graph::SqliteGraph;
use crate::progress::ProgressCallback;
use super::control_dependence::ControlDependenceResult;
use super::reachability::{reachable_from, reverse_reachable_from};
#[derive(Debug, Clone)]
pub struct SliceResult {
pub criterion: i64,
pub slice_nodes: AHashSet<i64>,
pub control_nodes: AHashSet<i64>,
pub data_nodes: AHashSet<i64>,
pub size: usize,
}
impl SliceResult {
pub fn contains(&self, node: i64) -> bool {
self.slice_nodes.contains(&node)
}
pub fn sorted_nodes(&self) -> Vec<i64> {
let mut nodes: Vec<i64> = self.slice_nodes.iter().copied().collect();
nodes.sort();
nodes
}
pub fn sorted_control_nodes(&self) -> Vec<i64> {
let mut nodes: Vec<i64> = self.control_nodes.iter().copied().collect();
nodes.sort();
nodes
}
pub fn sorted_data_nodes(&self) -> Vec<i64> {
let mut nodes: Vec<i64> = self.data_nodes.iter().copied().collect();
nodes.sort();
nodes
}
}
pub fn backward_slice(
graph: &SqliteGraph,
cdg_result: &ControlDependenceResult,
target: i64,
) -> Result<SliceResult, SqliteGraphError> {
let mut slice_nodes = AHashSet::new();
let mut control_nodes = AHashSet::new();
let mut data_nodes = AHashSet::new();
slice_nodes.insert(target);
let mut queue = VecDeque::new();
let mut visited = AHashSet::new();
queue.push_back(target);
visited.insert(target);
while let Some(node) = queue.pop_front() {
if let Some(deps) = cdg_result.reverse_cdg.get(&node) {
for &dep in deps {
if visited.insert(dep) {
control_nodes.insert(dep);
slice_nodes.insert(dep);
queue.push_back(dep);
}
}
}
}
let data_reachable = reverse_reachable_from(graph, target)?;
for &node in &data_reachable {
data_nodes.insert(node);
slice_nodes.insert(node);
}
let size = slice_nodes.len();
Ok(SliceResult {
criterion: target,
slice_nodes,
control_nodes,
data_nodes,
size,
})
}
pub fn backward_slice_with_progress<F>(
graph: &SqliteGraph,
cdg_result: &ControlDependenceResult,
target: i64,
progress: &F,
) -> Result<SliceResult, SqliteGraphError>
where
F: ProgressCallback,
{
let mut slice_nodes = AHashSet::new();
let mut control_nodes = AHashSet::new();
let mut data_nodes = AHashSet::new();
slice_nodes.insert(target);
let mut queue = VecDeque::new();
let mut visited = AHashSet::new();
let mut nodes_processed = 0;
queue.push_back(target);
visited.insert(target);
while let Some(node) = queue.pop_front() {
nodes_processed += 1;
if nodes_processed % 10 == 0 {
progress.on_progress(
nodes_processed,
None,
&format!(
"Backward slice: visited {} nodes, {} control, {} data",
nodes_processed,
control_nodes.len(),
data_nodes.len()
),
);
}
if let Some(deps) = cdg_result.reverse_cdg.get(&node) {
for &dep in deps {
if visited.insert(dep) {
control_nodes.insert(dep);
slice_nodes.insert(dep);
queue.push_back(dep);
}
}
}
}
let data_reachable = reverse_reachable_from(graph, target)?;
for &node in &data_reachable {
data_nodes.insert(node);
slice_nodes.insert(node);
}
let size = slice_nodes.len();
progress.on_complete();
Ok(SliceResult {
criterion: target,
slice_nodes,
control_nodes,
data_nodes,
size,
})
}
pub fn forward_slice(
graph: &SqliteGraph,
cdg_result: &ControlDependenceResult,
source: i64,
) -> Result<SliceResult, SqliteGraphError> {
let mut slice_nodes = AHashSet::new();
let mut control_nodes = AHashSet::new();
let mut data_nodes = AHashSet::new();
slice_nodes.insert(source);
let mut queue = VecDeque::new();
let mut visited = AHashSet::new();
queue.push_back(source);
visited.insert(source);
while let Some(node) = queue.pop_front() {
if let Some(controlled) = cdg_result.cdg.get(&node) {
for &controlled_node in controlled {
if visited.insert(controlled_node) {
control_nodes.insert(controlled_node);
slice_nodes.insert(controlled_node);
queue.push_back(controlled_node);
}
}
}
}
let data_affected = reachable_from(graph, source)?;
for &node in &data_affected {
data_nodes.insert(node);
slice_nodes.insert(node);
}
let size = slice_nodes.len();
Ok(SliceResult {
criterion: source,
slice_nodes,
control_nodes,
data_nodes,
size,
})
}
pub fn forward_slice_with_progress<F>(
graph: &SqliteGraph,
cdg_result: &ControlDependenceResult,
source: i64,
progress: &F,
) -> Result<SliceResult, SqliteGraphError>
where
F: ProgressCallback,
{
let mut slice_nodes = AHashSet::new();
let mut control_nodes = AHashSet::new();
let mut data_nodes = AHashSet::new();
slice_nodes.insert(source);
let mut queue = VecDeque::new();
let mut visited = AHashSet::new();
let mut nodes_processed = 0;
queue.push_back(source);
visited.insert(source);
while let Some(node) = queue.pop_front() {
nodes_processed += 1;
if nodes_processed % 10 == 0 {
progress.on_progress(
nodes_processed,
None,
&format!(
"Forward slice: visited {} nodes, {} control, {} data",
nodes_processed,
control_nodes.len(),
data_nodes.len()
),
);
}
if let Some(controlled) = cdg_result.cdg.get(&node) {
for &controlled_node in controlled {
if visited.insert(controlled_node) {
control_nodes.insert(controlled_node);
slice_nodes.insert(controlled_node);
queue.push_back(controlled_node);
}
}
}
}
let data_affected = reachable_from(graph, source)?;
for &node in &data_affected {
data_nodes.insert(node);
slice_nodes.insert(node);
}
let size = slice_nodes.len();
progress.on_complete();
Ok(SliceResult {
criterion: source,
slice_nodes,
control_nodes,
data_nodes,
size,
})
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{GraphEdge, GraphEntity};
fn create_linear_chain() -> SqliteGraph {
let graph = SqliteGraph::open_in_memory().expect("Failed to create graph");
for i in 0..4 {
let entity = GraphEntity {
id: 0,
kind: "node".to_string(),
name: format!("node_{}", i),
file_path: Some(format!("node_{}.rs", i)),
data: serde_json::json!({"index": i}),
};
graph
.insert_entity(&entity)
.expect("Failed to insert entity");
}
let entity_ids: Vec<i64> = graph.list_entity_ids().expect("Failed to get IDs");
for i in 0..entity_ids.len().saturating_sub(1) {
let edge = GraphEdge {
id: 0,
from_id: entity_ids[i],
to_id: entity_ids[i + 1],
edge_type: "next".to_string(),
data: serde_json::json!({}),
};
graph.insert_edge(&edge).expect("Failed to insert edge");
}
graph
}
fn create_if_then_else_cfg() -> SqliteGraph {
let graph = SqliteGraph::open_in_memory().expect("Failed to create graph");
for i in 0..4 {
let entity = GraphEntity {
id: 0,
kind: "node".to_string(),
name: format!("node_{}", i),
file_path: Some(format!("node_{}.rs", i)),
data: serde_json::json!({"index": i}),
};
graph
.insert_entity(&entity)
.expect("Failed to insert entity");
}
let entity_ids: Vec<i64> = graph.list_entity_ids().expect("Failed to get IDs");
let edges = vec![(0, 1), (0, 2), (1, 3), (2, 3)];
for (from_idx, to_idx) in edges {
let edge = GraphEdge {
id: 0,
from_id: entity_ids[from_idx],
to_id: entity_ids[to_idx],
edge_type: "next".to_string(),
data: serde_json::json!({}),
};
graph.insert_edge(&edge).expect("Failed to insert edge");
}
graph
}
fn create_nested_cfg() -> SqliteGraph {
let graph = SqliteGraph::open_in_memory().expect("Failed to create graph");
for i in 0..6 {
let entity = GraphEntity {
id: 0,
kind: "node".to_string(),
name: format!("node_{}", i),
file_path: Some(format!("node_{}.rs", i)),
data: serde_json::json!({"index": i}),
};
graph
.insert_entity(&entity)
.expect("Failed to insert entity");
}
let entity_ids: Vec<i64> = graph.list_entity_ids().expect("Failed to get IDs");
let edges = vec![(0, 1), (0, 5), (1, 2), (1, 3), (2, 4), (3, 4), (4, 5)];
for (from_idx, to_idx) in edges {
let edge = GraphEdge {
id: 0,
from_id: entity_ids[from_idx],
to_id: entity_ids[to_idx],
edge_type: "next".to_string(),
data: serde_json::json!({}),
};
graph.insert_edge(&edge).expect("Failed to insert edge");
}
graph
}
#[test]
fn test_backward_slice_linear() {
let graph = create_linear_chain();
let entity_ids: Vec<i64> = graph.list_entity_ids().expect("Failed to get IDs");
let cdg = super::super::control_dependence::control_dependence_from_exit(&graph)
.expect("Failed to compute CDG");
let result =
backward_slice(&graph, &cdg, entity_ids[3]).expect("Failed to compute backward slice");
assert_eq!(result.size, 4, "Expected 4 nodes in slice");
assert_eq!(
result.criterion, entity_ids[3],
"Criterion should be target"
);
assert!(
result.data_nodes.len() + result.control_nodes.len() >= 3,
"Should have nodes in slice"
);
assert!(result.contains(entity_ids[3]), "Target should be in slice");
}
#[test]
fn test_backward_slice_if_then_else() {
let graph = create_if_then_else_cfg();
let entity_ids: Vec<i64> = graph.list_entity_ids().expect("Failed to get IDs");
let cdg = super::super::control_dependence::control_dependence_from_exit(&graph)
.expect("Failed to compute CDG");
let result =
backward_slice(&graph, &cdg, entity_ids[3]).expect("Failed to compute backward slice");
assert_eq!(result.size, 4, "Expected 4 nodes in slice");
assert!(
result.control_nodes.contains(&entity_ids[0]),
"Node 0 should be in control_nodes (controls merge point)"
);
}
#[test]
fn test_backward_slice_self_inclusion() {
let graph = create_linear_chain();
let entity_ids: Vec<i64> = graph.list_entity_ids().expect("Failed to get IDs");
let cdg = super::super::control_dependence::control_dependence_from_exit(&graph)
.expect("Failed to compute CDG");
let result =
backward_slice(&graph, &cdg, entity_ids[1]).expect("Failed to compute backward slice");
assert_eq!(
result.criterion, entity_ids[1],
"Criterion should be target"
);
assert!(result.contains(entity_ids[1]), "Target should be in slice");
}
#[test]
fn test_backward_slice_control_data_separation() {
let graph = create_if_then_else_cfg();
let entity_ids: Vec<i64> = graph.list_entity_ids().expect("Failed to get IDs");
let cdg = super::super::control_dependence::control_dependence_from_exit(&graph)
.expect("Failed to compute CDG");
let result =
backward_slice(&graph, &cdg, entity_ids[3]).expect("Failed to compute backward slice");
assert!(
!result.control_nodes.is_empty(),
"Control nodes should be non-empty"
);
assert!(
!result.data_nodes.is_empty(),
"Data nodes should be non-empty"
);
let expected_union: AHashSet<i64> = result
.control_nodes
.union(&result.data_nodes)
.copied()
.collect();
assert_eq!(
result.slice_nodes, expected_union,
"Slice nodes should be union of control + data"
);
}
#[test]
fn test_backward_slice_empty_graph() {
let graph = SqliteGraph::open_in_memory().expect("Failed to create graph");
let cdg = super::super::control_dependence::control_dependence_from_exit(&graph)
.expect("Failed to compute CDG");
let result = backward_slice(&graph, &cdg, 999).expect("Failed to compute backward slice");
assert_eq!(result.size, 1, "Empty graph should have minimal slice");
assert_eq!(result.criterion, 999, "Criterion should be target");
}
#[test]
fn test_forward_slice_linear() {
let graph = create_linear_chain();
let entity_ids: Vec<i64> = graph.list_entity_ids().expect("Failed to get IDs");
let cdg = super::super::control_dependence::control_dependence_from_exit(&graph)
.expect("Failed to compute CDG");
let result =
forward_slice(&graph, &cdg, entity_ids[0]).expect("Failed to compute forward slice");
assert_eq!(result.size, 4, "Expected 4 nodes in slice");
assert_eq!(
result.criterion, entity_ids[0],
"Criterion should be source"
);
assert!(
result.data_nodes.len() + result.control_nodes.len() >= 3,
"Should have nodes in slice"
);
}
#[test]
fn test_forward_slice_if_then_else() {
let graph = create_if_then_else_cfg();
let entity_ids: Vec<i64> = graph.list_entity_ids().expect("Failed to get IDs");
let cdg = super::super::control_dependence::control_dependence_from_exit(&graph)
.expect("Failed to compute CDG");
let result =
forward_slice(&graph, &cdg, entity_ids[0]).expect("Failed to compute forward slice");
assert_eq!(result.size, 4, "Expected 4 nodes in slice");
assert!(
result.control_nodes.contains(&entity_ids[3]),
"Node 3 should be in control_nodes (controlled by branch)"
);
}
#[test]
fn test_forward_slice_self_inclusion() {
let graph = create_linear_chain();
let entity_ids: Vec<i64> = graph.list_entity_ids().expect("Failed to get IDs");
let cdg = super::super::control_dependence::control_dependence_from_exit(&graph)
.expect("Failed to compute CDG");
let result =
forward_slice(&graph, &cdg, entity_ids[1]).expect("Failed to compute forward slice");
assert_eq!(
result.criterion, entity_ids[1],
"Criterion should be source"
);
assert!(result.contains(entity_ids[1]), "Source should be in slice");
}
#[test]
fn test_forward_slice_control_data_separation() {
let graph = create_if_then_else_cfg();
let entity_ids: Vec<i64> = graph.list_entity_ids().expect("Failed to get IDs");
let cdg = super::super::control_dependence::control_dependence_from_exit(&graph)
.expect("Failed to compute CDG");
let result =
forward_slice(&graph, &cdg, entity_ids[0]).expect("Failed to compute forward slice");
assert!(
!result.control_nodes.is_empty(),
"Control nodes should be non-empty"
);
assert!(
!result.data_nodes.is_empty(),
"Data nodes should be non-empty"
);
let expected_union: AHashSet<i64> = result
.control_nodes
.union(&result.data_nodes)
.copied()
.collect();
assert_eq!(
result.slice_nodes, expected_union,
"Slice nodes should be union of control + data"
);
}
#[test]
fn test_slice_result_contains() {
let graph = create_linear_chain();
let entity_ids: Vec<i64> = graph.list_entity_ids().expect("Failed to get IDs");
let cdg = super::super::control_dependence::control_dependence_from_exit(&graph)
.expect("Failed to compute CDG");
let result =
forward_slice(&graph, &cdg, entity_ids[0]).expect("Failed to compute forward slice");
for &node_id in &entity_ids {
assert!(
result.contains(node_id),
"Node {} should be in slice",
node_id
);
}
assert!(
!result.contains(9999),
"Non-existent node should not be in slice"
);
}
#[test]
fn test_slice_result_sorted_nodes() {
let graph = create_if_then_else_cfg();
let entity_ids: Vec<i64> = graph.list_entity_ids().expect("Failed to get IDs");
let cdg = super::super::control_dependence::control_dependence_from_exit(&graph)
.expect("Failed to compute CDG");
let result =
forward_slice(&graph, &cdg, entity_ids[0]).expect("Failed to compute forward slice");
let sorted = result.sorted_nodes();
for i in 1..sorted.len() {
assert!(
sorted[i - 1] <= sorted[i],
"sorted_nodes should be in ascending order"
);
}
assert_eq!(
sorted.len(),
result.size,
"All slice nodes should be in sorted output"
);
}
#[test]
fn test_slice_result_sorted_control_nodes() {
let graph = create_if_then_else_cfg();
let entity_ids: Vec<i64> = graph.list_entity_ids().expect("Failed to get IDs");
let cdg = super::super::control_dependence::control_dependence_from_exit(&graph)
.expect("Failed to compute CDG");
let result =
forward_slice(&graph, &cdg, entity_ids[0]).expect("Failed to compute forward slice");
let sorted = result.sorted_control_nodes();
for i in 1..sorted.len() {
assert!(
sorted[i - 1] <= sorted[i],
"sorted_control_nodes should be in ascending order"
);
}
assert_eq!(sorted.len(), result.control_nodes.len());
}
#[test]
fn test_slice_result_sorted_data_nodes() {
let graph = create_if_then_else_cfg();
let entity_ids: Vec<i64> = graph.list_entity_ids().expect("Failed to get IDs");
let cdg = super::super::control_dependence::control_dependence_from_exit(&graph)
.expect("Failed to compute CDG");
let result =
forward_slice(&graph, &cdg, entity_ids[0]).expect("Failed to compute forward slice");
let sorted = result.sorted_data_nodes();
for i in 1..sorted.len() {
assert!(
sorted[i - 1] <= sorted[i],
"sorted_data_nodes should be in ascending order"
);
}
assert_eq!(sorted.len(), result.data_nodes.len());
}
#[test]
fn test_backward_slice_nested_cfg() {
let graph = create_nested_cfg();
let entity_ids: Vec<i64> = graph.list_entity_ids().expect("Failed to get IDs");
let cdg = super::super::control_dependence::control_dependence_from_exit(&graph)
.expect("Failed to compute CDG");
let result =
backward_slice(&graph, &cdg, entity_ids[4]).expect("Failed to compute backward slice");
assert!(
!result.control_nodes.is_empty(),
"Nested CFG should have control dependencies"
);
assert!(
result.size >= 4,
"Should have at least 4 nodes in slice, got {}",
result.size
);
}
#[test]
fn test_forward_slice_nested_cfg() {
let graph = create_nested_cfg();
let entity_ids: Vec<i64> = graph.list_entity_ids().expect("Failed to get IDs");
let cdg = super::super::control_dependence::control_dependence_from_exit(&graph)
.expect("Failed to compute CDG");
let result =
forward_slice(&graph, &cdg, entity_ids[0]).expect("Failed to compute forward slice");
assert_eq!(result.size, 6, "All 6 nodes should be in slice");
assert!(
!result.control_nodes.is_empty(),
"Should have control nodes from branching"
);
}
#[test]
fn test_backward_slice_with_progress() {
use crate::progress::NoProgress;
let graph = create_linear_chain();
let entity_ids: Vec<i64> = graph.list_entity_ids().expect("Failed to get IDs");
let cdg = super::super::control_dependence::control_dependence_from_exit(&graph)
.expect("Failed to compute CDG");
let progress = NoProgress;
let result_with =
backward_slice_with_progress(&graph, &cdg, entity_ids[3], &progress).expect("Failed");
let result_without = backward_slice(&graph, &cdg, entity_ids[3]).expect("Failed");
assert_eq!(
result_with.size, result_without.size,
"Progress and non-progress results should match"
);
for &node in &result_with.slice_nodes {
assert!(
result_without.contains(node),
"Progress result contains node not in non-progress result"
);
}
}
#[test]
fn test_forward_slice_with_progress() {
use crate::progress::NoProgress;
let graph = create_linear_chain();
let entity_ids: Vec<i64> = graph.list_entity_ids().expect("Failed to get IDs");
let cdg = super::super::control_dependence::control_dependence_from_exit(&graph)
.expect("Failed to compute CDG");
let progress = NoProgress;
let result_with =
forward_slice_with_progress(&graph, &cdg, entity_ids[0], &progress).expect("Failed");
let result_without = forward_slice(&graph, &cdg, entity_ids[0]).expect("Failed");
assert_eq!(
result_with.size, result_without.size,
"Progress and non-progress results should match"
);
for &node in &result_with.slice_nodes {
assert!(
result_without.contains(node),
"Progress result contains node not in non-progress result"
);
}
}
#[test]
fn test_backward_forward_symmetry() {
let graph = create_linear_chain();
let entity_ids: Vec<i64> = graph.list_entity_ids().expect("Failed to get IDs");
let cdg = super::super::control_dependence::control_dependence_from_exit(&graph)
.expect("Failed to compute CDG");
let backward = backward_slice(&graph, &cdg, entity_ids[3]).expect("Failed");
let forward = forward_slice(&graph, &cdg, entity_ids[0]).expect("Failed");
assert_eq!(backward.size, 4, "Backward slice should contain all nodes");
assert_eq!(forward.size, 4, "Forward slice should contain all nodes");
assert_eq!(
backward.slice_nodes, forward.slice_nodes,
"In linear chain, backward and forward slices should match"
);
}
}