weirflow 0.1.0

GPU-first dataflow analysis primitives for Vyre and Santh compiler pipelines.
Documentation
//! Should-panic garden: cross-module panic-path and error-path tests for Weir.
//!
//! Every test in this file exercises a panic or error branch that is not
//! covered by inline unit tests.  The goal is deterministic failure on bad
//! inputs, not allocation-failure paths (those require a custom allocator).

use std::collections::{HashMap, HashSet};
use weir::graph_layout::{CsrGraph, LinearDomain};
use weir::loop_sum::try_loop_summarize_with_count;
use weir::reachability_witness::{
    extract_path, extract_path_prepared, try_prepare_witness_graph, NodeAttr, PathSeed,
};
use weir::ssa::{compute_dominators, try_compute_dominators, Block, Cfg};

// ---------------------------------------------------------------------------
// Helpers
// ---------------------------------------------------------------------------

fn empty_attrs(count: usize) -> Vec<NodeAttr> {
    (0..count)
        .map(|_| NodeAttr {
            byte_start: 0,
            byte_end: 0,
            file_idx: 0,
        })
        .collect()
}

fn empty_strings(count: usize) -> Vec<String> {
    (0..count).map(|_| String::new()).collect()
}

// ---------------------------------------------------------------------------
// SSA dominators, error paths
// ---------------------------------------------------------------------------

#[test]
fn ssa_try_compute_dominators_rejects_missing_entry() {
    let mut blocks = HashMap::new();
    blocks.insert(
        1u32,
        Block {
            id: 1,
            preds: vec![],
            succs: vec![],
            defs: HashSet::new(),
            uses: HashSet::new(),
        },
    );
    let err = try_compute_dominators(&Cfg { entry: 0, blocks }).expect_err("missing entry");
    assert!(err.contains("entry block not found"), "{err}");
}

#[test]
fn ssa_compute_dominators_rejects_missing_entry() {
    let mut blocks = HashMap::new();
    blocks.insert(
        1u32,
        Block {
            id: 1,
            preds: vec![],
            succs: vec![],
            defs: HashSet::new(),
            uses: HashSet::new(),
        },
    );
    let err = compute_dominators(&Cfg { entry: 0, blocks }).expect_err("missing entry");
    assert!(
        err.contains("entry block not found") || err.contains("SSA dominator construction failed"),
        "{err}"
    );
}

// ---------------------------------------------------------------------------
// Graph layout, error paths
// ---------------------------------------------------------------------------

#[test]
fn csr_graph_validate_rejects_nonzero_first_offset() {
    let err = CsrGraph::new(2, &[1, 1, 1], &[0], &[0])
        .validate("garden")
        .expect_err("nonzero first offset");
    assert!(err.contains("edge_offsets[0]"), "{err}");
}

#[test]
fn csr_graph_validate_rejects_zero_node_graph_with_edges() {
    let err = CsrGraph::new(0, &[1], &[0], &[0])
        .validate("garden")
        .expect_err("zero-node with edges");
    assert!(err.contains("edge_offsets[0]"), "{err}");
}

#[test]
fn csr_graph_validate_rejects_target_out_of_domain() {
    let err = CsrGraph::new(2, &[0, 1, 1], &[2], &[0])
        .validate("garden")
        .expect_err("target OOB");
    assert!(err.contains("targets node 2"), "{err}");
}

#[test]
fn csr_graph_validate_rejects_mismatched_mask_len() {
    let err = CsrGraph::new(2, &[0, 1, 1], &[0], &[0, 0])
        .validate("garden")
        .expect_err("mask mismatch");
    assert!(err.contains("edge_kind_mask"), "{err}");
}

#[test]
fn csr_graph_normalize_rejects_non_monotonic_offsets() {
    // 3 nodes, final offset = 1, edge arrays have 1 element.
    // Offsets decrease at node 1: [0, 1, 0, 1].
    let err = CsrGraph::new(3, &[0, 1, 0, 1], &[0], &[0])
        .normalize("garden")
        .expect_err("non-monotonic");
    assert!(err.contains("not monotonic"), "{err}");
}

#[test]
fn csr_graph_normalize_rejects_target_out_of_bounds() {
    let err = CsrGraph::new(2, &[0, 1, 1], &[2], &[0])
        .normalize("garden")
        .expect_err("target OOB");
    assert!(err.contains("targets node 2"), "{err}");
}

#[test]
fn linear_domain_scalar_slots_rejects_overflow() {
    let err = LinearDomain::new(u32::MAX)
        .scalar_slots("garden", 2)
        .expect_err("overflow");
    assert!(err.contains("overflows"), "{err}");
}

// ---------------------------------------------------------------------------
// Loop summarize, should_panic
// ---------------------------------------------------------------------------

