use std::collections::{HashMap, HashSet};
use super::store::{Assignment, ValueStore};
use super::types::{SymbolicValue, MAX_RESOLUTION_DEPTH};
pub fn resolve_cross_function(
store: &mut ValueStore,
topo_order: &[String],
_call_map: &HashMap<String, HashSet<String>>,
) {
let mut resolving: HashSet<String> = HashSet::new();
for func_qn in topo_order {
resolve_function(store, func_qn, &mut resolving, 0);
}
}
fn resolve_function(
store: &mut ValueStore,
func_qn: &str,
resolving: &mut HashSet<String>,
depth: usize,
) {
if resolving.contains(func_qn) || depth > MAX_RESOLUTION_DEPTH {
return;
}
resolving.insert(func_qn.to_string());
if let Some(raw_return) = store.return_values.get(func_qn).cloned() {
let resolved = substitute_references(&raw_return, store, resolving, depth + 1);
store.return_values.insert(func_qn.to_string(), resolved);
}
if let Some(assignments) = store.function_values.get(func_qn).cloned() {
let resolved: Vec<Assignment> = assignments
.into_iter()
.map(|mut a| {
a.value = substitute_references(&a.value, store, resolving, depth + 1);
a
})
.collect();
store.function_values.insert(func_qn.to_string(), resolved);
}
resolving.remove(func_qn);
}
fn substitute_references(
value: &SymbolicValue,
store: &ValueStore,
resolving: &HashSet<String>,
depth: usize,
) -> SymbolicValue {
if depth > MAX_RESOLUTION_DEPTH {
return value.clone();
}
match value {
SymbolicValue::Variable(qn) => {
if let Some(constant) = store.constants.get(qn) {
return substitute_references(constant, store, resolving, depth + 1);
}
value.clone()
}
SymbolicValue::Call(callee, args) => {
let resolved_args: Vec<SymbolicValue> = args
.iter()
.map(|a| substitute_references(a, store, resolving, depth + 1))
.collect();
if resolving.contains(callee.as_str()) {
return SymbolicValue::Call(callee.clone(), resolved_args);
}
match store.return_values.get(callee) {
Some(ret_val) if *ret_val != SymbolicValue::Unknown => {
let substituted = substitute_params(ret_val, &resolved_args);
substitute_references(&substituted, store, resolving, depth + 1)
}
_ => {
SymbolicValue::Call(callee.clone(), resolved_args)
}
}
}
SymbolicValue::BinaryOp(op, lhs, rhs) => {
let new_lhs = substitute_references(lhs, store, resolving, depth + 1);
let new_rhs = substitute_references(rhs, store, resolving, depth + 1);
SymbolicValue::BinaryOp(*op, Box::new(new_lhs), Box::new(new_rhs))
}
SymbolicValue::Concat(parts) => {
let new_parts: Vec<SymbolicValue> = parts
.iter()
.map(|p| substitute_references(p, store, resolving, depth + 1))
.collect();
SymbolicValue::Concat(new_parts)
}
SymbolicValue::Phi(arms) => {
let new_arms: Vec<SymbolicValue> = arms
.iter()
.map(|a| substitute_references(a, store, resolving, depth + 1))
.collect();
SymbolicValue::Phi(new_arms)
}
SymbolicValue::FieldAccess(obj, field) => {
let new_obj = substitute_references(obj, store, resolving, depth + 1);
SymbolicValue::FieldAccess(Box::new(new_obj), field.clone())
}
SymbolicValue::Index(obj, key) => {
let new_obj = substitute_references(obj, store, resolving, depth + 1);
let new_key = substitute_references(key, store, resolving, depth + 1);
SymbolicValue::Index(Box::new(new_obj), Box::new(new_key))
}
SymbolicValue::Literal(_) | SymbolicValue::Parameter(_) | SymbolicValue::Unknown => {
value.clone()
}
}
}
fn substitute_params(value: &SymbolicValue, args: &[SymbolicValue]) -> SymbolicValue {
match value {
SymbolicValue::Parameter(n) => {
if *n < args.len() {
args[*n].clone()
} else {
SymbolicValue::Unknown
}
}
SymbolicValue::BinaryOp(op, lhs, rhs) => {
let new_lhs = substitute_params(lhs, args);
let new_rhs = substitute_params(rhs, args);
SymbolicValue::BinaryOp(*op, Box::new(new_lhs), Box::new(new_rhs))
}
SymbolicValue::Concat(parts) => {
let new_parts: Vec<SymbolicValue> =
parts.iter().map(|p| substitute_params(p, args)).collect();
SymbolicValue::Concat(new_parts)
}
SymbolicValue::Phi(arms) => {
let new_arms: Vec<SymbolicValue> =
arms.iter().map(|a| substitute_params(a, args)).collect();
SymbolicValue::Phi(new_arms)
}
SymbolicValue::FieldAccess(obj, field) => {
let new_obj = substitute_params(obj, args);
SymbolicValue::FieldAccess(Box::new(new_obj), field.clone())
}
SymbolicValue::Index(obj, key) => {
let new_obj = substitute_params(obj, args);
let new_key = substitute_params(key, args);
SymbolicValue::Index(Box::new(new_obj), Box::new(new_key))
}
SymbolicValue::Call(callee, call_args) => {
let new_args: Vec<SymbolicValue> = call_args
.iter()
.map(|a| substitute_params(a, args))
.collect();
SymbolicValue::Call(callee.clone(), new_args)
}
SymbolicValue::Variable(_) => value.clone(),
SymbolicValue::Literal(_) | SymbolicValue::Unknown => value.clone(),
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::values::store::*;
use crate::values::types::*;
use std::collections::HashMap;
#[test]
fn test_resolve_constant_reference() {
let mut store = ValueStore::new();
store.constants.insert(
"config.TIMEOUT".into(),
SymbolicValue::Literal(LiteralValue::Integer(3600)),
);
store.function_values.insert(
"api.handler".into(),
vec![Assignment {
variable: "timeout".into(),
value: SymbolicValue::Variable("config.TIMEOUT".into()),
line: 5,
column: 4,
}],
);
resolve_cross_function(&mut store, &["api.handler".into()], &HashMap::new());
let resolved = store.resolve_at("api.handler", "timeout", 10);
assert_eq!(
resolved,
SymbolicValue::Literal(LiteralValue::Integer(3600))
);
}
#[test]
fn test_resolve_call_return_value() {
let mut store = ValueStore::new();
store.return_values.insert(
"foo".into(),
SymbolicValue::Literal(LiteralValue::Integer(42)),
);
store.function_values.insert(
"bar".into(),
vec![Assignment {
variable: "x".into(),
value: SymbolicValue::Call("foo".into(), vec![]),
line: 1,
column: 0,
}],
);
resolve_cross_function(&mut store, &["foo".into(), "bar".into()], &HashMap::new());
let resolved = store.resolve_at("bar", "x", 10);
assert_eq!(resolved, SymbolicValue::Literal(LiteralValue::Integer(42)));
}
#[test]
fn test_parameter_substitution() {
let mut store = ValueStore::new();
store
.return_values
.insert("foo".into(), SymbolicValue::Parameter(0));
store.function_values.insert(
"bar".into(),
vec![Assignment {
variable: "result".into(),
value: SymbolicValue::Call(
"foo".into(),
vec![SymbolicValue::Literal(LiteralValue::Integer(42))],
),
line: 1,
column: 0,
}],
);
resolve_cross_function(&mut store, &["foo".into(), "bar".into()], &HashMap::new());
let resolved = store.resolve_at("bar", "result", 10);
assert_eq!(resolved, SymbolicValue::Literal(LiteralValue::Integer(42)));
}
#[test]
fn test_cycle_detection_does_not_hang() {
let mut store = ValueStore::new();
store
.return_values
.insert("a".into(), SymbolicValue::Call("b".into(), vec![]));
store
.return_values
.insert("b".into(), SymbolicValue::Call("a".into(), vec![]));
resolve_cross_function(&mut store, &["a".into(), "b".into()], &HashMap::new());
}
#[test]
fn test_depth_limit() {
let mut store = ValueStore::new();
for i in 0..20 {
store.return_values.insert(
format!("f{i}"),
SymbolicValue::Call(format!("f{}", i + 1), vec![]),
);
}
store.return_values.insert(
"f20".into(),
SymbolicValue::Literal(LiteralValue::Integer(99)),
);
let order: Vec<String> = (0..=20).rev().map(|i| format!("f{i}")).collect();
resolve_cross_function(&mut store, &order, &HashMap::new());
}
#[test]
fn test_nested_resolution() {
let mut store = ValueStore::new();
store.return_values.insert(
"foo".into(),
SymbolicValue::Literal(LiteralValue::Integer(10)),
);
store.return_values.insert(
"bar".into(),
SymbolicValue::BinaryOp(
BinOp::Add,
Box::new(SymbolicValue::Call("foo".into(), vec![])),
Box::new(SymbolicValue::Literal(LiteralValue::Integer(5))),
),
);
resolve_cross_function(&mut store, &["foo".into(), "bar".into()], &HashMap::new());
let ret = store.return_value("bar");
assert_eq!(
ret,
SymbolicValue::BinaryOp(
BinOp::Add,
Box::new(SymbolicValue::Literal(LiteralValue::Integer(10))),
Box::new(SymbolicValue::Literal(LiteralValue::Integer(5))),
)
);
}
#[test]
fn test_concat_resolution() {
let mut store = ValueStore::new();
store.constants.insert(
"config.PREFIX".into(),
SymbolicValue::Literal(LiteralValue::String("api".into())),
);
store.function_values.insert(
"build_url".into(),
vec![Assignment {
variable: "url".into(),
value: SymbolicValue::Concat(vec![
SymbolicValue::Variable("config.PREFIX".into()),
SymbolicValue::Literal(LiteralValue::String("/users".into())),
]),
line: 1,
column: 0,
}],
);
resolve_cross_function(&mut store, &["build_url".into()], &HashMap::new());
let resolved = store.resolve_at("build_url", "url", 10);
assert_eq!(
resolved,
SymbolicValue::Concat(vec![
SymbolicValue::Literal(LiteralValue::String("api".into())),
SymbolicValue::Literal(LiteralValue::String("/users".into())),
])
);
}
#[test]
fn test_parameter_out_of_bounds_becomes_unknown() {
let result = substitute_params(
&SymbolicValue::Parameter(5),
&[SymbolicValue::Literal(LiteralValue::Integer(1))],
);
assert_eq!(result, SymbolicValue::Unknown);
}
#[test]
fn test_substitute_params_in_binary_op() {
let value = SymbolicValue::BinaryOp(
BinOp::Add,
Box::new(SymbolicValue::Parameter(0)),
Box::new(SymbolicValue::Parameter(1)),
);
let args = vec![
SymbolicValue::Literal(LiteralValue::Integer(10)),
SymbolicValue::Literal(LiteralValue::Integer(20)),
];
let result = substitute_params(&value, &args);
assert_eq!(
result,
SymbolicValue::BinaryOp(
BinOp::Add,
Box::new(SymbolicValue::Literal(LiteralValue::Integer(10))),
Box::new(SymbolicValue::Literal(LiteralValue::Integer(20))),
)
);
}
#[test]
fn test_phi_resolution() {
let mut store = ValueStore::new();
store.constants.insert(
"config.A".into(),
SymbolicValue::Literal(LiteralValue::Integer(1)),
);
store.constants.insert(
"config.B".into(),
SymbolicValue::Literal(LiteralValue::Integer(2)),
);
store.function_values.insert(
"func".into(),
vec![Assignment {
variable: "x".into(),
value: SymbolicValue::Phi(vec![
SymbolicValue::Variable("config.A".into()),
SymbolicValue::Variable("config.B".into()),
]),
line: 5,
column: 0,
}],
);
resolve_cross_function(&mut store, &["func".into()], &HashMap::new());
let resolved = store.resolve_at("func", "x", 10);
assert_eq!(
resolved,
SymbolicValue::Phi(vec![
SymbolicValue::Literal(LiteralValue::Integer(1)),
SymbolicValue::Literal(LiteralValue::Integer(2)),
])
);
}
#[test]
fn test_field_access_resolution() {
let mut store = ValueStore::new();
store.constants.insert(
"config.OBJ".into(),
SymbolicValue::Literal(LiteralValue::String("resolved_obj".into())),
);
store.function_values.insert(
"func".into(),
vec![Assignment {
variable: "val".into(),
value: SymbolicValue::FieldAccess(
Box::new(SymbolicValue::Variable("config.OBJ".into())),
"field".into(),
),
line: 1,
column: 0,
}],
);
resolve_cross_function(&mut store, &["func".into()], &HashMap::new());
let resolved = store.resolve_at("func", "val", 10);
assert_eq!(
resolved,
SymbolicValue::FieldAccess(
Box::new(SymbolicValue::Literal(LiteralValue::String(
"resolved_obj".into()
))),
"field".into(),
)
);
}
#[test]
fn test_index_resolution() {
let mut store = ValueStore::new();
store.constants.insert(
"config.IDX".into(),
SymbolicValue::Literal(LiteralValue::Integer(0)),
);
store.function_values.insert(
"func".into(),
vec![Assignment {
variable: "elem".into(),
value: SymbolicValue::Index(
Box::new(SymbolicValue::Literal(LiteralValue::List(vec![
SymbolicValue::Literal(LiteralValue::Integer(42)),
]))),
Box::new(SymbolicValue::Variable("config.IDX".into())),
),
line: 1,
column: 0,
}],
);
resolve_cross_function(&mut store, &["func".into()], &HashMap::new());
let resolved = store.resolve_at("func", "elem", 10);
assert_eq!(
resolved,
SymbolicValue::Index(
Box::new(SymbolicValue::Literal(LiteralValue::List(vec![
SymbolicValue::Literal(LiteralValue::Integer(42)),
]))),
Box::new(SymbolicValue::Literal(LiteralValue::Integer(0))),
)
);
}
#[test]
fn test_empty_topo_order_is_noop() {
let mut store = ValueStore::new();
store
.return_values
.insert("foo".into(), SymbolicValue::Variable("config.X".into()));
resolve_cross_function(&mut store, &[], &HashMap::new());
assert_eq!(
store.return_value("foo"),
SymbolicValue::Variable("config.X".into())
);
}
}