use super::*;
use crate::decompiler::cfg::ssa::dominance::DominanceInfo;
use crate::decompiler::cfg::ssa::variable::{PhiNode, SsaVariable};
use crate::decompiler::cfg::{BlockId, Cfg};
use crate::decompiler::ir::{BinOp, Literal, UnaryOp};
#[test]
fn test_ssa_form_creation() {
let cfg = Cfg::new();
let dominance = DominanceInfo::new();
let ssa = SsaForm::new(cfg, dominance);
assert_eq!(ssa.block_count(), 0);
assert!(ssa.definitions.is_empty());
assert!(ssa.uses.is_empty());
}
#[test]
fn test_ssa_block_additions() {
let mut block = SsaBlock::new();
let phi = PhiNode::new(SsaVariable::initial("x".to_string()));
block.add_phi(phi);
let stmt = SsaStmt::assign(
SsaVariable::new("y".to_string(), 0),
SsaExpr::lit(Literal::Int(42)),
);
block.add_stmt(stmt);
assert_eq!(block.phi_count(), 1);
assert_eq!(block.stmt_count(), 1);
assert!(!block.is_empty());
}
#[test]
fn test_dominance_info_empty() {
let info = DominanceInfo::new();
assert!(info.idom(BlockId(0)).is_none());
assert!(info.children(BlockId(0)).is_empty());
assert!(info.dominance_frontier_vec(BlockId(0)).is_empty());
}
#[test]
fn test_ssa_expr_constructors() {
let var = SsaVariable::initial("x".to_string());
let expr = SsaExpr::var(var);
assert!(matches!(expr, SsaExpr::Variable(_)));
let lit = SsaExpr::lit(Literal::Int(42));
assert!(matches!(lit, SsaExpr::Literal(_)));
let binary = SsaExpr::binary(
BinOp::Add,
SsaExpr::lit(Literal::Int(1)),
SsaExpr::lit(Literal::Int(2)),
);
assert!(matches!(binary, SsaExpr::Binary { .. }));
let call = SsaExpr::call("foo".to_string(), vec![]);
assert!(matches!(call, SsaExpr::Call { .. }));
}
#[test]
fn test_use_site() {
let site = UseSite::new(BlockId(5), 10);
assert_eq!(site.block, BlockId(5));
assert_eq!(site.stmt_index, 10);
}
#[test]
fn test_ssa_expr_display() {
let var = SsaVariable::initial("x".to_string());
assert_eq!(format!("{}", SsaExpr::var(var.clone())), "x");
let lit = SsaExpr::lit(Literal::Int(42));
assert_eq!(format!("{}", lit), "42");
let binary = SsaExpr::binary(
BinOp::Add,
SsaExpr::var(var.clone()),
SsaExpr::lit(Literal::Int(10)),
);
assert_eq!(format!("{}", binary), "(x + 10)");
let unary = SsaExpr::unary(UnaryOp::Neg, SsaExpr::var(var));
assert_eq!(format!("{}", unary), "-(x)");
let var2 = SsaVariable::initial("x".to_string());
let call = SsaExpr::call("foo".to_string(), vec![SsaExpr::var(var2)]);
assert_eq!(format!("{}", call), "foo(x)");
}
#[test]
fn test_ssa_stmt_display() {
let var = SsaVariable::new("result".to_string(), 0);
let stmt = SsaStmt::assign(var, SsaExpr::lit(Literal::Int(42)));
assert_eq!(format!("{}", stmt), "result = 42;");
}
#[test]
fn test_ssa_block_display() {
let mut block = SsaBlock::new();
let phi = PhiNode::new(SsaVariable::initial("x".to_string()));
block.add_phi(phi);
let stmt = SsaStmt::assign(
SsaVariable::new("y".to_string(), 0),
SsaExpr::lit(Literal::Int(42)),
);
block.add_stmt(stmt);
let display = format!("{}", block);
assert!(display.contains("φ"));
assert!(display.contains("y = 42;"));
}
#[test]
fn test_ssa_form_render() {
let cfg = Cfg::new();
let dominance = DominanceInfo::new();
let mut ssa = SsaForm::new(cfg, dominance);
let mut block = SsaBlock::new();
block.add_stmt(SsaStmt::assign(
SsaVariable::new("x".to_string(), 0),
SsaExpr::lit(Literal::Int(42)),
));
ssa.add_block(BlockId::ENTRY, block);
let rendered = ssa.render();
assert!(rendered.contains("SSA Form"));
assert!(rendered.contains("block"));
assert!(rendered.contains("x = 42;"));
}
#[test]
fn test_ssa_stats() {
let cfg = Cfg::new();
let dominance = DominanceInfo::new();
let mut ssa = SsaForm::new(cfg, dominance);
let mut block = SsaBlock::new();
let phi = PhiNode::new(SsaVariable::initial("x".to_string()));
block.add_phi(phi);
let stmt = SsaStmt::assign(
SsaVariable::new("y".to_string(), 0),
SsaExpr::lit(Literal::Int(42)),
);
block.add_stmt(stmt);
ssa.add_block(BlockId::ENTRY, block);
ssa.add_definition(SsaVariable::initial("y".to_string()), BlockId::ENTRY);
let stats = ssa.stats();
assert_eq!(stats.block_count, 1);
assert_eq!(stats.total_phi_nodes, 1);
assert_eq!(stats.total_statements, 1);
assert_eq!(stats.total_variables, 1);
}
#[test]
fn test_ssa_expr_complex() {
let arr = SsaExpr::Array(vec![
SsaExpr::lit(Literal::Int(1)),
SsaExpr::lit(Literal::Int(2)),
]);
assert_eq!(format!("{}", arr), "[1, 2]");
let map = SsaExpr::Map(vec![(
SsaExpr::lit(Literal::String("key".to_string())),
SsaExpr::lit(Literal::Int(1)),
)]);
assert!(format!("{}", map).contains("{"));
let ternary = SsaExpr::Ternary {
condition: Box::new(SsaExpr::lit(Literal::Bool(true))),
then_expr: Box::new(SsaExpr::lit(Literal::Int(1))),
else_expr: Box::new(SsaExpr::lit(Literal::Int(2))),
};
assert_eq!(format!("{}", ternary), "true ? 1 : 2");
}
#[test]
fn phi_placement_diamond_cfg() {
use super::super::dominance;
use crate::decompiler::cfg::{BasicBlock, EdgeKind, Terminator};
let mut cfg = Cfg::new();
let entry = BasicBlock::new(
BlockId(0),
0,
1,
0..1,
Terminator::Branch {
then_target: BlockId(1),
else_target: BlockId(2),
},
);
cfg.add_block(entry);
let left = BasicBlock::new(
BlockId(1),
1,
2,
1..2,
Terminator::Jump { target: BlockId(3) },
);
cfg.add_block(left);
cfg.add_edge(BlockId(0), BlockId(1), EdgeKind::ConditionalTrue);
let right = BasicBlock::new(
BlockId(2),
2,
3,
2..3,
Terminator::Jump { target: BlockId(3) },
);
cfg.add_block(right);
cfg.add_edge(BlockId(0), BlockId(2), EdgeKind::ConditionalFalse);
let exit = BasicBlock::new(BlockId(3), 3, 4, 3..4, Terminator::Return);
cfg.add_block(exit);
cfg.add_edge(BlockId(1), BlockId(3), EdgeKind::Unconditional);
cfg.add_edge(BlockId(2), BlockId(3), EdgeKind::Unconditional);
let dom = dominance::compute(&cfg);
assert_eq!(dom.dominance_frontier_vec(BlockId(1)), vec![BlockId(3)],);
assert_eq!(dom.dominance_frontier_vec(BlockId(2)), vec![BlockId(3)],);
let mut ssa = SsaForm::new(cfg, dom);
ssa.add_block(BlockId(0), SsaBlock::new());
let mut bb1 = SsaBlock::new();
let x0 = SsaVariable::new("x".to_string(), 0);
bb1.add_stmt(SsaStmt::assign(x0.clone(), SsaExpr::lit(Literal::Int(1))));
ssa.add_block(BlockId(1), bb1);
ssa.add_definition(x0, BlockId(1));
let mut bb2 = SsaBlock::new();
let x1 = SsaVariable::new("x".to_string(), 1);
bb2.add_stmt(SsaStmt::assign(x1.clone(), SsaExpr::lit(Literal::Int(2))));
ssa.add_block(BlockId(2), bb2);
ssa.add_definition(x1, BlockId(2));
let mut bb3 = SsaBlock::new();
let mut phi = PhiNode::new(SsaVariable::new("x".to_string(), 2));
phi.operands
.insert(BlockId(1), SsaVariable::new("x".to_string(), 0));
phi.operands
.insert(BlockId(2), SsaVariable::new("x".to_string(), 1));
bb3.add_phi(phi);
ssa.add_block(BlockId(3), bb3);
let merge_block = ssa.block(BlockId(3)).expect("BB3 must exist");
assert_eq!(merge_block.phi_count(), 1, "BB3 should have 1 phi node");
assert_eq!(
merge_block.phi_nodes[0].target.base, "x",
"phi target should be variable x"
);
assert_eq!(
merge_block.phi_nodes[0].operands.len(),
2,
"phi should have 2 operands (one per predecessor)"
);
assert_eq!(
ssa.block(BlockId(0)).unwrap().phi_count(),
0,
"entry should have no phi nodes"
);
assert_eq!(
ssa.block(BlockId(1)).unwrap().phi_count(),
0,
"BB1 should have no phi nodes"
);
assert_eq!(
ssa.block(BlockId(2)).unwrap().phi_count(),
0,
"BB2 should have no phi nodes"
);
}