use rand::prelude::*;
use rand::rngs::StdRng;
use std::fs;
use std::io::Write;
use std::path::Path;
const RNG_SEED: u64 = 42;
const CSR_COUNT: usize = 250;
const IFDS_COUNT: usize = 250;
const DOM_COUNT: usize = 250;
const CROSS_COUNT: usize = 250;
#[allow(dead_code)]
const ADVERSARIAL_COUNT: usize = 50;
const EDGE_KINDS: &[u32] = &[
1 << 0, 1 << 1, 1 << 2, 1 << 3, 1 << 4, 1 << 5, 1 << 6, 1 << 7, 1 << 8, 1 << 9, ];
const IFDS_REACH_MASK: u32 =
(1 << 0) | (1 << 1) | (1 << 2) | (1 << 3) | (1 << 5) | (1 << 6) | (1 << 7) | (1 << 8);
fn bitset_words(node_count: u32) -> usize {
node_count.div_ceil(32) as usize
}
fn write_u32_slice(path: &Path, words: &[u32]) -> std::io::Result<()> {
let mut file = fs::File::create(path)?;
for word in words {
file.write_all(&word.to_le_bytes())?;
}
Ok(())
}
fn write_seed(path: &Path, header: &[u32], arrays: &[&[u32]]) -> std::io::Result<()> {
let mut words: Vec<u32> = Vec::new();
words.extend_from_slice(header);
for arr in arrays {
words.extend_from_slice(arr);
}
write_u32_slice(path, &words)
}
fn density_for_size(node_count: u32) -> f64 {
match node_count {
0 => 0.0,
1 => 0.0,
10 => 0.2,
100 => 0.05,
1000 => 0.01,
10000 => 0.002,
_ => 0.01,
}
}
fn random_csr<R: Rng>(
rng: &mut R,
node_count: u32,
edge_density: f64,
) -> (Vec<u32>, Vec<u32>, Vec<u32>) {
let n = node_count as usize;
let max_edges = (n * n).min(100_000);
let mut edges: Vec<(u32, u32)> = Vec::new();
if n > 0 && edge_density > 0.0 {
let total_possible = n.saturating_mul(n);
let desired = ((total_possible as f64) * edge_density) as usize;
let desired = desired.min(max_edges);
if total_possible <= 1_000_000 && edge_density > 0.3 {
for src in 0..node_count {
for dst in 0..node_count {
if rng.random_bool(edge_density) {
edges.push((src, dst));
}
}
}
} else {
while edges.len() < desired {
let src = rng.random_range(0..node_count);
let dst = rng.random_range(0..node_count);
edges.push((src, dst));
}
}
}
edges.sort_by(|a, b| a.0.cmp(&b.0).then(a.1.cmp(&b.1)));
edges.dedup();
let mut offsets = vec![0u32; n + 1];
let mut targets = Vec::new();
let mut masks = Vec::new();
for (src, dst) in &edges {
offsets[*src as usize + 1] += 1;
targets.push(*dst);
masks.push(*EDGE_KINDS.choose(rng).unwrap_or(&1));
}
for i in 1..=n {
offsets[i] += offsets[i - 1];
}
(offsets, targets, masks)
}
fn random_connected_cfg<R: Rng>(
rng: &mut R,
node_count: u32,
edge_density: f64,
) -> (u32, Vec<u32>, Vec<u32>) {
let n = node_count as usize;
if n == 0 {
return (0, Vec::new(), Vec::new());
}
let entry = 0u32;
let mut edges: Vec<(u32, u32)> = Vec::new();
for node in 1..node_count {
let parent = rng.random_range(0..node);
edges.push((parent, node));
}
let max_extra = (n.saturating_mul(n) as f64 * edge_density) as usize;
let max_extra = max_extra.min(100_000);
let target_len = edges.len() + max_extra;
if n <= 1000 {
for src in 0..node_count {
for dst in 0..node_count {
if src == dst {
continue;
}
if rng.random_bool(edge_density) {
edges.push((src, dst));
}
}
}
} else {
while edges.len() < target_len {
let src = rng.random_range(0..node_count);
let dst = rng.random_range(0..node_count);
if src != dst {
edges.push((src, dst));
}
}
}
edges.sort_by(|a, b| a.0.cmp(&b.0).then(a.1.cmp(&b.1)));
edges.dedup();
let mut offsets = vec![0u32; n + 1];
let mut targets = Vec::new();
for (src, dst) in &edges {
offsets[*src as usize + 1] += 1;
targets.push(*dst);
}
for i in 1..=n {
offsets[i] += offsets[i - 1];
}
(entry, offsets, targets)
}
fn compute_dominators(
node_count: u32,
entry: u32,
edge_offsets: &[u32],
edge_targets: &[u32],
) -> Vec<u32> {
let n = node_count as usize;
if n == 0 {
return Vec::new();
}
let words = bitset_words(node_count);
let entry_usize = entry as usize;
let mut dom: Vec<u32> = vec![0u32; n * words];
let tail_mask = if n % 32 == 0 {
u32::MAX
} else {
(1u32 << (n % 32)) - 1
};
for i in 0..n {
let base = i * words;
for w in 0..words {
dom[base + w] = u32::MAX;
}
if words > 0 {
dom[base + words - 1] = tail_mask;
}
}
for w in 0..words {
dom[entry_usize * words + w] = 0;
}
dom[entry_usize * words + entry_usize / 32] = 1u32 << (entry % 32);
let mut preds: Vec<Vec<u32>> = vec![Vec::new(); n];
for src in 0..node_count {
let start = edge_offsets[src as usize] as usize;
let end = edge_offsets[(src + 1) as usize] as usize;
for &target in edge_targets.iter().take(end).skip(start) {
let dst = target as usize;
if dst < n {
preds[dst].push(src);
}
}
}
let mut order = Vec::new();
let mut visited = vec![false; n];
let mut stack = vec![(entry, 0usize)];
visited[entry_usize] = true;
while let Some((node, idx)) = stack.last().copied() {
let start = edge_offsets[node as usize] as usize;
let end = edge_offsets[(node + 1) as usize] as usize;
let succs = &edge_targets[start..end];
if idx < succs.len() {
if let Some(last) = stack.last_mut() {
last.1 += 1;
} else {
break;
}
let nxt = succs[idx];
let nxt_idx = nxt as usize;
if nxt_idx < n && !visited[nxt_idx] {
visited[nxt_idx] = true;
stack.push((nxt, 0));
}
} else {
order.push(node);
stack.pop();
}
}
for (i, &vis) in visited.iter().enumerate() {
if !vis {
order.push(i as u32);
}
}
order.reverse();
let mut changed = true;
let mut iter_cap = n.saturating_mul(2).saturating_add(8).max(8);
let mut new_row = vec![0u32; words];
while changed && iter_cap > 0 {
changed = false;
iter_cap -= 1;
for &node in &order {
let i = node as usize;
if i == entry_usize {
continue;
}
let mut have_pred = false;
for &p in &preds[i] {
let p_idx = p as usize;
let p_start = p_idx * words;
let p_end = p_start + words;
let p_row = &dom[p_start..p_end];
if have_pred {
for (acc, w) in new_row.iter_mut().zip(p_row.iter()) {
*acc &= *w;
}
} else {
new_row.copy_from_slice(p_row);
have_pred = true;
}
}
if !have_pred {
new_row.fill(0);
new_row[entry_usize / 32] = 1u32 << (entry % 32);
} else {
new_row[i / 32] |= 1u32 << (i % 32);
}
let row_start = i * words;
let row_end = row_start + words;
let row = &mut dom[row_start..row_end];
if row != &new_row[..] {
row.copy_from_slice(&new_row);
changed = true;
}
}
}
dom
}
fn compute_forward_reach(
node_count: u32,
edge_offsets: &[u32],
edge_targets: &[u32],
edge_masks: &[u32],
allowed_mask: u32,
seeds: &[u32],
) -> Vec<u32> {
let n = node_count as usize;
let words = bitset_words(node_count);
let mut reached = vec![0u32; words];
for &seed in seeds {
let idx = seed as usize;
if idx < n {
reached[idx / 32] |= 1u32 << (idx % 32);
}
}
if n % 32 != 0 {
let keep = (1u32 << (n % 32)) - 1;
if let Some(last) = reached.last_mut() {
*last &= keep;
}
}
let mut changed = true;
while changed {
changed = false;
for node in 0..n {
if reached[node / 32] & (1u32 << (node % 32)) == 0 {
continue;
}
let start = edge_offsets[node] as usize;
let end = edge_offsets[node + 1] as usize;
for edge in start..end {
if edge >= edge_masks.len() {
break;
}
let kind = edge_masks[edge];
if (kind & allowed_mask) == 0 {
continue;
}
let target = edge_targets[edge] as usize;
if target < n {
let word = target / 32;
let bit = 1u32 << (target % 32);
if reached[word] & bit == 0 {
reached[word] |= bit;
changed = true;
}
}
}
}
}
reached
}
fn generate_csr_seeds<R: Rng>(rng: &mut R, out_dir: &Path) -> std::io::Result<Vec<String>> {
let sizes = [0u32, 1, 10, 100, 1000, 10000];
let mut files = Vec::new();
let per_size = CSR_COUNT / sizes.len();
let mut remainder = CSR_COUNT % sizes.len();
for &size in &sizes {
let count = per_size
+ if remainder > 0 {
remainder -= 1;
1
} else {
0
};
for i in 0..count {
let base_density = density_for_size(size);
let density = if base_density > 0.0 {
base_density * rng.random_range(0.5..1.5)
} else {
0.0
};
let (offsets, targets, masks) = random_csr(rng, size, density);
let header = vec![size, targets.len() as u32, offsets.len() as u32];
let fname = format!("csr_{size}n_{i:03}.bin");
let path = out_dir.join(&fname);
write_seed(&path, &header, &[&offsets, &targets, &masks])?;
files.push(fname);
}
}
Ok(files)
}
fn generate_ifds_seeds<R: Rng>(rng: &mut R, out_dir: &Path) -> std::io::Result<Vec<String>> {
let sizes = [0u32, 1, 10, 100, 1000, 10000];
let mut files = Vec::new();
let per_size = IFDS_COUNT / sizes.len();
let mut remainder = IFDS_COUNT % sizes.len();
for &size in &sizes {
let count = per_size
+ if remainder > 0 {
remainder -= 1;
1
} else {
0
};
for i in 0..count {
let base_density = density_for_size(size);
let density = if base_density > 0.0 {
base_density * rng.random_range(0.5..1.5)
} else {
0.0
};
let (offsets, targets, _masks) = random_csr(rng, size, density);
let seed_count = if size == 0 {
0
} else {
rng.random_range(1..=(size).min(16))
};
let mut seed_nodes: Vec<u32> = (0..size).collect();
seed_nodes.shuffle(rng);
seed_nodes.truncate(seed_count as usize);
seed_nodes.sort_unstable();
let summary_count = if size == 0 {
0
} else {
rng.random_range(0..=(size).min(8))
};
let mut summary_pairs = Vec::new();
for _ in 0..summary_count {
let src = rng.random_range(0..size.max(1));
let dst = rng.random_range(0..size.max(1));
summary_pairs.push(src);
summary_pairs.push(dst);
}
let header = vec![
size,
targets.len() as u32,
seed_nodes.len() as u32,
summary_count,
];
let fname = format!("ifds_{size}n_{i:03}.bin");
let path = out_dir.join(&fname);
write_seed(
&path,
&header,
&[&offsets, &targets, &seed_nodes, &summary_pairs],
)?;
files.push(fname);
}
}
Ok(files)
}
fn generate_dom_seeds<R: Rng>(rng: &mut R, out_dir: &Path) -> std::io::Result<Vec<String>> {
let sizes = [0u32, 1, 10, 100, 1000, 10000];
let mut files = Vec::new();
let per_size = DOM_COUNT / sizes.len();
let mut remainder = DOM_COUNT % sizes.len();
for &size in &sizes {
let count = per_size
+ if remainder > 0 {
remainder -= 1;
1
} else {
0
};
for i in 0..count {
let base_density = density_for_size(size);
let density = if base_density > 0.0 {
base_density * rng.random_range(0.5..1.5)
} else {
0.0
};
let (entry, offsets, targets) = random_connected_cfg(rng, size, density);
let header = vec![size, targets.len() as u32, entry];
let fname = format!("dom_{size}n_{i:03}.bin");
let path = out_dir.join(&fname);
write_seed(&path, &header, &[&offsets, &targets])?;
files.push(fname);
}
}
Ok(files)
}
fn generate_cross_seeds<R: Rng>(rng: &mut R, out_dir: &Path) -> std::io::Result<Vec<String>> {
let sizes = [0u32, 1, 10, 100, 1000, 10000];
let mut files = Vec::new();
let per_size = CROSS_COUNT / sizes.len();
let mut remainder = CROSS_COUNT % sizes.len();
for &size in &sizes {
let count = per_size
+ if remainder > 0 {
remainder -= 1;
1
} else {
0
};
for i in 0..count {
let base_density = density_for_size(size);
let density = if base_density > 0.0 {
base_density * rng.random_range(0.5..1.5)
} else {
0.0
};
let (entry, offsets, targets) = random_connected_cfg(rng, size, density);
let mut masks = Vec::new();
for _ in 0..targets.len() {
masks.push(*EDGE_KINDS.choose(rng).unwrap_or(&1));
}
let seed_count = if size == 0 {
0
} else {
rng.random_range(1..=(size).min(16))
};
let mut seed_nodes: Vec<u32> = (0..size).collect();
seed_nodes.shuffle(rng);
seed_nodes.truncate(seed_count as usize);
seed_nodes.sort_unstable();
let dom_output = compute_dominators(size, entry, &offsets, &targets);
let reaching_output = compute_forward_reach(
size,
&offsets,
&targets,
&masks,
1 << 9, &[entry],
);
let ifds_output = compute_forward_reach(
size,
&offsets,
&targets,
&masks,
IFDS_REACH_MASK,
&seed_nodes,
);
let header = vec![size, targets.len() as u32, entry, seed_nodes.len() as u32];
let fname = format!("cross_{size}n_{i:03}.bin");
let path = out_dir.join(&fname);
write_seed(
&path,
&header,
&[
&offsets,
&targets,
&masks,
&seed_nodes,
&dom_output,
&reaching_output,
&ifds_output,
],
)?;
files.push(fname);
}
}
Ok(files)
}
fn generate_adversarial_seeds<R: Rng>(
_rng: &mut R,
out_dir: &Path,
) -> std::io::Result<Vec<String>> {
let mut files = Vec::new();
for i in 0..15 {
let variant = i % 5;
let header: Vec<u32>;
let arrays: Vec<Vec<u32>>;
match variant {
0 => {
header = vec![5, 3, 2];
arrays = vec![vec![0, 1], vec![1, 2, 3], vec![1, 1, 1]];
}
1 => {
header = vec![3, 3, 4];
arrays = vec![vec![0, 2, 1, 3], vec![1, 2, 3], vec![1, 1, 1]];
}
2 => {
header = vec![3, 2, 4];
arrays = vec![vec![0, 1, 2, 2], vec![99, 1], vec![1, 1]];
}
3 => {
header = vec![2, 1, 3];
arrays = vec![vec![0, 1, 1], vec![1], vec![1, 2]];
}
_ => {
header = vec![0, 1, 1];
arrays = vec![vec![0], vec![0], vec![0]];
}
}
let fname = format!("adversarial_csr_{i:03}.bin");
let path = out_dir.join(&fname);
let refs: Vec<&[u32]> = arrays.iter().map(|v| v.as_slice()).collect();
write_seed(&path, &header, &refs)?;
files.push(fname);
}
for i in 0..15 {
let variant = i % 5;
let header: Vec<u32>;
let arrays: Vec<Vec<u32>>;
match variant {
0 => {
header = vec![u32::MAX, 0, 0, 0];
arrays = vec![vec![0], vec![], vec![], vec![]];
}
1 => {
header = vec![1, u32::MAX, 0, 0];
arrays = vec![vec![0, 0], vec![], vec![], vec![]];
}
2 => {
header = vec![2, 1, 5, 0];
arrays = vec![vec![0, 1, 1], vec![1], vec![0, 1, 2, 3, 4], vec![]];
}
3 => {
header = vec![2, 1, 0, 2];
arrays = vec![vec![0, 1, 1], vec![1], vec![], vec![0, 99, 1, 99]];
}
_ => {
header = vec![0, 0, 0, 0];
arrays = vec![vec![0], vec![], vec![], vec![]];
}
}
let fname = format!("adversarial_ifds_{i:03}.bin");
let path = out_dir.join(&fname);
let refs: Vec<&[u32]> = arrays.iter().map(|v| v.as_slice()).collect();
write_seed(&path, &header, &refs)?;
files.push(fname);
}
for i in 0..10 {
let variant = i % 4;
let header: Vec<u32>;
let arrays: Vec<Vec<u32>>;
match variant {
0 => {
header = vec![3, 2, 99];
arrays = vec![vec![0, 1, 2, 2], vec![1, 2]];
}
1 => {
header = vec![3, 3, 0];
arrays = vec![vec![0, 3, 1, 3], vec![1, 2, 3]];
}
2 => {
header = vec![3, 2, 0];
arrays = vec![vec![0, 1, 1, 1], vec![99, 99]];
}
_ => {
header = vec![0, 1, 0];
arrays = vec![vec![0], vec![0]];
}
}
let fname = format!("adversarial_dom_{i:03}.bin");
let path = out_dir.join(&fname);
let refs: Vec<&[u32]> = arrays.iter().map(|v| v.as_slice()).collect();
write_seed(&path, &header, &refs)?;
files.push(fname);
}
for i in 0..10 {
let variant = i % 4;
let header: Vec<u32>;
let arrays: Vec<Vec<u32>>;
match variant {
0 => {
header = vec![1, 1, 0, 1];
arrays = vec![
vec![0, 1],
vec![0],
vec![1 << 9],
vec![0],
vec![1], vec![1], vec![1], ];
}
1 => {
header = vec![2, 0, 0, 1];
arrays = vec![
vec![0, 0, 0],
vec![],
vec![],
vec![0],
vec![1, 0, 0, 1], vec![1], vec![1], ];
}
2 => {
header = vec![2, 1, 0, 1];
arrays = vec![
vec![0, 1, 1],
vec![0],
vec![1 << 9],
vec![1],
vec![1, 0, 0, 1], vec![1], vec![0, 1 << 1], ];
}
_ => {
header = vec![0, 0, 0, 0];
arrays = vec![vec![0], vec![], vec![], vec![], vec![], vec![], vec![]];
}
}
let fname = format!("adversarial_cross_{i:03}.bin");
let path = out_dir.join(&fname);
let refs: Vec<&[u32]> = arrays.iter().map(|v| v.as_slice()).collect();
write_seed(&path, &header, &refs)?;
files.push(fname);
}
Ok(files)
}
fn main() -> std::io::Result<()> {
let out_dir = Path::new("tests/corpus/seeds");
fs::create_dir_all(out_dir)?;
let mut rng = StdRng::seed_from_u64(RNG_SEED);
let csr_files = generate_csr_seeds(&mut rng, out_dir)?;
let ifds_files = generate_ifds_seeds(&mut rng, out_dir)?;
let dom_files = generate_dom_seeds(&mut rng, out_dir)?;
let cross_files = generate_cross_seeds(&mut rng, out_dir)?;
let adversarial_files = generate_adversarial_seeds(&mut rng, out_dir)?;
let total = csr_files.len()
+ ifds_files.len()
+ dom_files.len()
+ cross_files.len()
+ adversarial_files.len();
let manifest_path = out_dir.join("manifest.json");
let manifest = serde_json::json!({
"seed_count": total,
"categories": {
"csr": { "count": csr_files.len(), "files": csr_files },
"ifds": { "count": ifds_files.len(), "files": ifds_files },
"dom": { "count": dom_files.len(), "files": dom_files },
"cross": { "count": cross_files.len(), "files": cross_files },
"adversarial": { "count": adversarial_files.len(), "files": adversarial_files },
},
"rng_seed": RNG_SEED,
"manifest_path": manifest_path.to_str(),
"output_directory": out_dir.to_str(),
});
let json = serde_json::to_string_pretty(&manifest).map_err(std::io::Error::other)?;
fs::write(&manifest_path, format!("{json}\n"))?;
println!("{json}");
Ok(())
}