use vyre_foundation::ir::Program;
use vyre_primitives::graph::program_graph::ProgramGraphShape;
use vyre_primitives::predicate::edge_kind;
use crate::security::flow_composition::{
dominance_fixture_expected, dominance_fixture_inputs, security_flow_program, FlowPredicate,
SecurityFlowOptions, FLOW_MAX_ITERATIONS,
};
pub(crate) const OP_ID: &str = "vyre-libs::security::bounded_by_comparison";
#[must_use]
pub fn bounded_by_comparison(
shape: ProgramGraphShape,
frontier_in: &str,
frontier_out: &str,
) -> Program {
security_flow_program(SecurityFlowOptions::reach(
OP_ID,
shape,
FlowPredicate::backward(edge_kind::DOMINANCE),
frontier_in,
frontier_out,
))
}
inventory::submit! {
vyre_foundation::operation::OperationRegistration::library(
OP_ID,
|| bounded_by_comparison(ProgramGraphShape::new(4, 4), "fin", "fout"),
Some(dominance_fixture_inputs),
Some(dominance_fixture_expected),
)
.with_category("security")
}
inventory::submit! {
crate::operation_catalog::ConvergenceContract {
op_id: OP_ID,
max_iterations: FLOW_MAX_ITERATIONS,
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::security::flow_composition::diamond_dominance_tree;
use vyre_primitives::graph::csr_backward_traverse::cpu_ref;
#[test]
fn bounded_by_comparison_mask_is_dominance_only() {
let _p = bounded_by_comparison(ProgramGraphShape::new(4, 4), "fin", "fout");
assert_eq!(edge_kind::DOMINANCE & edge_kind::ASSIGNMENT, 0);
assert_eq!(edge_kind::DOMINANCE & edge_kind::CONTROL, 0);
}
#[test]
fn bounded_by_comparison_backward_step_reaches_ancestors() {
let (node_count, offsets, targets, masks) = diamond_dominance_tree();
let frontier_in = vec![0b1000]; let out = cpu_ref(
node_count,
&offsets,
&targets,
&masks,
&frontier_in,
edge_kind::DOMINANCE,
);
assert_eq!(out[0], 0b0110, "backward from 3 must reach 1 and 2");
}
#[test]
fn bounded_by_comparison_program_emits_frontier_buffers() {
let _p = bounded_by_comparison(ProgramGraphShape::new(4, 4), "fin", "fout");
let names: Vec<&str> = _p.buffers().iter().map(|b| b.name()).collect();
assert!(names.contains(&"fin"));
assert!(names.contains(&"fout"));
}
#[test]
fn bounded_by_comparison_deep_chain_reaches_all_ancestors() {
let node_count = 10u32;
let mut offsets = vec![0u32; (node_count + 1) as usize];
let mut targets = Vec::new();
let mut masks = Vec::new();
for i in 0..node_count {
offsets[i as usize] = i;
if i + 1 < node_count {
targets.push(i + 1);
masks.push(edge_kind::DOMINANCE);
}
}
offsets[node_count as usize] = node_count.saturating_sub(1);
let mut accumulated = vec![0u32; 1];
accumulated[0] = 1 << (node_count - 1);
for _ in 0..node_count {
let out = cpu_ref(
node_count,
&offsets,
&targets,
&masks,
&accumulated,
edge_kind::DOMINANCE,
);
let new_accumulated: Vec<u32> =
accumulated.iter().zip(&out).map(|(a, b)| a | b).collect();
if new_accumulated == accumulated {
break;
}
accumulated = new_accumulated;
}
let expected = (1u32 << node_count) - 1;
assert_eq!(
accumulated[0],
expected,
"backward reachability from node {} must reach all ancestors in a {}-node chain; \
if max_iterations truncates, this test fails",
node_count - 1,
node_count
);
let contract = crate::operation_catalog::convergence_contract(OP_ID)
.expect("Fix: bounded_by_comparison must have a ConvergenceContract");
assert!(
contract.max_iterations >= node_count,
"ConvergenceContract max_iterations ({}) must be >= chain depth ({}) to avoid silent truncation",
contract.max_iterations, node_count
);
}
#[test]
#[should_panic(expected = "node_count must be positive")]
fn bounded_by_comparison_zero_node_count_should_panic() {
let _ = bounded_by_comparison(ProgramGraphShape::new(0, 0), "fin", "fout");
}
#[test]
#[should_panic(expected = "empty buffer name")]
fn bounded_by_comparison_empty_buffer_name_should_panic() {
let _ = bounded_by_comparison(ProgramGraphShape::new(4, 4), "", "fout");
}
}