#[test]
fn loop_summarize_with_count_rejects_overflow() {
    let err = try_loop_summarize_with_count("cfg", "ranges", "out", 1 << 31)
        .expect_err("overflowing loop summarize count must fail");
    assert!(
        err.contains("loop_summarize var_count=2147483648 overflows u32 buffer slots"),
        "{err}"
    );
}

// ---------------------------------------------------------------------------
// Reachability witness, legacy panic-input contracts on the fallible API
// ---------------------------------------------------------------------------

#[test]
fn try_prepare_witness_graph_rejects_malformed_csr_legacy_panic_input() {
    let attrs = empty_attrs(1);
    let files = empty_strings(1);
    let descs = empty_strings(1);
    let err = try_prepare_witness_graph(&[0, 0], &[0], &[0], &attrs, &files, &descs, "test")
        .expect_err("malformed CSR");
    let msg = format!("{err:?}");
    assert!(msg.contains("EdgeArrayLengthMismatch"), "{msg}");
}

#[test]
fn try_prepare_witness_graph_rejects_nonzero_first_offset_legacy_panic_input() {
    let attrs = empty_attrs(1);
    let files = empty_strings(1);
    let descs = empty_strings(1);
    let err = try_prepare_witness_graph(&[1, 1], &[0], &[0], &attrs, &files, &descs, "test")
        .expect_err("nonzero first offset");
    let msg = format!("{err:?}");
    assert!(msg.contains("NonZeroInitialOffset"), "{msg}");
}

#[test]
fn try_prepare_witness_graph_rejects_non_monotonic_offsets_legacy_panic_input() {
    let attrs = empty_attrs(2);
    let files = empty_strings(1);
    let descs = empty_strings(2);
    let err = try_prepare_witness_graph(&[0, 2, 1], &[0, 1], &[0, 0], &attrs, &files, &descs, "test")
        .expect_err("non-monotonic offsets");
    let msg = format!("{err:?}");
    assert!(msg.contains("NonMonotonicOffsets"), "{msg}");
}

// ---------------------------------------------------------------------------
// Reachability witness, error paths
// ---------------------------------------------------------------------------

#[test]
fn try_prepare_witness_graph_rejects_invalid_offset_count() {
    let attrs = empty_attrs(1);
    let files = empty_strings(1);
    let descs = empty_strings(1);
    // 1 node needs 2 offsets; pass only 1.
    let err = try_prepare_witness_graph(&[0], &[0], &[0], &attrs, &files, &descs, "test")
        .expect_err("invalid offsets");
    let msg = format!("{err:?}");
    assert!(msg.contains("InvalidOffsetCount"), "{msg}");
}

#[test]
fn try_prepare_witness_graph_rejects_nonzero_first_offset() {
    let attrs = empty_attrs(1);
    let files = empty_strings(1);
    let descs = empty_strings(1);
    let err = try_prepare_witness_graph(&[1, 1], &[0], &[0], &attrs, &files, &descs, "test")
        .expect_err("nonzero first");
    let msg = format!("{err:?}");
    assert!(msg.contains("NonZeroInitialOffset"), "{msg}");
}

#[test]
fn try_prepare_witness_graph_rejects_non_monotonic_offsets() {
    let attrs = empty_attrs(3);
    let files = empty_strings(1);
    let descs = empty_strings(3);
    // 3 nodes, final offset = 1, edge arrays have 1 element.
    // Offsets decrease at node 1: [0, 1, 0, 1].
    let err = try_prepare_witness_graph(&[0, 1, 0, 1], &[0], &[0], &attrs, &files, &descs, "test")
        .expect_err("non-monotonic");
    let msg = format!("{err:?}");
    assert!(msg.contains("NonMonotonicOffsets"), "{msg}");
}

#[test]
fn try_prepare_witness_graph_rejects_target_out_of_bounds() {
    let attrs = empty_attrs(2);
    let files = empty_strings(1);
    let descs = empty_strings(2);
    let err = try_prepare_witness_graph(&[0, 1, 1], &[2], &[0], &attrs, &files, &descs, "test")
        .expect_err("target OOB");
    let msg = format!("{err:?}");
    assert!(msg.contains("TargetOutOfBounds"), "{msg}");
}

#[test]
fn try_prepare_witness_graph_rejects_edge_array_mismatch() {
    let attrs = empty_attrs(2);
    let files = empty_strings(1);
    let descs = empty_strings(2);
    let err = try_prepare_witness_graph(&[0, 1, 1], &[0], &[0, 0], &attrs, &files, &descs, "test")
        .expect_err("mismatch");
    let msg = format!("{err:?}");
    assert!(msg.contains("EdgeArrayLengthMismatch"), "{msg}");
}

#[test]
fn try_prepare_witness_graph_rejects_terminal_offset_mismatch() {
    let attrs = empty_attrs(2);
    let files = empty_strings(1);
    let descs = empty_strings(2);
    let err = try_prepare_witness_graph(&[0, 1, 2], &[0], &[0], &attrs, &files, &descs, "test")
        .expect_err("terminal mismatch");
    let msg = format!("{err:?}");
    assert!(msg.contains("TerminalOffsetMismatch"), "{msg}");
}

