mod ast_mutator;
pub mod cwe_bash;
mod operators;
pub use ast_mutator::{AstMutation, AstMutator};
pub use cwe_bash::{generate_cwe_mutations, CweMutation};
pub use operators::MutationOperator;
use crate::{Error, Result};
#[derive(Debug, Clone)]
pub struct MutatedCode {
pub original: String,
pub mutated: String,
pub operator: MutationOperator,
pub location: (usize, usize),
pub description: String,
}
#[derive(Debug, Default)]
pub struct Mutator {
enabled_operators: Vec<MutationOperator>,
}
impl Mutator {
#[must_use]
pub fn new() -> Self {
Self {
enabled_operators: MutationOperator::all(),
}
}
#[must_use]
pub fn with_operators(operators: Vec<MutationOperator>) -> Self {
Self {
enabled_operators: operators,
}
}
pub fn mutate(&self, code: &str) -> Result<Vec<MutatedCode>> {
if code.is_empty() {
return Err(Error::Mutation("cannot mutate empty code".to_string()));
}
contract_pre_mutation_soundness!(code);
let mut mutations = Vec::new();
for operator in &self.enabled_operators {
let operator_mutations = self.apply_operator(code, operator)?;
mutations.extend(operator_mutations);
}
Ok(mutations)
}
fn apply_operator(&self, code: &str, operator: &MutationOperator) -> Result<Vec<MutatedCode>> {
let mutations = match operator {
MutationOperator::Aor => self.apply_aor(code),
MutationOperator::Ror => self.apply_ror(code),
MutationOperator::Lor => self.apply_lor(code),
MutationOperator::Uoi => self.apply_uoi(code),
MutationOperator::Abs => self.apply_abs(code),
MutationOperator::Sdl => self.apply_sdl(code),
MutationOperator::Svr => self.apply_svr(code),
MutationOperator::Bsr => self.apply_bsr(code),
};
Ok(mutations)
}
fn apply_aor(&self, code: &str) -> Vec<MutatedCode> {
let mut mutations = Vec::new();
if code.contains('+') {
mutations.push(MutatedCode {
original: code.to_string(),
mutated: code.replace('+', "-"),
operator: MutationOperator::Aor,
location: (1, code.find('+').unwrap_or(0)),
description: "Replace + with -".to_string(),
});
}
mutations
}
fn apply_ror(&self, code: &str) -> Vec<MutatedCode> {
let mut mutations = Vec::new();
if code.contains('<') && !code.contains("<=") {
mutations.push(MutatedCode {
original: code.to_string(),
mutated: code.replace('<', "<="),
operator: MutationOperator::Ror,
location: (1, code.find('<').unwrap_or(0)),
description: "Replace < with <=".to_string(),
});
}
mutations
}
fn apply_lor(&self, _code: &str) -> Vec<MutatedCode> {
Vec::new()
}
fn apply_uoi(&self, _code: &str) -> Vec<MutatedCode> {
Vec::new()
}
fn apply_abs(&self, _code: &str) -> Vec<MutatedCode> {
Vec::new()
}
fn apply_sdl(&self, _code: &str) -> Vec<MutatedCode> {
Vec::new()
}
fn apply_svr(&self, _code: &str) -> Vec<MutatedCode> {
Vec::new()
}
fn apply_bsr(&self, code: &str) -> Vec<MutatedCode> {
let mut mutations = Vec::new();
if code.contains(" 0") || code.contains("=0") {
mutations.push(MutatedCode {
original: code.to_string(),
mutated: code.replace(" 0", " -1").replace("=0", "=-1"),
operator: MutationOperator::Bsr,
location: (1, 0),
description: "Replace 0 with -1".to_string(),
});
}
mutations
}
#[must_use]
pub fn enabled_operators(&self) -> &[MutationOperator] {
&self.enabled_operators
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_mutator_new() {
let mutator = Mutator::new();
assert_eq!(mutator.enabled_operators().len(), 8);
}
#[test]
fn test_mutator_default() {
let mutator = Mutator::default();
assert!(mutator.enabled_operators().is_empty());
}
#[test]
fn test_mutator_with_operators() {
let ops = vec![MutationOperator::Aor, MutationOperator::Ror];
let mutator = Mutator::with_operators(ops);
assert_eq!(mutator.enabled_operators().len(), 2);
}
#[test]
fn test_mutator_aor() {
let mutator = Mutator::with_operators(vec![MutationOperator::Aor]);
let mutations = mutator
.mutate("x = a + b")
.expect("mutation should succeed");
assert!(!mutations.is_empty());
assert!(mutations[0].mutated.contains('-'));
}
#[test]
fn test_mutator_aor_no_operator() {
let mutator = Mutator::with_operators(vec![MutationOperator::Aor]);
let mutations = mutator.mutate("x = 5").expect("mutation should succeed");
assert!(mutations.is_empty());
}
#[test]
fn test_mutator_ror() {
let mutator = Mutator::with_operators(vec![MutationOperator::Ror]);
let mutations = mutator
.mutate("if x < 5:")
.expect("mutation should succeed");
assert!(!mutations.is_empty());
assert!(mutations[0].mutated.contains("<="));
}
#[test]
fn test_mutator_ror_already_lte() {
let mutator = Mutator::with_operators(vec![MutationOperator::Ror]);
let mutations = mutator
.mutate("if x <= 5:")
.expect("mutation should succeed");
assert!(mutations.is_empty()); }
#[test]
fn test_mutator_ror_no_operator() {
let mutator = Mutator::with_operators(vec![MutationOperator::Ror]);
let mutations = mutator.mutate("x = 5").expect("mutation should succeed");
assert!(mutations.is_empty());
}
#[test]
fn test_mutator_lor() {
let mutator = Mutator::with_operators(vec![MutationOperator::Lor]);
let mutations = mutator.mutate("x and y").expect("mutation should succeed");
assert!(mutations.is_empty()); }
#[test]
fn test_mutator_uoi() {
let mutator = Mutator::with_operators(vec![MutationOperator::Uoi]);
let mutations = mutator.mutate("x = 5").expect("mutation should succeed");
assert!(mutations.is_empty()); }
#[test]
fn test_mutator_abs() {
let mutator = Mutator::with_operators(vec![MutationOperator::Abs]);
let mutations = mutator.mutate("x = -5").expect("mutation should succeed");
assert!(mutations.is_empty()); }
#[test]
fn test_mutator_sdl() {
let mutator = Mutator::with_operators(vec![MutationOperator::Sdl]);
let mutations = mutator.mutate("x = 5").expect("mutation should succeed");
assert!(mutations.is_empty()); }
#[test]
fn test_mutator_svr() {
let mutator = Mutator::with_operators(vec![MutationOperator::Svr]);
let mutations = mutator.mutate("x = y").expect("mutation should succeed");
assert!(mutations.is_empty()); }
#[test]
fn test_mutator_bsr_space_zero() {
let mutator = Mutator::with_operators(vec![MutationOperator::Bsr]);
let mutations = mutator.mutate("x = 0").expect("mutation should succeed");
assert!(!mutations.is_empty());
assert!(mutations[0].mutated.contains("-1"));
}
#[test]
fn test_mutator_bsr_equals_zero() {
let mutator = Mutator::with_operators(vec![MutationOperator::Bsr]);
let mutations = mutator.mutate("x=0").expect("mutation should succeed");
assert!(!mutations.is_empty());
assert!(mutations[0].mutated.contains("=-1"));
}
#[test]
fn test_mutator_bsr_no_zero() {
let mutator = Mutator::with_operators(vec![MutationOperator::Bsr]);
let mutations = mutator.mutate("x = 5").expect("mutation should succeed");
assert!(mutations.is_empty());
}
#[test]
fn test_mutator_empty_code() {
let mutator = Mutator::new();
let result = mutator.mutate("");
assert!(result.is_err());
}
#[test]
fn test_mutator_all_operators() {
let mutator = Mutator::new();
let mutations = mutator
.mutate("x = a + b if y < 0")
.expect("mutation should succeed");
assert!(!mutations.is_empty());
assert!(mutations
.iter()
.any(|m| m.operator == MutationOperator::Aor));
assert!(mutations
.iter()
.any(|m| m.operator == MutationOperator::Ror));
let mutations2 = mutator.mutate("x = 0").expect("mutation should succeed");
assert!(mutations2
.iter()
.any(|m| m.operator == MutationOperator::Bsr));
}
#[test]
fn test_mutated_code_debug() {
let mc = MutatedCode {
original: "x + 1".to_string(),
mutated: "x - 1".to_string(),
operator: MutationOperator::Aor,
location: (1, 2),
description: "Replace + with -".to_string(),
};
let debug = format!("{:?}", mc);
assert!(debug.contains("MutatedCode"));
assert!(debug.contains("Aor"));
}
#[test]
fn test_mutated_code_clone() {
let mc = MutatedCode {
original: "x + 1".to_string(),
mutated: "x - 1".to_string(),
operator: MutationOperator::Aor,
location: (1, 2),
description: "Replace + with -".to_string(),
};
let cloned = mc.clone();
assert_eq!(cloned.original, mc.original);
assert_eq!(cloned.mutated, mc.mutated);
}
}