use std::collections::BTreeMap;
use std::path::PathBuf;
pub use crate::verify::harnesses::mutation::{
mutation_probe, AppliedMutation, GateReport, MutationOutcome, MutationResult,
};
pub use crate::verify::harnesses::wgsl_mutation::{
wgsl_mutation_probe, WgslMutation, WgslMutationOutcome, WgslMutationReport,
};
pub struct MutationTest<'a> {
pub source_file: PathBuf,
pub test_fn_name: &'a str,
pub mutations: Vec<&'a dyn AppliedMutation>,
pub classes: Vec<crate::spec::MutationClass>,
pub cargo_test_args: Vec<&'a str>,
}
pub struct WgslMutationTest<'a> {
pub backend: &'a dyn crate::pipeline::backend::WgslBackend,
pub spec: &'a crate::spec::OpSpec,
pub mutations: Vec<WgslMutation>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct SurvivingMutation {
pub test_name: String,
pub source_file: PathBuf,
pub mutation_id: String,
pub description: String,
pub fix: String,
}
#[inline]
pub fn validate_catalog(
catalog: &[crate::adversarial::mutations::catalog::Mutation],
) -> Result<(), Vec<String>> {
if catalog.is_empty() {
return Err(vec![
"mutation corpus is empty — see build.rs defender_corpus generation".to_string(),
]);
}
let mut messages = Vec::new();
let mut seen = BTreeMap::new();
for mutation in catalog {
let adapter =
crate::adversarial::mutations::catalog::applied::CatalogMutation::new(mutation.clone());
let id = adapter.id().to_string();
if let Some(first) = seen.insert(id.clone(), format!("{mutation:?}")) {
messages.push(format!(
"duplicate mutation id `{id}` for `{first}` and `{mutation:?}`"
));
}
}
let required = [
(
"integer_constant_bit_flip",
catalog.iter().any(|m| {
matches!(
m,
crate::adversarial::mutations::catalog::Mutation::IntegerConstantBitFlip { .. }
)
}),
),
(
"float_constant_bit_flip",
catalog.iter().any(|m| {
matches!(
m,
crate::adversarial::mutations::catalog::Mutation::FloatConstantBitFlip { .. }
)
}),
),
(
"workgroup_stride_mul_div",
catalog.iter().any(|m| {
matches!(
m,
crate::adversarial::mutations::catalog::Mutation::IrWorkgroupStrideMul { .. }
)
}) && catalog.iter().any(|m| {
matches!(
m,
crate::adversarial::mutations::catalog::Mutation::IrWorkgroupStrideDiv { .. }
)
}),
),
(
"workgroup_size_mul_div_offset",
catalog.iter().any(|m| {
matches!(
m,
crate::adversarial::mutations::catalog::Mutation::IrWorkgroupSizeMul { .. }
)
}) && catalog.iter().any(|m| {
matches!(
m,
crate::adversarial::mutations::catalog::Mutation::IrWorkgroupSizeDiv { .. }
)
}) && catalog.iter().any(|m| {
matches!(
m,
crate::adversarial::mutations::catalog::Mutation::IrWorkgroupSizeOffset { .. }
)
}),
),
(
"atomic_acqrel_ordering",
catalog.iter().any(|m| {
matches!(
m,
crate::adversarial::mutations::catalog::Mutation::AtomicOrderingAcqRelWeaken
)
}),
),
(
"buffer_miscount_plus_minus_one",
catalog.iter().any(|m| {
matches!(
m,
crate::adversarial::mutations::catalog::Mutation::BufferCountShift { by: -1 }
)
}) && catalog.iter().any(|m| {
matches!(
m,
crate::adversarial::mutations::catalog::Mutation::BufferCountShift { by: 1 }
)
}),
),
(
"le_ge_swap",
catalog.iter().any(|m| {
matches!(
m,
crate::adversarial::mutations::catalog::Mutation::CompareOpSwap {
from: crate::adversarial::mutations::catalog::BinOpKind::Le,
to: crate::adversarial::mutations::catalog::BinOpKind::Ge
}
)
}) && catalog.iter().any(|m| {
matches!(
m,
crate::adversarial::mutations::catalog::Mutation::CompareOpSwap {
from: crate::adversarial::mutations::catalog::BinOpKind::Ge,
to: crate::adversarial::mutations::catalog::BinOpKind::Le
}
)
}),
),
(
"control_flow_skip",
catalog.iter().any(|m| {
matches!(
m,
crate::adversarial::mutations::catalog::Mutation::ControlFlowSkip
)
}),
),
];
for (name, present) in required {
if !present {
messages.push(format!("missing required mutation operator `{name}`"));
}
}
if messages.is_empty() {
Ok(())
} else {
Err(messages)
}
}
#[inline]
pub fn enforce_on(test_set: &[MutationTest<'_>]) -> Result<(), Vec<SurvivingMutation>> {
enforce_on_with_wgsl(test_set, &[])
}
#[inline]
pub fn enforce_on_with_wgsl(
test_set: &[MutationTest<'_>],
wgsl_test_set: &[WgslMutationTest<'_>],
) -> Result<(), Vec<SurvivingMutation>> {
let mut survivors = Vec::new();
for test in test_set {
let report = mutation_probe(
&test.source_file,
test.test_fn_name,
&test.mutations,
&test.classes,
&test.cargo_test_args,
);
survivors.extend(survivors_from_report(&report));
}
for test in wgsl_test_set {
match wgsl_mutation_probe(test.backend, test.spec, &test.mutations) {
Ok(report) => survivors.extend(survivors_from_wgsl_report(&report)),
Err(error) => survivors.push(SurvivingMutation {
test_name: format!("wgsl_mutation:{}", test.spec.id),
source_file: PathBuf::from(format!("wgsl_fn:{}", test.spec.id)),
mutation_id: "wgsl.baseline".to_string(),
description: "WGSL baseline parity failed before mutation probing".to_string(),
fix: error,
}),
}
}
if survivors.is_empty() {
Ok(())
} else {
Err(survivors)
}
}
#[inline]
pub fn enforce_catalog(
catalog: &[crate::adversarial::mutations::catalog::Mutation],
) -> Result<(), Vec<String>> {
validate_catalog(catalog)?;
let applied: Vec<Box<dyn AppliedMutation>> = catalog
.iter()
.cloned()
.map(crate::adversarial::mutations::catalog::applied::applied_for)
.collect();
let mutation_refs: Vec<&dyn AppliedMutation> = applied
.iter()
.map(|mutation| mutation.as_ref() as &dyn AppliedMutation)
.collect();
let source_file = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("src/mutations/catalog.rs");
let test_set = [MutationTest {
source_file,
test_fn_name: "catalog_entries_are_unique",
mutations: mutation_refs,
classes: Vec::new(),
cargo_test_args: vec!["--offline"],
}];
enforce_on(&test_set).map_err(|survivors| {
survivors
.into_iter()
.map(|survivor| {
format!(
"{} survived {} on {}. {}",
survivor.mutation_id,
survivor.test_name,
survivor.source_file.display(),
survivor.fix
)
})
.collect()
})
}
fn survivors_from_report(report: &GateReport) -> Vec<SurvivingMutation> {
report
.results
.iter()
.filter(|result| matches!(result.outcome, MutationOutcome::Survived))
.map(|result| SurvivingMutation {
test_name: report.test_name.clone(),
source_file: report.source_file.clone(),
mutation_id: result.mutation_id.clone(),
description: result.description.clone(),
fix: report
.feedback
.iter()
.find(|feedback| feedback.mutation_id == result.mutation_id)
.map_or_else(
|| {
"Fix: add an assertion that distinguishes the original behavior from this mutation."
.to_string()
},
|feedback| format!("Fix: {}", feedback.hint),
),
})
.collect()
}
fn survivors_from_wgsl_report(report: &WgslMutationReport) -> Vec<SurvivingMutation> {
report
.results
.iter()
.filter(|result| matches!(result.outcome, WgslMutationOutcome::Survived))
.map(|result| SurvivingMutation {
test_name: format!("wgsl_mutation:{}", report.op_id),
source_file: PathBuf::from(format!("wgsl_fn:{}", report.op_id)),
mutation_id: result.mutation_id.clone(),
description: result.description.clone(),
fix: result.detail.clone(),
})
.collect()
}
pub struct Layer4MutationGateEnforcer;
impl crate::enforce::EnforceGate for Layer4MutationGateEnforcer {
fn id(&self) -> &'static str {
"layer4_mutation_gate"
}
fn name(&self) -> &'static str {
"layer4_mutation_gate"
}
fn run(&self, _ctx: &crate::enforce::EnforceCtx<'_>) -> Vec<crate::enforce::Finding> {
let messages =
match validate_catalog(crate::adversarial::mutations::catalog::MUTATION_CATALOG) {
Ok(()) => Vec::new(),
Err(reasons) => reasons
.into_iter()
.map(|reason| format!("Fix: mutation catalog precondition failed: {reason}"))
.collect(),
};
crate::enforce::finding_result(self.id(), messages)
}
}
pub const REGISTERED: Layer4MutationGateEnforcer = Layer4MutationGateEnforcer;