#[test]
fn extract_path_rejects_node_out_of_bounds_source() {
    let attrs = empty_attrs(2);
    let files = empty_strings(1);
    let descs = empty_strings(2);
    let graph = try_prepare_witness_graph(&[0, 1, 1], &[0], &[0], &attrs, &files, &descs, "test")
        .expect("valid graph");
    let seed = PathSeed {
        source_file: String::new(),
        source_node: 5,
        sink_file: String::new(),
        sink_node: 0,
    };
    let err = extract_path_prepared(&seed, &[1, 0], &[0, 0], &graph).expect_err("OOB source");
    assert!(
        matches!(
            err,
            weir::reachability_witness::PathError::NodeOutOfBounds { node: 5, .. }
        ),
        "{err:?}"
    );
}

#[test]
fn extract_path_rejects_node_out_of_bounds_sink() {
    let attrs = empty_attrs(2);
    let files = empty_strings(1);
    let descs = empty_strings(2);
    let graph = try_prepare_witness_graph(&[0, 1, 1], &[0], &[0], &attrs, &files, &descs, "test")
        .expect("valid graph");
    let seed = PathSeed {
        source_file: String::new(),
        source_node: 0,
        sink_file: String::new(),
        sink_node: 5,
    };
    let err = extract_path_prepared(&seed, &[1, 0], &[0, 0], &graph).expect_err("OOB sink");
    assert!(
        matches!(
            err,
            weir::reachability_witness::PathError::NodeOutOfBounds { node: 5, .. }
        ),
        "{err:?}"
    );
}

#[test]
fn extract_path_rejects_no_path() {
    let attrs = empty_attrs(2);
    let files = empty_strings(1);
    let descs = empty_strings(2);
    let graph = try_prepare_witness_graph(&[0, 1, 1], &[0], &[0], &attrs, &files, &descs, "test")
        .expect("valid graph");
    let seed = PathSeed {
        source_file: String::new(),
        source_node: 0,
        sink_file: String::new(),
        sink_node: 1,
    };
    let err = extract_path_prepared(&seed, &[0, 0], &[0, 0], &graph).expect_err("no path");
    assert!(
        matches!(err, weir::reachability_witness::PathError::NoPath),
        "{err:?}"
    );
}

#[test]
fn extract_path_rejects_sanitizer_at_source() {
    let attrs = empty_attrs(2);
    let files = empty_strings(1);
    let descs = empty_strings(2);
    let graph = try_prepare_witness_graph(&[0, 1, 1], &[0], &[0], &attrs, &files, &descs, "test")
        .expect("valid graph");
    let seed = PathSeed {
        source_file: String::new(),
        source_node: 0,
        sink_file: String::new(),
        sink_node: 0,
    };
    let err =
        extract_path_prepared(&seed, &[1, 0], &[1, 0], &graph).expect_err("sanitizer at source");
    assert!(
        matches!(err, weir::reachability_witness::PathError::NoPath),
        "{err:?}"
    );
}

#[test]
fn extract_path_rejects_sanitizer_at_sink() {
    let attrs = empty_attrs(2);
    let files = empty_strings(1);
    let descs = empty_strings(2);
    // Graph: 0 -> 1
    let graph = try_prepare_witness_graph(&[0, 1, 1], &[1], &[0], &attrs, &files, &descs, "test")
        .expect("valid graph");
    let seed = PathSeed {
        source_file: String::new(),
        source_node: 0,
        sink_file: String::new(),
        sink_node: 1,
    };
    // source_reach = [1, 1] (both reached), sanitizer_mask = [0, 1] (sink is sanitizer)
    let err =
        extract_path_prepared(&seed, &[1, 1], &[0, 1], &graph).expect_err("sanitizer at sink");
    assert!(
        matches!(err, weir::reachability_witness::PathError::NoPath),
        "{err:?}"
    );
}

#[test]
fn extract_path_rejects_malformed_graph_via_extract_path() {
    let attrs = empty_attrs(1);
    let files = empty_strings(1);
    let descs = empty_strings(1);
    let seed = PathSeed {
        source_file: String::new(),
        source_node: 0,
        sink_file: String::new(),
        sink_node: 0,
    };
    // Nonzero first offset is malformed.
    let err = extract_path(
        &seed,
        &[1],
        &[0],
        &[1, 1],
        &[0],
        &[0],
        &attrs,
        &files,
        &descs,
        "test",
    )
    .expect_err("malformed");
    let msg = format!("{err:?}");
    assert!(
        msg.contains("NonZeroInitialOffset") || msg.contains("edge_offsets[0]"),
        "{msg}"
    );
}