pmat 3.11.0

PMAT - Zero-config AI context generation and code quality toolkit (CLI, MCP, HTTP)
#![cfg_attr(coverage_nightly, coverage(off))]
//! WASM language adapter for mutation testing
//!
//! Provides mutation testing capabilities for WebAssembly (WAT text format).
//! Implements control flow, numeric, and stack-based mutations.

use super::language::{LanguageAdapter, TestRunResult};
use super::operators::MutationOperator;
use super::types::*;
use anyhow::Result;
use async_trait::async_trait;
use std::path::Path;

/// WASM language adapter
pub struct WasmAdapter {
    operators: Vec<Box<dyn MutationOperator>>,
}

impl WasmAdapter {
    pub fn new() -> Self {
        Self {
            operators: vec![
                Box::new(WasmNumericMutator),
                Box::new(WasmControlFlowMutator),
                Box::new(WasmLocalMutator),
            ],
        }
    }
}

impl Default for WasmAdapter {
    fn default() -> Self {
        Self::new()
    }
}

#[async_trait]
impl LanguageAdapter for WasmAdapter {
    fn name(&self) -> &str {
        "wasm"
    }

    fn extensions(&self) -> &[&str] {
        &["wasm", "wat"]
    }

    async fn parse(&self, source: &str) -> Result<String> {
        // WAT is already text format, return as-is
        Ok(source.to_string())
    }

    async fn unparse(&self, ast: &str) -> Result<String> {
        // WAT is already text format, return as-is
        Ok(ast.to_string())
    }

    fn mutation_operators(&self) -> Vec<Box<dyn MutationOperator>> {
        self.operators
            .iter()
            .map(|op| -> Box<dyn MutationOperator> {
                // Clone operators (we'll implement this pattern)
                match op.name() {
                    "WASM_NUM" => Box::new(WasmNumericMutator),
                    "WASM_CF" => Box::new(WasmControlFlowMutator),
                    "WASM_LOCAL" => Box::new(WasmLocalMutator),
                    _ => Box::new(WasmNumericMutator),
                }
            })
            .collect()
    }

    async fn run_tests(&self, _source_file: &Path) -> Result<TestRunResult> {
        // For WASM, we would typically run tests using a WASM runtime
        // For now, return a placeholder result
        Ok(TestRunResult {
            passed: true,
            failures: vec![],
            execution_time_ms: 0,
            stdout: String::new(),
            stderr: String::new(),
        })
    }
}

/// WASM Numeric instruction mutator
/// Mutates: i32.add → i32.sub, i32.mul → i32.div, etc.
pub struct WasmNumericMutator;

impl MutationOperator for WasmNumericMutator {
    fn name(&self) -> &str {
        "WASM_NUM"
    }

    fn operator_type(&self) -> MutationOperatorType {
        MutationOperatorType::ArithmeticReplacement
    }

    fn can_mutate(&self, _expr: &syn::Expr) -> bool {
        // For WAT, we'll check string-based patterns
        false // Not applicable for Rust AST
    }

    fn mutate(&self, _expr: &syn::Expr, _location: SourceLocation) -> Result<Vec<syn::Expr>> {
        // WASM mutations work at text level, not Rust AST
        Ok(vec![])
    }

    fn kill_probability(&self) -> f64 {
        0.80
    }
}

// Additional helper for string-based WAT mutations
impl WasmNumericMutator {
    /// Mutate WASM instructions in WAT text format
    pub fn mutate_wat(&self, wat_text: &str) -> Result<Vec<String>> {
        let mut mutants = Vec::new();

        // i32 arithmetic mutations
        if wat_text.contains("i32.add") {
            mutants.push(wat_text.replace("i32.add", "i32.sub"));
            mutants.push(wat_text.replace("i32.add", "i32.mul"));
        }
        if wat_text.contains("i32.sub") {
            mutants.push(wat_text.replace("i32.sub", "i32.add"));
            mutants.push(wat_text.replace("i32.sub", "i32.mul"));
        }
        if wat_text.contains("i32.mul") {
            mutants.push(wat_text.replace("i32.mul", "i32.add"));
            mutants.push(wat_text.replace("i32.mul", "i32.div_s"));
        }
        if wat_text.contains("i32.div_s") {
            mutants.push(wat_text.replace("i32.div_s", "i32.mul"));
        }

        // i64 arithmetic mutations
        if wat_text.contains("i64.add") {
            mutants.push(wat_text.replace("i64.add", "i64.sub"));
            mutants.push(wat_text.replace("i64.add", "i64.mul"));
        }

        // f32/f64 arithmetic mutations
        if wat_text.contains("f32.add") {
            mutants.push(wat_text.replace("f32.add", "f32.sub"));
            mutants.push(wat_text.replace("f32.add", "f32.mul"));
        }
        if wat_text.contains("f64.add") {
            mutants.push(wat_text.replace("f64.add", "f64.sub"));
            mutants.push(wat_text.replace("f64.add", "f64.mul"));
        }

        Ok(mutants)
    }
}

