use super::*;
#[path = "reachability_witness_prepared_tests.rs"]
mod prepared_tests;
fn dummy_attrs(n: usize) -> Vec<NodeAttr> {
(0..n as u32)
.map(|i| NodeAttr {
byte_start: i * 10,
byte_end: (i + 1) * 10,
file_idx: 0,
})
.collect()
}
#[test]
fn trivial_self_path() {
let seed = PathSeed {
source_file: "a.c".to_string(),
source_node: 3,
sink_file: "a.c".to_string(),
sink_node: 3,
};
let attrs = dummy_attrs(5);
let files = vec!["a.c".to_string()];
let descs: Vec<String> = (0..5).map(|i| format!("stmt{i}")).collect();
let path = extract_path(
&seed,
&[0xFFFF],
&[],
&[0; 6],
&[],
&[],
&attrs,
&files,
&descs,
"c-c11",
)
.expect("trivial path must reconstruct");
assert_eq!(path.statements.len(), 1);
assert_eq!(path.statements[0].node_id, 3);
}
#[test]
fn no_path_when_endpoints_not_reached() {
let seed = PathSeed {
source_file: "a.c".to_string(),
source_node: 0,
sink_file: "a.c".to_string(),
sink_node: 3,
};
let attrs = dummy_attrs(4);
let files = vec!["a.c".to_string()];
let descs: Vec<String> = (0..4).map(|i| format!("stmt{i}")).collect();
let err = extract_path(
&seed,
&[0b0001],
&[],
&[0, 1, 2, 3, 3],
&[1, 2, 3],
&[edge_kind::ASSIGNMENT; 3],
&attrs,
&files,
&descs,
"c-c11",
)
.expect_err("no path expected");
assert_eq!(err, PathError::NoPath);
}
#[test]
fn witness_graph_rejects_orphan_prefix_edges() {
let attrs = dummy_attrs(2);
let files = vec!["a.c".to_string()];
let descs: Vec<String> = (0..2).map(|i| format!("stmt{i}")).collect();
let err = try_prepare_witness_graph(
&[1, 1, 1],
&[0],
&[edge_kind::ASSIGNMENT],
&attrs,
&files,
&descs,
"c-c11",
)
.expect_err("nonzero first CSR offset must be rejected");
assert!(
matches!(err, PathError::MalformedGraph { ref fix, .. } if fix.contains("edge_offsets[0]")),
"unexpected diagnostic: {err:?}"
);
}
#[test]
fn linear_chain_reconstructs() {
let seed = PathSeed {
source_file: "a.c".to_string(),
source_node: 0,
sink_file: "a.c".to_string(),
sink_node: 3,
};
let attrs = dummy_attrs(4);
let files = vec!["a.c".to_string()];
let descs: Vec<String> = (0..4).map(|i| format!("stmt{i}")).collect();
let edge_offsets = vec![0, 1, 2, 3, 3];
let edge_targets = vec![1u32, 2, 3];
let edge_kind_mask = vec![edge_kind::ASSIGNMENT; 3];
let source_reach = vec![0b1111];
let path = extract_path(
&seed,
&source_reach,
&[],
&edge_offsets,
&edge_targets,
&edge_kind_mask,
&attrs,
&files,
&descs,
"c-c11",
)
.expect("linear-chain path must reconstruct");
let ids: Vec<u32> = path.statements.iter().map(|s| s.node_id).collect();
assert_eq!(ids, vec![0, 1, 2, 3]);
assert_eq!(path.statements[0].file, "a.c");
assert_eq!(path.statements[0].adapter, "c-c11");
}
#[test]
fn ignores_non_ifds_edges() {
let seed = PathSeed {
source_file: "a.c".to_string(),
source_node: 0,
sink_file: "a.c".to_string(),
sink_node: 2,
};
let attrs = dummy_attrs(3);
let files = vec!["a.c".to_string()];
let descs: Vec<String> = (0..3).map(|i| format!("stmt{i}")).collect();
let edge_offsets = vec![0, 1, 2, 2];
let edge_targets = vec![1u32, 2];
let edge_kind_mask = vec![edge_kind::CONTROL, edge_kind::ASSIGNMENT];
let source_reach = vec![0b111];
let err = extract_path(
&seed,
&source_reach,
&[],
&edge_offsets,
&edge_targets,
&edge_kind_mask,
&attrs,
&files,
&descs,
"c-c11",
)
.expect_err("no IFDS-eligible predecessor of 1 → no path");
assert_eq!(err, PathError::NoPath);
}
#[test]
fn rejects_sanitizer_on_path() {
let seed = PathSeed {
source_file: "a.c".to_string(),
source_node: 0,
sink_file: "a.c".to_string(),
sink_node: 3,
};
let attrs = dummy_attrs(4);
let files = vec!["a.c".to_string()];
let descs: Vec<String> = (0..4).map(|i| format!("stmt{i}")).collect();
let edge_offsets = vec![0, 1, 2, 3, 3];
let edge_targets = vec![1u32, 2, 3];
let edge_kind_mask = vec![edge_kind::ASSIGNMENT; 3];
let source_reach = vec![0b1111];
let sanitizer_mask = vec![0b0010]; let err = extract_path(
&seed,
&source_reach,
&sanitizer_mask,
&edge_offsets,
&edge_targets,
&edge_kind_mask,
&attrs,
&files,
&descs,
"c-c11",
)
.expect_err("witness must not cross sanitizer");
assert_eq!(err, PathError::NoPath);
}
#[test]
fn sanitizer_off_path_does_not_block_reconstruction() {
let seed = PathSeed {
source_file: "a.c".to_string(),
source_node: 0,
sink_file: "a.c".to_string(),
sink_node: 2,
};
let attrs = dummy_attrs(6);
let files = vec!["a.c".to_string()];
let descs: Vec<String> = (0..6).map(|i| format!("stmt{i}")).collect();
let edge_offsets = vec![0, 1, 2, 2, 2, 2, 2];
let edge_targets = vec![1u32, 2];
let edge_kind_mask = vec![edge_kind::ASSIGNMENT; 2];
let source_reach = vec![0b111111];
let sanitizer_mask = vec![0b100000]; let path = extract_path(
&seed,
&source_reach,
&sanitizer_mask,
&edge_offsets,
&edge_targets,
&edge_kind_mask,
&attrs,
&files,
&descs,
"c-c11",
)
.expect("off-path sanitizer must not block reconstruction");
assert_eq!(
path.statements
.iter()
.map(|s| s.node_id)
.collect::<Vec<_>>(),
vec![0, 1, 2]
);
}
#[test]
fn depth_exceeded_returns_partial_chain() {
let n = MAX_PATH_DEPTH + 50;
let seed = PathSeed {
source_file: "a.c".to_string(),
source_node: 0,
sink_file: "a.c".to_string(),
sink_node: (n as u32) - 1,
};
let attrs = dummy_attrs(n);
let files = vec!["a.c".to_string()];
let descs: Vec<String> = (0..n).map(|i| format!("stmt{i}")).collect();
let mut edge_offsets: Vec<u32> = (0..=n as u32).collect();
if let Some(last) = edge_offsets.last_mut() {
*last = (n - 1) as u32;
}
let edge_targets: Vec<u32> = (1..n as u32).collect();
let edge_kind_mask: Vec<u32> = vec![edge_kind::ASSIGNMENT; n - 1];
let words = n.div_ceil(32);
let source_reach = vec![u32::MAX; words];
let err = extract_path(
&seed,
&source_reach,
&[],
&edge_offsets,
&edge_targets,
&edge_kind_mask,
&attrs,
&files,
&descs,
"c-c11",
)
.expect_err("path longer than MAX_PATH_DEPTH must surface");
match err {
PathError::DepthExceeded { partial_chain } => {
assert!(partial_chain.len() >= MAX_PATH_DEPTH);
assert_eq!(partial_chain[0], (n as u32) - 1);
}
other => panic!("expected DepthExceeded, got {other:?}"),
}
}
#[test]
fn out_of_bounds_seed_surfaces_as_error() {
let seed = PathSeed {
source_file: "a.c".to_string(),
source_node: 0,
sink_file: "a.c".to_string(),
sink_node: 9999, };
let attrs = dummy_attrs(4);
let files = vec!["a.c".to_string()];
let descs: Vec<String> = (0..4).map(|i| format!("stmt{i}")).collect();
let err = extract_path(
&seed,
&[u32::MAX; 4],
&[],
&[0, 1, 2, 3, 3],
&[1, 2, 3],
&[edge_kind::ASSIGNMENT; 3],
&attrs,
&files,
&descs,
"c-c11",
)
.expect_err("OOB seed must surface as error, not panic");
match err {
PathError::NodeOutOfBounds { node, node_count } => {
assert_eq!(node, 9999);
assert_eq!(node_count, 4);
}
other => panic!("expected NodeOutOfBounds, got {other:?}"),
}
}
#[test]
fn per_source_mask_drives_path_choice() {
let attrs = dummy_attrs(5);
let files = vec!["a.c".to_string()];
let descs: Vec<String> = (0..5).map(|i| format!("stmt{i}")).collect();
let edge_offsets = vec![0, 1, 2, 3, 4, 4];
let edge_targets = vec![2u32, 3, 4, 4];
let edge_kind_mask = vec![edge_kind::ASSIGNMENT; 4];
let source_a_reach = vec![0b10101];
let seed_a = PathSeed {
source_file: "a.c".to_string(),
source_node: 0,
sink_file: "a.c".to_string(),
sink_node: 4,
};
let path_a = extract_path(
&seed_a,
&source_a_reach,
&[],
&edge_offsets,
&edge_targets,
&edge_kind_mask,
&attrs,
&files,
&descs,
"c-c11",
)
.expect("source A's path must reconstruct");
assert_eq!(
path_a
.statements
.iter()
.map(|s| s.node_id)
.collect::<Vec<_>>(),
vec![0, 2, 4]
);
}
#[test]
fn exploded_reachability_decodes_to_statement_mask() {
let nodes = vec![
vyre_primitives::graph::exploded::encode_node(0, 0, 7).expect("valid node"),
vyre_primitives::graph::exploded::encode_node(0, 1, 7).expect("valid node"),
vyre_primitives::graph::exploded::encode_node(1, 0, 3).expect("valid node"),
];
let block_to_statement = vec![2, 5, 8, 13];
let mask = exploded_reachability_to_statement_mask(&nodes, 2, &block_to_statement, Some(7), 16)
.expect("valid exploded reachability must decode");
assert!(super::bit_is_set(&mask, 2));
assert!(super::bit_is_set(&mask, 5));
assert!(!super::bit_is_set(&mask, 8));
assert!(!super::bit_is_set(&mask, 13));
}
#[test]
fn exploded_reachability_rejects_missing_block_map_slot() {
let node = vyre_primitives::graph::exploded::encode_node(2, 3, 0).expect("valid node");
let err = exploded_reachability_to_statement_mask(&[node], 4, &[0, 1], None, 8)
.expect_err("missing block map slot must surface");
assert_eq!(
err,
ExplodedDecodeError::BlockOutOfBounds {
proc_id: 2,
block_id: 3,
slot: 11,
slot_count: 2,
}
);
}
#[test]
fn exploded_reachability_rejects_statement_oob() {
let node = vyre_primitives::graph::exploded::encode_node(0, 0, 0).expect("valid node");
let err = exploded_reachability_to_statement_mask(&[node], 1, &[9], None, 4)
.expect_err("statement map must stay inside statement table");
assert_eq!(
err,
ExplodedDecodeError::StatementOutOfBounds {
statement: 9,
statement_count: 4,
}
);
}