use crate::cfg::reachability::find_unreachable;
use crate::cfg::{BasicBlock, BlockKind, Cfg, EdgeType, Terminator};
use crate::cli::responses::*;
use crate::cli::*;
use petgraph::graph::DiGraph;
fn create_cfg_with_unreachable() -> Cfg {
let mut g = DiGraph::new();
let b0 = g.add_node(BasicBlock {
id: 0,
db_id: None,
kind: BlockKind::Entry,
statements: vec!["let x = 1".to_string()],
terminator: Terminator::Goto { target: 1 },
source_location: None,
coord_x: 0,
coord_y: 0,
coord_z: 0,
});
let b1 = g.add_node(BasicBlock {
id: 1,
db_id: None,
kind: BlockKind::Normal,
statements: vec!["if x > 0".to_string()],
terminator: Terminator::SwitchInt {
targets: vec![2],
otherwise: 3,
},
source_location: None,
coord_x: 0,
coord_y: 0,
coord_z: 0,
});
let b2 = g.add_node(BasicBlock {
id: 2,
db_id: None,
kind: BlockKind::Exit,
statements: vec!["return true".to_string()],
terminator: Terminator::Return,
source_location: None,
coord_x: 0,
coord_y: 0,
coord_z: 0,
});
let b3 = g.add_node(BasicBlock {
id: 3,
db_id: None,
kind: BlockKind::Exit,
statements: vec!["return false".to_string()],
terminator: Terminator::Return,
source_location: None,
coord_x: 0,
coord_y: 0,
coord_z: 0,
});
let _b4 = g.add_node(BasicBlock {
id: 4,
db_id: None,
kind: BlockKind::Exit,
statements: vec!["unreachable code".to_string()],
terminator: Terminator::Unreachable,
source_location: None,
coord_x: 0,
coord_y: 0,
coord_z: 0,
});
g.add_edge(b0, b1, EdgeType::Fallthrough);
g.add_edge(b1, b2, EdgeType::TrueBranch);
g.add_edge(b1, b3, EdgeType::FalseBranch);
g
}
#[test]
fn test_unreachable_detects_dead_code() {
let cfg = create_cfg_with_unreachable();
let unreachable_indices = find_unreachable(&cfg);
assert_eq!(
unreachable_indices.len(),
1,
"Should find exactly 1 unreachable block"
);
let block_id = cfg.node_weight(unreachable_indices[0]).unwrap().id;
assert_eq!(block_id, 4, "Unreachable block should be block 4");
}
#[test]
fn test_unreachable_response_serialization() {
use crate::output::JsonResponse;
let response = UnreachableResponse {
uncalled_functions: None,
function: "test_func".to_string(),
total_functions: 1,
functions_with_unreachable: 1,
unreachable_count: 1,
blocks: vec![UnreachableBlock {
block_id: 4,
kind: "Exit".to_string(),
statements: vec!["unreachable code".to_string()],
terminator: "Unreachable".to_string(),
incoming_edges: vec![],
}],
};
let wrapper = JsonResponse::new(response);
let json = wrapper.to_json();
assert!(json.contains("\"function\":\"test_func\""));
assert!(json.contains("\"unreachable_count\":1"));
assert!(json.contains("\"block_id\":4"));
assert!(json.contains("\"kind\":\"Exit\""));
}
#[test]
fn test_unreachable_empty_response() {
use crate::output::JsonResponse;
let response = UnreachableResponse {
uncalled_functions: None,
function: "test_func".to_string(),
total_functions: 1,
functions_with_unreachable: 0,
unreachable_count: 0,
blocks: vec![],
};
let wrapper = JsonResponse::new(response);
let json = wrapper.to_json();
assert!(json.contains("\"unreachable_count\":0"));
assert!(json.contains("\"functions_with_unreachable\":0"));
}
#[test]
fn test_unreachable_block_fields() {
let block = UnreachableBlock {
block_id: 5,
kind: "Normal".to_string(),
statements: vec!["stmt1".to_string(), "stmt2".to_string()],
terminator: "Return".to_string(),
incoming_edges: vec![],
};
assert_eq!(block.block_id, 5);
assert_eq!(block.kind, "Normal");
assert_eq!(block.statements.len(), 2);
assert_eq!(block.terminator, "Return");
}
#[test]
fn test_unreachable_args_flags() {
let args_with = UnreachableArgs {
include_uncalled: false,
within_functions: true,
show_branches: true,
};
let args_without = UnreachableArgs {
include_uncalled: false,
within_functions: false,
show_branches: false,
};
assert!(args_with.within_functions);
assert!(args_with.show_branches);
assert!(!args_without.within_functions);
assert!(!args_without.show_branches);
}
#[test]
fn test_test_cfg_fully_reachable() {
let cfg = cmds::create_test_cfg();
let unreachable_indices = find_unreachable(&cfg);
assert_eq!(
unreachable_indices.len(),
0,
"Test CFG should have no unreachable blocks"
);
}
#[test]
fn test_unreachable_show_branches_with_edges() {
use crate::cfg::reachability::find_unreachable;
use petgraph::visit::EdgeRef;
let mut g = DiGraph::new();
let b0 = g.add_node(BasicBlock {
id: 0,
db_id: None,
kind: BlockKind::Entry,
statements: vec!["let x = 1".to_string()],
terminator: Terminator::Goto { target: 1 },
source_location: None,
coord_x: 0,
coord_y: 0,
coord_z: 0,
});
let b1 = g.add_node(BasicBlock {
id: 1,
db_id: None,
kind: BlockKind::Normal,
statements: vec!["if x > 0".to_string()],
terminator: Terminator::SwitchInt {
targets: vec![2],
otherwise: 3,
},
source_location: None,
coord_x: 0,
coord_y: 0,
coord_z: 0,
});
let b2 = g.add_node(BasicBlock {
id: 2,
db_id: None,
kind: BlockKind::Exit,
statements: vec!["return true".to_string()],
terminator: Terminator::Return,
source_location: None,
coord_x: 0,
coord_y: 0,
coord_z: 0,
});
let b3 = g.add_node(BasicBlock {
id: 3,
db_id: None,
kind: BlockKind::Normal,
statements: vec!["unreachable branch".to_string()],
terminator: Terminator::Goto { target: 4 },
source_location: None,
coord_x: 0,
coord_y: 0,
coord_z: 0,
});
let b4 = g.add_node(BasicBlock {
id: 4,
db_id: None,
kind: BlockKind::Exit,
statements: vec!["unreachable code".to_string()],
terminator: Terminator::Unreachable,
source_location: None,
coord_x: 0,
coord_y: 0,
coord_z: 0,
});
g.add_edge(b0, b1, EdgeType::Fallthrough);
g.add_edge(b1, b2, EdgeType::TrueBranch);
g.add_edge(b3, b4, EdgeType::Fallthrough);
let unreachable_indices = find_unreachable(&g);
let blocks: Vec<UnreachableBlock> = unreachable_indices
.iter()
.map(|&idx| {
let block = &g[idx];
let kind_str = format!("{:?}", block.kind);
let terminator_str = format!("{:?}", block.terminator);
let incoming_edges: Vec<IncomingEdge> = g
.edge_references()
.filter(|edge| edge.target() == idx)
.map(|edge| {
let source_block = &g[edge.source()];
let edge_type = g.edge_weight(edge.id()).unwrap();
IncomingEdge {
from_block: source_block.id,
edge_type: format!("{:?}", edge_type),
}
})
.collect();
UnreachableBlock {
block_id: block.id,
kind: kind_str,
statements: block.statements.clone(),
terminator: terminator_str,
incoming_edges,
}
})
.collect();
assert_eq!(blocks.len(), 2);
let block3 = blocks.iter().find(|b| b.block_id == 3).unwrap();
assert_eq!(block3.incoming_edges.len(), 0);
let block4 = blocks.iter().find(|b| b.block_id == 4).unwrap();
assert_eq!(block4.incoming_edges.len(), 1);
assert_eq!(block4.incoming_edges[0].from_block, 3);
assert_eq!(block4.incoming_edges[0].edge_type, "Fallthrough");
}
#[test]
fn test_unreachable_show_branches_json_output() {
use crate::cfg::reachability::find_unreachable;
use crate::output::JsonResponse;
use petgraph::visit::EdgeRef;
let mut g = DiGraph::new();
let b0 = g.add_node(BasicBlock {
id: 0,
db_id: None,
kind: BlockKind::Entry,
statements: vec!["let x = 1".to_string()],
terminator: Terminator::Goto { target: 1 },
source_location: None,
coord_x: 0,
coord_y: 0,
coord_z: 0,
});
let b1 = g.add_node(BasicBlock {
id: 1,
db_id: None,
kind: BlockKind::Normal,
statements: vec!["if x > 0".to_string()],
terminator: Terminator::SwitchInt {
targets: vec![2],
otherwise: 3,
},
source_location: None,
coord_x: 0,
coord_y: 0,
coord_z: 0,
});
let b2 = g.add_node(BasicBlock {
id: 2,
db_id: None,
kind: BlockKind::Exit,
statements: vec!["return true".to_string()],
terminator: Terminator::Return,
source_location: None,
coord_x: 0,
coord_y: 0,
coord_z: 0,
});
let b3 = g.add_node(BasicBlock {
id: 3,
db_id: None,
kind: BlockKind::Normal,
statements: vec!["unreachable branch".to_string()],
terminator: Terminator::Goto { target: 4 },
source_location: None,
coord_x: 0,
coord_y: 0,
coord_z: 0,
});
let b4 = g.add_node(BasicBlock {
id: 4,
db_id: None,
kind: BlockKind::Exit,
statements: vec!["unreachable code".to_string()],
terminator: Terminator::Unreachable,
source_location: None,
coord_x: 0,
coord_y: 0,
coord_z: 0,
});
g.add_edge(b0, b1, EdgeType::Fallthrough);
g.add_edge(b1, b2, EdgeType::TrueBranch);
g.add_edge(b3, b4, EdgeType::Fallthrough);
let unreachable_indices = find_unreachable(&g);
let blocks: Vec<UnreachableBlock> = unreachable_indices
.iter()
.map(|&idx| {
let block = &g[idx];
UnreachableBlock {
block_id: block.id,
kind: format!("{:?}", block.kind),
statements: block.statements.clone(),
terminator: format!("{:?}", block.terminator),
incoming_edges: g
.edge_references()
.filter(|edge| edge.target() == idx)
.map(|edge| {
let source_block = &g[edge.source()];
let edge_type = g.edge_weight(edge.id()).unwrap();
IncomingEdge {
from_block: source_block.id,
edge_type: format!("{:?}", edge_type),
}
})
.collect(),
}
})
.collect();
let response = UnreachableResponse {
function: "test".to_string(),
total_functions: 1,
functions_with_unreachable: 1,
unreachable_count: 2,
blocks,
uncalled_functions: None,
};
let wrapper = JsonResponse::new(response);
let json = wrapper.to_json();
assert!(json.contains("\"incoming_edges\""));
assert!(json.contains("\"from_block\":3"));
assert!(json.contains("\"edge_type\":\"Fallthrough\""));
}
#[test]
fn test_incoming_edge_serialization() {
let edge = IncomingEdge {
from_block: 5,
edge_type: "TrueBranch".to_string(),
};
let serialized = serde_json::to_string(&edge).unwrap();
assert!(serialized.contains("\"from_block\":5"));
assert!(serialized.contains("\"edge_type\":\"TrueBranch\""));
}