/// WASM Control flow mutator
/// Mutates: br → br_if, if → block, loop → block
pub struct WasmControlFlowMutator;

impl MutationOperator for WasmControlFlowMutator {
    fn name(&self) -> &str {
        "WASM_CF"
    }

    fn operator_type(&self) -> MutationOperatorType {
        MutationOperatorType::ConditionalReplacement
    }

    fn can_mutate(&self, _expr: &syn::Expr) -> bool {
        false // Not applicable for Rust AST
    }

    fn mutate(&self, _expr: &syn::Expr, _location: SourceLocation) -> Result<Vec<syn::Expr>> {
        Ok(vec![])
    }

    fn kill_probability(&self) -> f64 {
        0.90
    }
}

impl WasmControlFlowMutator {
    /// Mutate WASM control flow in WAT text
    pub fn mutate_wat(&self, wat_text: &str) -> Result<Vec<String>> {
        let mut mutants = Vec::new();

        // Branch mutations
        if wat_text.contains("br ") {
            mutants.push(wat_text.replace("br ", "br_if "));
        }
        if wat_text.contains("br_if") {
            mutants.push(wat_text.replace("br_if", "br"));
        }

        // Loop to block mutation
        if wat_text.contains("(loop") {
            mutants.push(wat_text.replace("(loop", "(block"));
        }

        Ok(mutants)
    }
}

/// WASM Local variable mutator
/// Mutates: local.get $x → local.get $y, local.set → local.tee
pub struct WasmLocalMutator;

impl MutationOperator for WasmLocalMutator {
    fn name(&self) -> &str {
        "WASM_LOCAL"
    }

    fn operator_type(&self) -> MutationOperatorType {
        MutationOperatorType::Custom("WASM_LOCAL".to_string())
    }

    fn can_mutate(&self, _expr: &syn::Expr) -> bool {
        false // Not applicable for Rust AST
    }

    fn mutate(&self, _expr: &syn::Expr, _location: SourceLocation) -> Result<Vec<syn::Expr>> {
        Ok(vec![])
    }

    fn kill_probability(&self) -> f64 {
        0.75
    }
}

impl WasmLocalMutator {
    /// Mutate WASM local operations in WAT text
    pub fn mutate_wat(&self, wat_text: &str) -> Result<Vec<String>> {
        let mut mutants = Vec::new();

        // local.set → local.tee mutation
        if wat_text.contains("local.set") {
            mutants.push(wat_text.replace("local.set", "local.tee"));
        }
        if wat_text.contains("local.tee") {
            mutants.push(wat_text.replace("local.tee", "local.set"));
        }

        Ok(mutants)
    }
}

#[cfg_attr(coverage_nightly, coverage(off))]
#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_wasm_adapter_creation() {
        let adapter = WasmAdapter::new();
        assert_eq!(adapter.name(), "wasm");
        assert_eq!(adapter.extensions(), &["wasm", "wat"]);
        assert_eq!(adapter.mutation_operators().len(), 3);
    }

    #[test]
    fn test_wasm_numeric_mutations() {
        let mutator = WasmNumericMutator;
        let wat = "(i32.add (i32.const 1) (i32.const 2))";
        let mutants = mutator.mutate_wat(wat).unwrap();

        assert!(!mutants.is_empty());
        assert!(mutants.iter().any(|m| m.contains("i32.sub")));
        assert!(mutants.iter().any(|m| m.contains("i32.mul")));
    }

    #[test]
    fn test_wasm_control_flow_mutations() {
        let mutator = WasmControlFlowMutator;
        let wat = "(block (br 0))";
        let mutants = mutator.mutate_wat(wat).unwrap();

        assert!(!mutants.is_empty());
        assert!(mutants.iter().any(|m| m.contains("br_if")));
    }

    #[test]
    fn test_wasm_local_mutations() {
        let mutator = WasmLocalMutator;
        let wat = "(local.set $x (i32.const 10))";
        let mutants = mutator.mutate_wat(wat).unwrap();

        assert!(!mutants.is_empty());
        assert!(mutants.iter().any(|m| m.contains("local.tee")));
    }

    #[test]
    fn test_wasm_i64_mutations() {
        let mutator = WasmNumericMutator;
        let wat = "(i64.add (i64.const 1) (i64.const 2))";
        let mutants = mutator.mutate_wat(wat).unwrap();

        assert!(!mutants.is_empty());
        assert!(mutants
            .iter()
            .any(|m| m.contains("i64.sub") || m.contains("i64.mul")));
    }

    #[test]
    fn test_wasm_float_mutations() {
        let mutator = WasmNumericMutator;
        let wat = "(f32.add (f32.const 1.5) (f32.const 2.5))";
        let mutants = mutator.mutate_wat(wat).unwrap();

        assert!(!mutants.is_empty());
        assert!(mutants
            .iter()
            .any(|m| m.contains("f32.sub") || m.contains("f32.mul")));
    }
}