use vyre::ir::Program;
use vyre_primitives::graph::csr_forward_traverse::csr_forward_traverse;
use vyre_primitives::graph::program_graph::ProgramGraphShape;
use vyre_primitives::predicate::edge_kind;
const OP_ID: &str = "vyre-libs::security::flows_to";
pub const FLOWS_TO_MASK: u32 = edge_kind::ASSIGNMENT
| edge_kind::CALL_ARG
| edge_kind::RETURN
| edge_kind::PHI
| edge_kind::ALIAS
| edge_kind::MEM_STORE
| edge_kind::MEM_LOAD
| edge_kind::MUT_REF;
pub const ALIAS_PROPAGATION_MASK: u32 =
edge_kind::ASSIGNMENT | edge_kind::ALIAS | edge_kind::MUT_REF | edge_kind::PHI;
#[must_use]
pub fn flows_to(shape: ProgramGraphShape, frontier_in: &str, frontier_out: &str) -> Program {
crate::security::assert_security_inputs(
OP_ID,
shape.node_count,
&[("frontier_in", frontier_in), ("frontier_out", frontier_out)],
);
crate::region::tag_program(
OP_ID,
csr_forward_traverse(shape, frontier_in, frontier_out, FLOWS_TO_MASK),
)
}
#[must_use]
pub fn flows_to_alias_only(
shape: ProgramGraphShape,
frontier_in: &str,
frontier_out: &str,
) -> Program {
crate::region::tag_program(
OP_ID,
csr_forward_traverse(shape, frontier_in, frontier_out, ALIAS_PROPAGATION_MASK),
)
}
inventory::submit! {
crate::harness::OpEntry {
id: OP_ID,
build: || flows_to(ProgramGraphShape::new(4, 3), "fin", "fout"),
test_inputs: Some(|| {
let to_bytes = |w: &[u32]| vyre_primitives::wire::pack_u32_slice(w);
vec![vec![
to_bytes(&[0, 0, 0, 0]), to_bytes(&[0, 1, 2, 3, 3]), to_bytes(&[1, 2, 3]), to_bytes(&[
edge_kind::ASSIGNMENT,
edge_kind::ASSIGNMENT,
edge_kind::ASSIGNMENT,
]), to_bytes(&[0, 0, 0, 0]), to_bytes(&[0b0001]), to_bytes(&[0b0001]), ]]
}),
expected_output: Some(|| {
let to_bytes = |w: &[u32]| vyre_primitives::wire::pack_u32_slice(w);
vec![vec![to_bytes(&[0b0011])]]
}),
category: Some("security"),
}
}
inventory::submit! {
crate::harness::ConvergenceContract {
op_id: OP_ID,
max_iterations: 4096,
}
}
#[cfg(test)]
mod tests {
use super::*;
use vyre_primitives::predicate::edge_kind;
#[test]
fn flows_to_mask_excludes_control_and_dominance() {
assert_eq!(FLOWS_TO_MASK & edge_kind::CONTROL, 0);
assert_eq!(FLOWS_TO_MASK & edge_kind::DOMINANCE, 0);
}
#[test]
fn flows_to_mask_includes_assignment_and_call_arg() {
assert_ne!(FLOWS_TO_MASK & edge_kind::ASSIGNMENT, 0);
assert_ne!(FLOWS_TO_MASK & edge_kind::CALL_ARG, 0);
}
#[test]
fn flows_to_mask_is_not_universal() {
assert_ne!(FLOWS_TO_MASK, 0xFFFF_FFFF, "regression to universal mask");
}
#[test]
fn flows_to_program_emits_frontier_buffers() {
let p = flows_to(ProgramGraphShape::new(4, 3), "fin", "fout");
let names: Vec<&str> = p.buffers.iter().map(|b| b.name()).collect();
assert!(names.contains(&"fin"));
assert!(names.contains(&"fout"));
}
#[test]
fn flows_to_program_uses_non_degenerate_shape() {
let shape = ProgramGraphShape::new(64, 128);
let p = flows_to(shape, "fin", "fout");
let fin_buf = p
.buffers
.iter()
.find(|b| b.name() == "fin")
.expect("Fix: fin buffer");
assert!(
fin_buf.count >= 2,
"bitset_words(64) = 2; count {} suggests degenerate shape",
fin_buf.count
);
}
#[test]
fn flows_to_and_taint_flow_convergence_contracts_match_intentionally() {
let c_flows = crate::harness::convergence_contract("vyre-libs::security::flows_to")
.expect("Fix: flows_to must have a ConvergenceContract");
let c_taint = crate::harness::convergence_contract("vyre-libs::security::taint_flow")
.expect("Fix: taint_flow must have a ConvergenceContract");
assert_eq!(
c_flows.max_iterations, c_taint.max_iterations,
"flows_to and taint_flow MUST share max_iterations: they close the \
same forward-mask fixpoint and a divergence here would silently \
truncate one path while letting the other run to completion."
);
}
#[test]
#[should_panic(expected = "node_count must be positive")]
fn flows_to_zero_node_count_should_panic() {
let _ = flows_to(ProgramGraphShape::new(0, 0), "fin", "fout");
}
#[test]
#[should_panic(expected = "empty buffer name")]
fn flows_to_empty_buffer_name_should_panic() {
let _ = flows_to(ProgramGraphShape::new(4, 3), "", "fout");
}
#[test]
fn flows_to_edge_count_exceeds_actual_edges_traps_in_reference() {
let p = flows_to(ProgramGraphShape::new(4, 10), "fin", "fout");
let to_bytes = |w: &[u32]| vyre_primitives::wire::pack_u32_slice(w);
let inputs = vec![
to_bytes(&[0, 0, 0, 0]), to_bytes(&[0, 1, 2, 3, 3]), to_bytes(&[1, 2, 3]), to_bytes(&[
edge_kind::ASSIGNMENT,
edge_kind::ASSIGNMENT,
edge_kind::ASSIGNMENT,
]),
to_bytes(&[0, 0, 0, 0]), to_bytes(&[0b0001]), to_bytes(&[0b0001]), ];
let values: Vec<vyre_reference::value::Value> = inputs
.into_iter()
.map(vyre_reference::value::Value::from)
.collect();
let error = vyre_reference::reference_eval(&p, &values).expect_err(
"edge_count (10) exceeds actual edges (3) must trap or error in reference_eval",
);
let msg = error.to_string();
assert!(
msg.contains("trap") || msg.contains("Fix:") || msg.contains("edge"),
"flows_to edge-count mismatch error must be actionable: {msg}"
);
}
}