#![cfg_attr(coverage_nightly, coverage(off))]
use super::language::{LanguageAdapter, TestRunResult};
use super::operators::MutationOperator;
use super::types::*;
use anyhow::Result;
use async_trait::async_trait;
use std::path::Path;
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> {
Ok(source.to_string())
}
async fn unparse(&self, ast: &str) -> Result<String> {
Ok(ast.to_string())
}
fn mutation_operators(&self) -> Vec<Box<dyn MutationOperator>> {
self.operators
.iter()
.map(|op| -> Box<dyn MutationOperator> {
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> {
Ok(TestRunResult {
passed: true,
failures: vec![],
execution_time_ms: 0,
stdout: String::new(),
stderr: String::new(),
})
}
}
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 {
false }
fn mutate(&self, _expr: &syn::Expr, _location: SourceLocation) -> Result<Vec<syn::Expr>> {
Ok(vec![])
}
fn kill_probability(&self) -> f64 {
0.80
}
}
impl WasmNumericMutator {
pub fn mutate_wat(&self, wat_text: &str) -> Result<Vec<String>> {
let mut mutants = Vec::new();
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"));
}
if wat_text.contains("i64.add") {
mutants.push(wat_text.replace("i64.add", "i64.sub"));
mutants.push(wat_text.replace("i64.add", "i64.mul"));
}
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)
}
}
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 }
fn mutate(&self, _expr: &syn::Expr, _location: SourceLocation) -> Result<Vec<syn::Expr>> {
Ok(vec![])
}
fn kill_probability(&self) -> f64 {
0.90
}
}
impl WasmControlFlowMutator {
pub fn mutate_wat(&self, wat_text: &str) -> Result<Vec<String>> {
let mut mutants = Vec::new();
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"));
}
if wat_text.contains("(loop") {
mutants.push(wat_text.replace("(loop", "(block"));
}
Ok(mutants)
}
}
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 }
fn mutate(&self, _expr: &syn::Expr, _location: SourceLocation) -> Result<Vec<syn::Expr>> {
Ok(vec![])
}
fn kill_probability(&self) -> f64 {
0.75
}
}
impl WasmLocalMutator {
pub fn mutate_wat(&self, wat_text: &str) -> Result<Vec<String>> {
let mut mutants = Vec::new();
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")));
}
}