use vyre::ir::Program;
use vyre_primitives::graph::program_graph::ProgramGraphShape;
use vyre_primitives::predicate::edge_kind;
use crate::security::flow_composition::dataflow_reach_program;
pub(crate) 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)],
);
dataflow_reach_program(OP_ID, 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 {
dataflow_reach_program(
OP_ID,
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_cpu_fixture_excludes_control_and_dominance_false_positive_edges() {
let edge_offsets = [0, 2, 3, 3, 3];
let edge_targets = [1, 3, 2];
let edge_kind_mask = [
edge_kind::CONTROL,
edge_kind::ASSIGNMENT,
edge_kind::DOMINANCE,
];
let out = crate::security::flow_composition::dataflow_reach_step_cpu_ref(
4,
&edge_offsets,
&edge_targets,
&edge_kind_mask,
&[0b0001],
);
assert_eq!(
out,
vec![0b1000],
"Fix: flows_to must follow assignment dataflow edges while excluding CONTROL/DOMINANCE false-positive paths."
);
}
#[test]
fn alias_only_fixture_keeps_copy_and_realloc_but_excludes_dup_call_edges() {
let edge_offsets = [0, 5, 5, 5, 5, 5, 5];
let edge_targets = [1, 2, 3, 4, 5];
let edge_kind_mask = [
edge_kind::CALL_ARG,
edge_kind::ASSIGNMENT,
edge_kind::ALIAS,
edge_kind::MUT_REF,
edge_kind::RETURN,
];
let alias_only = vyre_primitives::graph::csr_forward_traverse::cpu_ref(
6,
&edge_offsets,
&edge_targets,
&edge_kind_mask,
&[0b000001],
ALIAS_PROPAGATION_MASK,
);
let full_flow = vyre_primitives::graph::csr_forward_traverse::cpu_ref(
6,
&edge_offsets,
&edge_targets,
&edge_kind_mask,
&[0b000001],
FLOWS_TO_MASK,
);
assert_eq!(
alias_only,
vec![0b011100],
"Fix: alias-only propagation should keep direct copy/alias/realloc edges and reject dup-style CALL_ARG/RETURN taint."
);
assert_eq!(
full_flow,
vec![0b111110],
"Fix: full flows_to should still carry regular source-to-sink CALL_ARG/RETURN dataflow."
);
}
#[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}"
);
}
}