use std::collections::{HashMap, HashSet};
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum MockAbstractLocation {
Alloc {
line: u32,
},
Param {
name: String,
},
Unknown,
Field {
base: Box<MockAbstractLocation>,
field: String,
},
}
impl MockAbstractLocation {
pub fn alloc(line: u32) -> Self {
MockAbstractLocation::Alloc { line }
}
pub fn param(name: &str) -> Self {
MockAbstractLocation::Param {
name: name.to_string(),
}
}
pub fn unknown() -> Self {
MockAbstractLocation::Unknown
}
pub fn field(base: MockAbstractLocation, field: &str) -> Self {
MockAbstractLocation::Field {
base: Box::new(base),
field: field.to_string(),
}
}
pub fn format(&self) -> String {
match self {
MockAbstractLocation::Alloc { line } => format!("alloc_{}", line),
MockAbstractLocation::Param { name } => format!("param_{}", name),
MockAbstractLocation::Unknown => "unknown".to_string(),
MockAbstractLocation::Field { base, field } => format!("{}.{}", base.format(), field),
}
}
}
#[derive(Debug, Clone, Default)]
pub struct MockAliasInfo {
pub function_name: String,
pub may_alias: HashMap<String, HashSet<String>>,
pub must_alias: HashMap<String, HashSet<String>>,
pub points_to: HashMap<String, HashSet<String>>,
pub allocation_sites: HashMap<u32, String>,
}
impl MockAliasInfo {
pub fn new(function_name: &str) -> Self {
Self {
function_name: function_name.to_string(),
..Default::default()
}
}
pub fn may_alias_check(&self, a: &str, b: &str) -> bool {
if a == b {
return true;
}
if self.may_alias.get(a).is_some_and(|s| s.contains(b)) {
return true;
}
if self.may_alias.get(b).is_some_and(|s| s.contains(a)) {
return true;
}
let pts_a = self.points_to.get(a);
let pts_b = self.points_to.get(b);
match (pts_a, pts_b) {
(Some(a_set), Some(b_set)) => !a_set.is_disjoint(b_set),
_ => false,
}
}
pub fn must_alias_check(&self, a: &str, b: &str) -> bool {
if a == b {
return true;
}
if self.must_alias.get(a).is_some_and(|s| s.contains(b)) {
return true;
}
if self.must_alias.get(b).is_some_and(|s| s.contains(a)) {
return true;
}
false
}
pub fn get_points_to(&self, var: &str) -> HashSet<String> {
self.points_to.get(var).cloned().unwrap_or_default()
}
pub fn get_aliases(&self, var: &str) -> HashSet<String> {
self.may_alias.get(var).cloned().unwrap_or_default()
}
pub fn to_json_value(&self) -> serde_json::Value {
use serde_json::json;
let sorted_may_alias: HashMap<_, Vec<_>> = self
.may_alias
.iter()
.map(|(k, v)| {
let mut sorted: Vec<_> = v.iter().cloned().collect();
sorted.sort();
(k.clone(), sorted)
})
.collect();
let sorted_must_alias: HashMap<_, Vec<_>> = self
.must_alias
.iter()
.map(|(k, v)| {
let mut sorted: Vec<_> = v.iter().cloned().collect();
sorted.sort();
(k.clone(), sorted)
})
.collect();
let sorted_points_to: HashMap<_, Vec<_>> = self
.points_to
.iter()
.map(|(k, v)| {
let mut sorted: Vec<_> = v.iter().cloned().collect();
sorted.sort();
(k.clone(), sorted)
})
.collect();
json!({
"function": self.function_name,
"may_alias": sorted_may_alias,
"must_alias": sorted_must_alias,
"points_to": sorted_points_to,
"allocation_sites": self.allocation_sites,
})
}
}
#[test]
fn test_abstract_location_alloc() {
let loc = MockAbstractLocation::alloc(5);
assert_eq!(loc.format(), "alloc_5");
}
#[test]
fn test_abstract_location_param() {
let loc = MockAbstractLocation::param("x");
assert_eq!(loc.format(), "param_x");
}
#[test]
fn test_abstract_location_unknown() {
let loc = MockAbstractLocation::unknown();
assert_eq!(loc.format(), "unknown");
}
#[test]
fn test_abstract_location_field() {
let base = MockAbstractLocation::alloc(5);
let loc = MockAbstractLocation::field(base, "data");
assert_eq!(loc.format(), "alloc_5.data");
}
#[test]
fn test_abstract_location_nested_field() {
let base = MockAbstractLocation::alloc(5);
let field1 = MockAbstractLocation::field(base, "inner");
let field2 = MockAbstractLocation::field(field1, "value");
assert_eq!(field2.format(), "alloc_5.inner.value");
}
#[test]
fn test_alias_info_fields() {
let mut info = MockAliasInfo::new("test_func");
info.may_alias.insert(
"x".to_string(),
HashSet::from(["y".to_string(), "z".to_string()]),
);
info.must_alias
.insert("a".to_string(), HashSet::from(["b".to_string()]));
info.points_to.insert(
"x".to_string(),
HashSet::from(["alloc_1".to_string(), "param_0".to_string()]),
);
info.allocation_sites.insert(5, "alloc_5".to_string());
assert_eq!(info.may_alias.get("x").unwrap().len(), 2);
assert!(info.must_alias.get("a").unwrap().contains("b"));
assert!(info.points_to.get("x").unwrap().contains("alloc_1"));
assert_eq!(info.allocation_sites.get(&5), Some(&"alloc_5".to_string()));
assert_eq!(info.function_name, "test_func");
}
#[test]
fn test_alias_info_default_empty() {
let info = MockAliasInfo::default();
assert!(info.may_alias.is_empty());
assert!(info.must_alias.is_empty());
assert!(info.points_to.is_empty());
assert!(info.allocation_sites.is_empty());
assert!(info.function_name.is_empty());
}
#[test]
fn test_alias_info_may_alias_check_same_var() {
let info = MockAliasInfo::new("test");
assert!(info.may_alias_check("x", "x"));
assert!(info.may_alias_check("anything", "anything"));
}
#[test]
fn test_alias_info_may_alias_check_in_set() {
let mut info = MockAliasInfo::new("test");
info.may_alias.insert(
"x".to_string(),
HashSet::from(["y".to_string(), "z".to_string()]),
);
assert!(info.may_alias_check("x", "y"));
assert!(info.may_alias_check("x", "z"));
assert!(info.may_alias_check("y", "x")); }
#[test]
fn test_alias_info_may_alias_check_points_to_overlap() {
let mut info = MockAliasInfo::new("test");
info.points_to.insert(
"x".to_string(),
HashSet::from(["alloc_1".to_string(), "alloc_2".to_string()]),
);
info.points_to.insert(
"y".to_string(),
HashSet::from(["alloc_2".to_string(), "alloc_3".to_string()]),
);
info.points_to
.insert("z".to_string(), HashSet::from(["alloc_4".to_string()]));
assert!(info.may_alias_check("x", "y"));
assert!(!info.may_alias_check("x", "z"));
}
#[test]
fn test_alias_info_may_alias_check_no_overlap() {
let mut info = MockAliasInfo::new("test");
info.may_alias
.insert("x".to_string(), HashSet::from(["a".to_string()]));
info.points_to
.insert("x".to_string(), HashSet::from(["alloc_1".to_string()]));
info.points_to
.insert("y".to_string(), HashSet::from(["alloc_2".to_string()]));
assert!(!info.may_alias_check("x", "y"));
}
#[test]
fn test_alias_info_must_alias_check_same_var() {
let info = MockAliasInfo::new("test");
assert!(info.must_alias_check("x", "x"));
}
#[test]
fn test_alias_info_must_alias_check_in_set() {
let mut info = MockAliasInfo::new("test");
info.must_alias.insert(
"a".to_string(),
HashSet::from(["b".to_string(), "c".to_string()]),
);
assert!(info.must_alias_check("a", "b"));
assert!(info.must_alias_check("b", "a")); assert!(info.must_alias_check("a", "c"));
}
#[test]
fn test_alias_info_must_alias_check_not_in_set() {
let mut info = MockAliasInfo::new("test");
info.must_alias
.insert("a".to_string(), HashSet::from(["b".to_string()]));
info.may_alias.insert(
"a".to_string(),
HashSet::from(["b".to_string(), "c".to_string()]),
);
assert!(!info.must_alias_check("a", "c"));
}
#[test]
fn test_alias_info_get_points_to() {
let mut info = MockAliasInfo::new("test");
info.points_to.insert(
"x".to_string(),
HashSet::from(["alloc_1".to_string(), "param_0".to_string()]),
);
assert_eq!(
info.get_points_to("x"),
HashSet::from(["alloc_1".to_string(), "param_0".to_string()])
);
assert!(info.get_points_to("unknown").is_empty()); }
#[test]
fn test_alias_info_get_aliases() {
let mut info = MockAliasInfo::new("test");
info.may_alias.insert(
"x".to_string(),
HashSet::from(["y".to_string(), "z".to_string()]),
);
assert_eq!(
info.get_aliases("x"),
HashSet::from(["y".to_string(), "z".to_string()])
);
assert!(info.get_aliases("unknown").is_empty());
}
#[test]
fn test_alias_info_to_json_sorted() {
let mut info = MockAliasInfo::new("test");
info.may_alias.insert(
"x".to_string(),
HashSet::from(["z".to_string(), "a".to_string(), "m".to_string()]),
);
let json = info.to_json_value();
let may_alias = json["may_alias"]["x"].as_array().unwrap();
let sorted: Vec<&str> = may_alias.iter().map(|v| v.as_str().unwrap()).collect();
assert_eq!(sorted, vec!["a", "m", "z"]);
}
#[test]
#[ignore = "Alias analysis not yet implemented"]
fn test_compute_alias_returns_alias_info() {
todo!("Implement compute_alias_from_ssa");
}
#[test]
#[ignore = "Alias analysis not yet implemented"]
fn test_compute_alias_empty_function() {
todo!("Implement compute_alias_from_ssa");
}
#[test]
#[ignore = "Alias analysis not yet implemented"]
fn test_simple_assignment_creates_must_alias() {
todo!("Implement assignment must-alias");
}
#[test]
#[ignore = "Alias analysis not yet implemented"]
fn test_transitive_aliasing() {
todo!("Implement transitive aliasing");
}
#[test]
#[ignore = "Alias analysis not yet implemented"]
fn test_assignment_propagates_points_to() {
todo!("Implement points-to propagation");
}
#[test]
#[ignore = "Alias analysis not yet implemented"]
fn test_new_object_gets_unique_location() {
todo!("Implement allocation site tracking");
}
#[test]
#[ignore = "Alias analysis not yet implemented"]
fn test_allocation_sites_recorded() {
todo!("Implement allocation site recording");
}
#[test]
#[ignore = "Alias analysis not yet implemented"]
fn test_same_object_reused_aliases() {
todo!("Implement shared allocation aliasing");
}
#[test]
#[ignore = "Alias analysis not yet implemented"]
fn test_points_to_computation() {
todo!("Implement points-to computation");
}
#[test]
#[ignore = "Alias analysis not yet implemented"]
fn test_points_to_alloc_format() {
todo!("Implement points-to format validation");
}
#[test]
#[ignore = "Alias analysis not yet implemented"]
fn test_phi_creates_may_alias() {
todo!("Implement phi function may-alias");
}
#[test]
#[ignore = "Alias analysis not yet implemented"]
fn test_phi_propagates_points_to() {
todo!("Implement phi points-to propagation");
}
#[test]
#[ignore = "Alias analysis not yet implemented"]
fn test_ssa_uses_versioned_names() {
todo!("Implement SSA name handling");
}
#[test]
#[ignore = "Alias analysis not yet implemented"]
fn test_ssa_integration_blocks() {
todo!("Implement SSA block processing");
}
#[test]
#[ignore = "Alias analysis not yet implemented"]
fn test_ssa_call_instruction_allocation() {
todo!("Implement Call instruction handling");
}
#[test]
#[ignore = "Alias analysis not yet implemented"]
fn test_field_access_separate() {
todo!("Implement field access tracking");
}
#[test]
#[ignore = "Alias analysis not yet implemented"]
fn test_same_field_same_object_aliases() {
todo!("Implement same-field aliasing");
}
#[test]
#[ignore = "Alias analysis not yet implemented"]
fn test_different_fields_no_alias() {
todo!("Implement different-field separation");
}
#[test]
#[ignore = "Alias analysis not yet implemented"]
fn test_field_store() {
todo!("Implement field store");
}
#[test]
#[ignore = "Alias analysis not yet implemented"]
fn test_field_of_aliasing_objects_may_alias() {
todo!("Implement field aliasing propagation");
}
#[test]
#[ignore = "Alias analysis not yet implemented"]
fn test_parameters_may_alias_conservatively() {
todo!("Implement parameter conservative aliasing");
}
#[test]
#[ignore = "Alias analysis not yet implemented"]
fn test_parameter_points_to_param_location() {
todo!("Implement parameter location");
}
#[test]
#[ignore = "Alias analysis not yet implemented"]
fn test_unknown_call_result() {
todo!("Implement unknown source handling");
}
#[test]
#[ignore = "Alias analysis not yet implemented"]
fn test_unknown_sources_may_alias() {
todo!("Implement unknown source aliasing");
}
#[test]
fn test_self_alias_always_true() {
let info = MockAliasInfo::new("test");
assert!(info.may_alias_check("x_0", "x_0"));
assert!(info.must_alias_check("x_0", "x_0"));
assert!(info.may_alias_check("anything", "anything"));
assert!(info.must_alias_check("anything", "anything"));
}
#[test]
fn test_may_alias_symmetric() {
let mut info = MockAliasInfo::new("test");
info.may_alias
.insert("a".to_string(), HashSet::from(["b".to_string()]));
assert!(info.may_alias_check("a", "b"));
assert!(info.may_alias_check("b", "a")); }
#[test]
#[ignore = "Alias analysis not yet implemented"]
fn test_must_alias_transitive() {
todo!("Implement must-alias transitivity");
}
#[test]
fn test_unknown_variable_no_crash() {
let info = MockAliasInfo::new("test");
assert!(!info.may_alias_check("unknown1", "unknown2"));
assert!(!info.must_alias_check("unknown1", "unknown2"));
assert!(info.get_points_to("unknown").is_empty());
assert!(info.get_aliases("unknown").is_empty());
}
#[test]
#[ignore = "Alias analysis not yet implemented"]
fn test_empty_cfg_handling() {
todo!("Implement empty CFG handling");
}
#[test]
#[ignore = "Alias analysis not yet implemented"]
fn test_max_iterations_limit() {
todo!("Implement iteration limit");
}
#[test]
#[ignore = "Alias analysis not yet implemented"]
fn test_iteration_limit_error() {
todo!("Implement IterationLimit error");
}
#[test]
fn test_must_alias_implies_may_alias() {
let mut info = MockAliasInfo::new("test");
info.must_alias
.insert("a".to_string(), HashSet::from(["b".to_string()]));
}
#[test]
fn test_different_alloc_sites_no_alias() {
let mut info = MockAliasInfo::new("test");
info.points_to
.insert("x".to_string(), HashSet::from(["alloc_1".to_string()]));
info.points_to
.insert("y".to_string(), HashSet::from(["alloc_2".to_string()]));
assert!(!info.may_alias_check("x", "y"));
}
#[test]
#[ignore = "Alias analysis not yet implemented"]
fn test_phi_never_must_alias_sources() {
todo!("Implement phi must-alias property");
}
#[test]
fn test_json_output_structure() {
let mut info = MockAliasInfo::new("test_func");
info.may_alias
.insert("x".to_string(), HashSet::from(["y".to_string()]));
info.must_alias
.insert("a".to_string(), HashSet::from(["b".to_string()]));
info.points_to
.insert("x".to_string(), HashSet::from(["alloc_1".to_string()]));
info.allocation_sites.insert(5, "alloc_5".to_string());
let json = info.to_json_value();
assert!(json.get("function").is_some());
assert!(json.get("may_alias").is_some());
assert!(json.get("must_alias").is_some());
assert!(json.get("points_to").is_some());
assert!(json.get("allocation_sites").is_some());
let serialized = serde_json::to_string(&json).unwrap();
assert!(!serialized.is_empty());
}
#[test]
fn test_json_output_deterministic() {
let mut info = MockAliasInfo::new("test");
info.may_alias.insert(
"x".to_string(),
HashSet::from(["c".to_string(), "a".to_string(), "b".to_string()]),
);
let json1 = info.to_json_value();
let json2 = info.to_json_value();
assert_eq!(
serde_json::to_string(&json1).unwrap(),
serde_json::to_string(&json2).unwrap()
);
}
#[test]
#[ignore = "Alias analysis not yet implemented"]
fn test_integration_simple_function() {
todo!("Implement integration test");
}
#[test]
#[ignore = "Alias analysis not yet implemented"]
fn test_integration_conditional() {
todo!("Implement conditional integration test");
}
#[test]
#[ignore = "Alias analysis not yet implemented"]
fn test_integration_loop() {
todo!("Implement loop integration test");
}
#[test]
#[ignore = "Alias analysis not yet implemented"]
fn test_many_variables_performance() {
todo!("Implement performance test");
}
#[test]
#[ignore = "Alias analysis not yet implemented"]
fn test_deep_nesting_performance() {
todo!("Implement nesting test");
}
mod ssa_integration {
use crate::alias::{compute_alias_from_ssa, AliasInfo};
use crate::ssa::types::{
PhiFunction, PhiSource, SsaBlock, SsaFunction, SsaInstruction, SsaInstructionKind, SsaName,
SsaNameId, SsaStats, SsaType,
};
use std::path::PathBuf;
fn create_test_ssa(name: &str) -> SsaFunction {
SsaFunction {
function: name.to_string(),
file: PathBuf::from("test.py"),
ssa_type: SsaType::Minimal,
blocks: vec![],
ssa_names: vec![],
def_use: std::collections::HashMap::new(),
stats: SsaStats::default(),
}
}
fn create_ssa_name(id: u32, variable: &str, version: u32, line: u32) -> SsaName {
SsaName {
id: SsaNameId(id),
variable: variable.to_string(),
version,
def_block: Some(0),
def_line: line,
}
}
#[test]
fn test_compute_alias_returns_alias_info() {
let ssa = create_test_ssa("test_func");
let result = compute_alias_from_ssa(&ssa).unwrap();
assert_eq!(result.function_name, "test_func");
assert!(result.may_alias.is_empty());
assert!(result.must_alias.is_empty());
}
#[test]
fn test_compute_alias_empty_function() {
let ssa = create_test_ssa("empty");
let result = compute_alias_from_ssa(&ssa).unwrap();
assert!(result.may_alias.is_empty());
assert!(result.must_alias.is_empty());
assert!(result.points_to.is_empty());
}
#[test]
fn test_parameter_gets_param_location() {
let mut ssa = create_test_ssa("param_test");
ssa.ssa_names = vec![create_ssa_name(0, "p", 0, 1)];
ssa.blocks = vec![SsaBlock {
id: 0,
label: Some("entry".to_string()),
lines: (1, 1),
phi_functions: vec![],
instructions: vec![SsaInstruction {
kind: SsaInstructionKind::Param,
target: Some(SsaNameId(0)),
uses: vec![],
line: 1,
source_text: Some("def f(p):".to_string()),
}],
successors: vec![],
predecessors: vec![],
}];
let result = compute_alias_from_ssa(&ssa).unwrap();
let pts = result.get_points_to("p_0");
assert!(
pts.contains("param_p"),
"Expected p_0 to point to param_p, got {:?}",
pts
);
}
#[test]
fn test_parameters_may_alias_conservatively() {
let mut ssa = create_test_ssa("two_params");
ssa.ssa_names = vec![create_ssa_name(0, "a", 0, 1), create_ssa_name(1, "b", 0, 1)];
ssa.blocks = vec![SsaBlock {
id: 0,
label: Some("entry".to_string()),
lines: (1, 1),
phi_functions: vec![],
instructions: vec![
SsaInstruction {
kind: SsaInstructionKind::Param,
target: Some(SsaNameId(0)),
uses: vec![],
line: 1,
source_text: Some("def f(a, b):".to_string()),
},
SsaInstruction {
kind: SsaInstructionKind::Param,
target: Some(SsaNameId(1)),
uses: vec![],
line: 1,
source_text: None,
},
],
successors: vec![],
predecessors: vec![],
}];
let result = compute_alias_from_ssa(&ssa).unwrap();
assert!(
result.may_alias_check("a_0", "b_0"),
"Parameters should may-alias conservatively"
);
}
#[test]
fn test_simple_assignment_creates_must_alias() {
let mut ssa = create_test_ssa("copy_test");
ssa.ssa_names = vec![create_ssa_name(0, "p", 0, 1), create_ssa_name(1, "x", 0, 2)];
ssa.blocks = vec![SsaBlock {
id: 0,
label: Some("entry".to_string()),
lines: (1, 2),
phi_functions: vec![],
instructions: vec![
SsaInstruction {
kind: SsaInstructionKind::Param,
target: Some(SsaNameId(0)),
uses: vec![],
line: 1,
source_text: Some("def f(p):".to_string()),
},
SsaInstruction {
kind: SsaInstructionKind::Assign,
target: Some(SsaNameId(1)),
uses: vec![SsaNameId(0)],
line: 2,
source_text: Some("x = p".to_string()),
},
],
successors: vec![],
predecessors: vec![],
}];
let result = compute_alias_from_ssa(&ssa).unwrap();
assert!(
result.must_alias_check("x_0", "p_0"),
"x_0 should must-alias p_0"
);
assert!(
result.may_alias_check("x_0", "p_0"),
"x_0 should may-alias p_0"
);
}
#[test]
fn test_assignment_propagates_points_to() {
let mut ssa = create_test_ssa("propagate_test");
ssa.ssa_names = vec![create_ssa_name(0, "p", 0, 1), create_ssa_name(1, "x", 0, 2)];
ssa.blocks = vec![SsaBlock {
id: 0,
label: Some("entry".to_string()),
lines: (1, 2),
phi_functions: vec![],
instructions: vec![
SsaInstruction {
kind: SsaInstructionKind::Param,
target: Some(SsaNameId(0)),
uses: vec![],
line: 1,
source_text: Some("def f(p):".to_string()),
},
SsaInstruction {
kind: SsaInstructionKind::Assign,
target: Some(SsaNameId(1)),
uses: vec![SsaNameId(0)],
line: 2,
source_text: Some("x = p".to_string()),
},
],
successors: vec![],
predecessors: vec![],
}];
let result = compute_alias_from_ssa(&ssa).unwrap();
let pts_p = result.get_points_to("p_0");
let pts_x = result.get_points_to("x_0");
assert!(
pts_p.is_subset(&pts_x),
"pts(x) should include pts(p): pts(p)={:?}, pts(x)={:?}",
pts_p,
pts_x
);
}
#[test]
fn test_allocation_creates_unique_location() {
let mut ssa = create_test_ssa("alloc_test");
ssa.ssa_names = vec![create_ssa_name(0, "x", 0, 1)];
ssa.blocks = vec![SsaBlock {
id: 0,
label: Some("entry".to_string()),
lines: (1, 1),
phi_functions: vec![],
instructions: vec![SsaInstruction {
kind: SsaInstructionKind::Call,
target: Some(SsaNameId(0)),
uses: vec![],
line: 1,
source_text: Some("x = Foo()".to_string()),
}],
successors: vec![],
predecessors: vec![],
}];
let result = compute_alias_from_ssa(&ssa).unwrap();
let pts = result.get_points_to("x_0");
assert!(pts.contains("alloc_1"), "Expected alloc_1, got {:?}", pts);
}
#[test]
fn test_different_allocations_no_alias() {
let mut ssa = create_test_ssa("two_allocs");
ssa.ssa_names = vec![create_ssa_name(0, "x", 0, 1), create_ssa_name(1, "y", 0, 2)];
ssa.blocks = vec![SsaBlock {
id: 0,
label: Some("entry".to_string()),
lines: (1, 2),
phi_functions: vec![],
instructions: vec![
SsaInstruction {
kind: SsaInstructionKind::Call,
target: Some(SsaNameId(0)),
uses: vec![],
line: 1,
source_text: Some("x = Foo()".to_string()),
},
SsaInstruction {
kind: SsaInstructionKind::Call,
target: Some(SsaNameId(1)),
uses: vec![],
line: 2,
source_text: Some("y = Bar()".to_string()),
},
],
successors: vec![],
predecessors: vec![],
}];
let result = compute_alias_from_ssa(&ssa).unwrap();
assert!(
!result.may_alias_check("x_0", "y_0"),
"Different allocations should not alias"
);
}
#[test]
fn test_shared_allocation_aliases() {
let mut ssa = create_test_ssa("shared_alloc");
ssa.ssa_names = vec![
create_ssa_name(0, "obj", 0, 1),
create_ssa_name(1, "x", 0, 2),
create_ssa_name(2, "y", 0, 3),
];
ssa.blocks = vec![SsaBlock {
id: 0,
label: Some("entry".to_string()),
lines: (1, 3),
phi_functions: vec![],
instructions: vec![
SsaInstruction {
kind: SsaInstructionKind::Call,
target: Some(SsaNameId(0)),
uses: vec![],
line: 1,
source_text: Some("obj = Foo()".to_string()),
},
SsaInstruction {
kind: SsaInstructionKind::Assign,
target: Some(SsaNameId(1)),
uses: vec![SsaNameId(0)],
line: 2,
source_text: Some("x = obj".to_string()),
},
SsaInstruction {
kind: SsaInstructionKind::Assign,
target: Some(SsaNameId(2)),
uses: vec![SsaNameId(0)],
line: 3,
source_text: Some("y = obj".to_string()),
},
],
successors: vec![],
predecessors: vec![],
}];
let result = compute_alias_from_ssa(&ssa).unwrap();
assert!(
result.may_alias_check("x_0", "y_0"),
"x and y should alias via shared obj"
);
}
#[test]
fn test_phi_creates_may_alias_not_must() {
let mut ssa = create_test_ssa("phi_test");
ssa.ssa_names = vec![
create_ssa_name(0, "a", 0, 1),
create_ssa_name(1, "b", 0, 1),
create_ssa_name(2, "x", 0, 3), create_ssa_name(3, "x", 1, 5), create_ssa_name(4, "x", 2, 7), ];
ssa.blocks = vec![
SsaBlock {
id: 0,
label: Some("entry".to_string()),
lines: (1, 2),
phi_functions: vec![],
instructions: vec![
SsaInstruction {
kind: SsaInstructionKind::Param,
target: Some(SsaNameId(0)),
uses: vec![],
line: 1,
source_text: Some("def f(a, b):".to_string()),
},
SsaInstruction {
kind: SsaInstructionKind::Param,
target: Some(SsaNameId(1)),
uses: vec![],
line: 1,
source_text: None,
},
],
successors: vec![1, 2],
predecessors: vec![],
},
SsaBlock {
id: 1,
label: Some("true".to_string()),
lines: (3, 4),
phi_functions: vec![],
instructions: vec![SsaInstruction {
kind: SsaInstructionKind::Assign,
target: Some(SsaNameId(2)),
uses: vec![SsaNameId(0)],
line: 3,
source_text: Some("x = a".to_string()),
}],
successors: vec![3],
predecessors: vec![0],
},
SsaBlock {
id: 2,
label: Some("false".to_string()),
lines: (5, 6),
phi_functions: vec![],
instructions: vec![SsaInstruction {
kind: SsaInstructionKind::Assign,
target: Some(SsaNameId(3)),
uses: vec![SsaNameId(1)],
line: 5,
source_text: Some("x = b".to_string()),
}],
successors: vec![3],
predecessors: vec![0],
},
SsaBlock {
id: 3,
label: Some("merge".to_string()),
lines: (7, 8),
phi_functions: vec![PhiFunction {
target: SsaNameId(4),
variable: "x".to_string(),
sources: vec![
PhiSource {
block: 1,
name: SsaNameId(2),
},
PhiSource {
block: 2,
name: SsaNameId(3),
},
],
line: 7,
}],
instructions: vec![SsaInstruction {
kind: SsaInstructionKind::Return,
target: None,
uses: vec![SsaNameId(4)],
line: 7,
source_text: Some("return x".to_string()),
}],
successors: vec![],
predecessors: vec![1, 2],
},
];
let result = compute_alias_from_ssa(&ssa).unwrap();
assert!(
result.may_alias_check("x_2", "x_0"),
"Phi result x_2 should may-alias x_0"
);
assert!(
result.may_alias_check("x_2", "x_1"),
"Phi result x_2 should may-alias x_1"
);
assert!(
!result.must_alias_check("x_2", "x_0"),
"Phi result x_2 should NOT must-alias x_0"
);
assert!(
!result.must_alias_check("x_2", "x_1"),
"Phi result x_2 should NOT must-alias x_1"
);
}
#[test]
fn test_unknown_call_gets_unknown_location() {
let mut ssa = create_test_ssa("unknown_test");
ssa.ssa_names = vec![create_ssa_name(0, "x", 0, 1)];
ssa.blocks = vec![SsaBlock {
id: 0,
label: Some("entry".to_string()),
lines: (1, 1),
phi_functions: vec![],
instructions: vec![SsaInstruction {
kind: SsaInstructionKind::Call,
target: Some(SsaNameId(0)),
uses: vec![],
line: 1,
source_text: Some("x = external_func()".to_string()),
}],
successors: vec![],
predecessors: vec![],
}];
let result = compute_alias_from_ssa(&ssa).unwrap();
let pts = result.get_points_to("x_0");
assert!(
pts.contains("unknown_1"),
"Expected unknown_1, got {:?}",
pts
);
}
#[test]
fn test_json_output_deterministic() {
let mut ssa = create_test_ssa("json_test");
ssa.ssa_names = vec![
create_ssa_name(0, "a", 0, 1),
create_ssa_name(1, "b", 0, 1),
create_ssa_name(2, "c", 0, 1),
];
ssa.blocks = vec![SsaBlock {
id: 0,
label: Some("entry".to_string()),
lines: (1, 1),
phi_functions: vec![],
instructions: vec![
SsaInstruction {
kind: SsaInstructionKind::Param,
target: Some(SsaNameId(0)),
uses: vec![],
line: 1,
source_text: Some("def f(a, b, c):".to_string()),
},
SsaInstruction {
kind: SsaInstructionKind::Param,
target: Some(SsaNameId(1)),
uses: vec![],
line: 1,
source_text: None,
},
SsaInstruction {
kind: SsaInstructionKind::Param,
target: Some(SsaNameId(2)),
uses: vec![],
line: 1,
source_text: None,
},
],
successors: vec![],
predecessors: vec![],
}];
let result = compute_alias_from_ssa(&ssa).unwrap();
let json = result.to_json_value();
assert!(json.get("function").is_some());
assert!(json.get("may_alias").is_some());
assert!(json.get("must_alias").is_some());
assert!(json.get("points_to").is_some());
}
#[test]
fn test_json_output_structure() {
let ssa = create_test_ssa("json_struct");
let result = compute_alias_from_ssa(&ssa).unwrap();
let json = result.to_json_value();
assert_eq!(json["function"], "json_struct");
assert!(json["may_alias"].is_object());
assert!(json["must_alias"].is_object());
assert!(json["points_to"].is_object());
assert!(json["allocation_sites"].is_object());
}
#[test]
fn test_field_load_creates_field_location() {
let mut ssa = create_test_ssa("field_load_test");
ssa.ssa_names = vec![
create_ssa_name(0, "obj", 0, 1),
create_ssa_name(1, "x", 0, 2),
];
ssa.blocks = vec![SsaBlock {
id: 0,
label: Some("entry".to_string()),
lines: (1, 2),
phi_functions: vec![],
instructions: vec![
SsaInstruction {
kind: SsaInstructionKind::Param,
target: Some(SsaNameId(0)),
uses: vec![],
line: 1,
source_text: Some("def f(obj):".to_string()),
},
SsaInstruction {
kind: SsaInstructionKind::Assign,
target: Some(SsaNameId(1)),
uses: vec![SsaNameId(0)],
line: 2,
source_text: Some("x = obj.field".to_string()),
},
],
successors: vec![],
predecessors: vec![],
}];
let result = compute_alias_from_ssa(&ssa).unwrap();
let pts = result.get_points_to("x_0");
assert!(
pts.iter()
.any(|loc| loc.contains("param_obj") && loc.contains("field")),
"Expected x_0 to point to param_obj.field, got {:?}",
pts
);
}
#[test]
fn test_same_field_same_object_aliasing() {
let mut ssa = create_test_ssa("same_field_test");
ssa.ssa_names = vec![
create_ssa_name(0, "obj", 0, 1),
create_ssa_name(1, "x", 0, 2),
create_ssa_name(2, "y", 0, 3),
];
ssa.blocks = vec![SsaBlock {
id: 0,
label: Some("entry".to_string()),
lines: (1, 3),
phi_functions: vec![],
instructions: vec![
SsaInstruction {
kind: SsaInstructionKind::Param,
target: Some(SsaNameId(0)),
uses: vec![],
line: 1,
source_text: Some("def f(obj):".to_string()),
},
SsaInstruction {
kind: SsaInstructionKind::Assign,
target: Some(SsaNameId(1)),
uses: vec![SsaNameId(0)],
line: 2,
source_text: Some("x = obj.data".to_string()),
},
SsaInstruction {
kind: SsaInstructionKind::Assign,
target: Some(SsaNameId(2)),
uses: vec![SsaNameId(0)],
line: 3,
source_text: Some("y = obj.data".to_string()),
},
],
successors: vec![],
predecessors: vec![],
}];
let result = compute_alias_from_ssa(&ssa).unwrap();
assert!(
result.may_alias_check("x_0", "y_0"),
"x_0 and y_0 should may-alias (same field from same object)"
);
}
#[test]
fn test_different_fields_no_aliasing() {
let mut ssa = create_test_ssa("diff_field_test");
ssa.ssa_names = vec![
create_ssa_name(0, "obj", 0, 1),
create_ssa_name(1, "x", 0, 2),
create_ssa_name(2, "y", 0, 3),
];
ssa.blocks = vec![SsaBlock {
id: 0,
label: Some("entry".to_string()),
lines: (1, 3),
phi_functions: vec![],
instructions: vec![
SsaInstruction {
kind: SsaInstructionKind::Param,
target: Some(SsaNameId(0)),
uses: vec![],
line: 1,
source_text: Some("def f(obj):".to_string()),
},
SsaInstruction {
kind: SsaInstructionKind::Assign,
target: Some(SsaNameId(1)),
uses: vec![SsaNameId(0)],
line: 2,
source_text: Some("x = obj.field_a".to_string()),
},
SsaInstruction {
kind: SsaInstructionKind::Assign,
target: Some(SsaNameId(2)),
uses: vec![SsaNameId(0)],
line: 3,
source_text: Some("y = obj.field_b".to_string()),
},
],
successors: vec![],
predecessors: vec![],
}];
let result = compute_alias_from_ssa(&ssa).unwrap();
assert!(
!result.may_alias_check("x_0", "y_0"),
"x_0 and y_0 should NOT alias (different fields)"
);
}
#[test]
fn test_class_variable_creates_class_var_location() {
let mut ssa = create_test_ssa("class_var_test");
ssa.ssa_names = vec![create_ssa_name(0, "x", 0, 1)];
ssa.blocks = vec![SsaBlock {
id: 0,
label: Some("entry".to_string()),
lines: (1, 1),
phi_functions: vec![],
instructions: vec![SsaInstruction {
kind: SsaInstructionKind::Assign,
target: Some(SsaNameId(0)),
uses: vec![],
line: 1,
source_text: Some("x = Config.DEBUG".to_string()),
}],
successors: vec![],
predecessors: vec![],
}];
let result = compute_alias_from_ssa(&ssa).unwrap();
let pts = result.get_points_to("x_0");
assert!(
pts.contains("alloc_class_Config_DEBUG"),
"Expected alloc_class_Config_DEBUG, got {:?}",
pts
);
}
#[test]
fn test_class_variable_singleton_aliasing() {
let mut ssa = create_test_ssa("class_var_alias_test");
ssa.ssa_names = vec![create_ssa_name(0, "x", 0, 1), create_ssa_name(1, "y", 0, 2)];
ssa.blocks = vec![SsaBlock {
id: 0,
label: Some("entry".to_string()),
lines: (1, 2),
phi_functions: vec![],
instructions: vec![
SsaInstruction {
kind: SsaInstructionKind::Assign,
target: Some(SsaNameId(0)),
uses: vec![],
line: 1,
source_text: Some("x = Config.DEBUG".to_string()),
},
SsaInstruction {
kind: SsaInstructionKind::Assign,
target: Some(SsaNameId(1)),
uses: vec![],
line: 2,
source_text: Some("y = Config.DEBUG".to_string()),
},
],
successors: vec![],
predecessors: vec![],
}];
let result = compute_alias_from_ssa(&ssa).unwrap();
assert!(
result.may_alias_check("x_0", "y_0"),
"x_0 and y_0 should alias (same class variable)"
);
}
#[test]
fn test_alias_info_has_uncertain_fields() {
use crate::alias::Confidence;
let info = AliasInfo::new("test_func");
assert!(info.uncertain.is_empty());
assert_eq!(info.confidence, Confidence::Low);
assert!(info.language_notes.is_empty());
}
#[test]
fn test_uncertain_alias_construction() {
use crate::alias::UncertainAlias;
let ua = UncertainAlias {
vars: vec!["a".to_string(), "b".to_string()],
line: 42,
reason: "assignment from function return - type unknown".to_string(),
};
assert_eq!(ua.vars.len(), 2);
assert_eq!(ua.line, 42);
assert!(ua.reason.contains("function return"));
}
#[test]
fn test_uncertain_alias_serialization() {
use crate::alias::UncertainAlias;
let ua = UncertainAlias {
vars: vec!["x".to_string(), "y".to_string()],
line: 10,
reason: "depends on value vs reference semantics".to_string(),
};
let json = serde_json::to_string(&ua).unwrap();
assert!(json.contains("\"vars\""));
assert!(json.contains("\"line\""));
assert!(json.contains("\"reason\""));
let deserialized: UncertainAlias = serde_json::from_str(&json).unwrap();
assert_eq!(deserialized.vars, ua.vars);
assert_eq!(deserialized.line, ua.line);
}
#[test]
fn test_alias_info_uncertain_in_json() {
use crate::alias::{Confidence, UncertainAlias};
let mut info = AliasInfo::new("my_func");
info.uncertain.push(UncertainAlias {
vars: vec!["a".to_string(), "b".to_string()],
line: 42,
reason: "assignment from function return".to_string(),
});
info.confidence = Confidence::Medium;
info.language_notes = "Python uses reference semantics for non-primitive types".to_string();
let json_str = info.to_json();
let json: serde_json::Value = serde_json::from_str(&json_str).unwrap();
assert!(
json.get("uncertain").is_some(),
"JSON should have uncertain key"
);
assert!(
json.get("confidence").is_some(),
"JSON should have confidence key"
);
assert!(
json.get("language_notes").is_some(),
"JSON should have language_notes key"
);
let uncertain = json["uncertain"].as_array().unwrap();
assert_eq!(uncertain.len(), 1);
assert_eq!(uncertain[0]["line"], 42);
assert_eq!(json["confidence"], "medium");
}
#[test]
fn test_confidence_enum_shared() {
use crate::alias::Confidence;
assert_eq!(Confidence::Low, Confidence::default());
let json = serde_json::to_string(&Confidence::High).unwrap();
assert_eq!(json, "\"high\"");
}
}