diff --git a/docs/mutators.md b/docs/mutators.md index 402ade9..d702a7b 100644 --- a/docs/mutators.md +++ b/docs/mutators.md @@ -33,6 +33,23 @@ Byte-literals like `b'a'` are not mutated by this mutator. Customization is WIP +## lit_str + +### Target Code + +`&str` literals. + +Char literals like `'a'` are not mutated by this mutator. + +### Mutations + +* If not empty: + * Replace the literal with an empty string + * Prepend `'-'` + * Append `'-'` +* If empty: + * Replace the literal with `"A"` + ## unop_not ### Target Code diff --git a/mutagen-core/src/mutator.rs b/mutagen-core/src/mutator.rs index 0427e6e..6e11ad6 100644 --- a/mutagen-core/src/mutator.rs +++ b/mutagen-core/src/mutator.rs @@ -6,5 +6,6 @@ pub mod mutator_binop_eq; pub mod mutator_binop_num; pub mod mutator_lit_bool; pub mod mutator_lit_int; +pub mod mutator_lit_str; pub mod mutator_stmt_call; pub mod mutator_unop_not; diff --git a/mutagen-core/src/mutator/mutator_lit_str.rs b/mutagen-core/src/mutator/mutator_lit_str.rs new file mode 100644 index 0000000..12f3bd1 --- /dev/null +++ b/mutagen-core/src/mutator/mutator_lit_str.rs @@ -0,0 +1,199 @@ +//! Mutator for str literals. + +use std::convert::TryFrom; +use std::ops::Deref; + +use proc_macro2::Span; +use quote::quote_spanned; +use syn::{Expr, Lit, LitStr}; + +use crate::comm::Mutation; +use crate::transformer::transform_info::SharedTransformInfo; +use crate::transformer::TransformContext; + +use crate::MutagenRuntimeConfig; + +pub fn run( + mutator_id: usize, + original_lit: &'static str, + mutations: &[&'static str], + runtime: &impl Deref, +) -> &'static str { + runtime.covered(mutator_id); + if let Some(m) = runtime.get_mutation_for_mutator(mutator_id, &mutations) { + m + } else { + original_lit + } +} + +pub fn transform( + e: Expr, + transform_info: &SharedTransformInfo, + context: &TransformContext, +) -> Expr { + let e = match ExprLitStr::try_from(e) { + Ok(e) => e, + Err(e) => return e, + }; + + let possible_mutations = MutationLitStr::possible_mutations(e.clone().value); + let mutations: Vec<_> = possible_mutations + .iter() + .map(|x| x.mutate(&e.clone().value)) + .collect(); + + let mutator_id = transform_info.add_mutations( + possible_mutations + .into_iter() + .map(|m| m.to_mutation(&e, context)), + ); + + let original_lit = e.lit.value(); + + syn::parse2(quote_spanned! {e.span=> + ::mutagen::mutator::mutator_lit_str::run( + #mutator_id, + #original_lit, + &[#(&#mutations),*], // Expands to `&[mutations[0], mutations[1], ..., [mutations[n]]]` + &::mutagen::MutagenRuntimeConfig::get_default() + ) + }) + .expect("transformed code invalid") +} + +#[derive(Clone, Debug, PartialEq, Eq)] +enum MutationLitStr { + Clear, + Set(&'static str), + Append(char), + Prepend(char), +} + +impl MutationLitStr { + fn possible_mutations(val: String) -> Vec { + let mut mutations = vec![]; + if val.is_empty() { + mutations.push(Self::Set("A")) + } else { + mutations.push(Self::Clear); + mutations.push(Self::Prepend('-')); + mutations.push(Self::Append('-')); + } + mutations + } + + fn mutate(&self, val: &str) -> String { + match self { + Self::Clear => "".to_string(), + Self::Set(string) => string.to_string(), + Self::Append(char) => { + let mut new = val.to_string(); + new.push(*char); + new + } + Self::Prepend(char) => { + let mut new = val.to_string(); + new.insert(0, *char); + new + } + } + } + + fn to_mutation(&self, original_lit: &ExprLitStr, context: &TransformContext) -> Mutation { + Mutation::new_spanned( + context, + "lit_str".to_owned(), + original_lit.value.to_string(), + self.mutate(&original_lit.value).to_string(), + original_lit.span, + ) + } +} + +#[derive(Clone, Debug)] +pub struct ExprLitStr { + pub value: String, + pub lit: LitStr, + pub span: Span, +} + +impl TryFrom for ExprLitStr { + type Error = Expr; + fn try_from(expr: Expr) -> Result { + match expr { + Expr::Lit(expr) => match expr.lit { + Lit::Str(lit) => Ok(ExprLitStr { + value: lit.value(), + span: lit.span(), + lit, + }), + _ => Err(Expr::Lit(expr)), + }, + _ => Err(expr), + } + } +} + +#[cfg(test)] +mod tests { + + use super::*; + use crate::MutagenRuntimeConfig; + + #[test] + pub fn mutator_lit_str_empty_inactive() { + let result = run(1, "", &["A"], &&MutagenRuntimeConfig::without_mutation()); + assert_eq!(result, "") + } + + #[test] + pub fn mutator_lit_str_non_empty_inactive() { + let result = run( + 1, + "ABCD", + &["", "-ABCD", "ABCD-"], + &&MutagenRuntimeConfig::without_mutation(), + ); + assert_eq!(result, "ABCD") + } + + #[test] + pub fn mutator_lit_str_non_empty_active_1() { + let result = run( + 1, + "a", + &["", "-ABCD", "ABCD-"], + &&MutagenRuntimeConfig::with_mutation_id(1), + ); + assert_eq!(result, "") + } + + #[test] + pub fn mutator_lit_str_non_empty_active_2() { + let result = run( + 1, + "a", + &["", "-ABCD", "ABCD-"], + &&MutagenRuntimeConfig::with_mutation_id(2), + ); + assert_eq!(result, "-ABCD") + } + + #[test] + pub fn mutator_lit_str_non_empty_active_3() { + let result = run( + 1, + "a", + &["", "-ABCD", "ABCD-"], + &&MutagenRuntimeConfig::with_mutation_id(3), + ); + assert_eq!(result, "ABCD-") + } + + #[test] + pub fn mutator_lit_str_empty_active_1() { + let result = run(1, "", &["A"], &&MutagenRuntimeConfig::with_mutation_id(1)); + assert_eq!(result, "A") + } +} diff --git a/mutagen-core/src/transformer.rs b/mutagen-core/src/transformer.rs index ee7d87a..e963242 100644 --- a/mutagen-core/src/transformer.rs +++ b/mutagen-core/src/transformer.rs @@ -228,6 +228,7 @@ impl MutagenTransformerBundle { ) -> MutagenTransformer { match transformer_name { "lit_int" => MutagenTransformer::Expr(Box::new(mutator_lit_int::transform)), + "lit_str" => MutagenTransformer::Expr(Box::new(mutator_lit_str::transform)), "lit_bool" => MutagenTransformer::Expr(Box::new(mutator_lit_bool::transform)), "unop_not" => MutagenTransformer::Expr(Box::new(mutator_unop_not::transform)), "binop_bit" => MutagenTransformer::Expr(Box::new(mutator_binop_bit::transform)), @@ -244,6 +245,7 @@ impl MutagenTransformerBundle { pub fn all_transformers() -> Vec { [ "lit_int", + "lit_str", "lit_bool", "unop_not", "binop_bit", diff --git a/mutagen-selftest/src/mutator.rs b/mutagen-selftest/src/mutator.rs index 378800e..821d8f8 100644 --- a/mutagen-selftest/src/mutator.rs +++ b/mutagen-selftest/src/mutator.rs @@ -5,5 +5,6 @@ mod test_binop_eq; mod test_binop_num; mod test_lit_bool; mod test_lit_int; +mod test_lit_str; mod test_stmt_call; mod test_unop_not; diff --git a/mutagen-selftest/src/mutator/test_lit_str.rs b/mutagen-selftest/src/mutator/test_lit_str.rs new file mode 100644 index 0000000..a55ea1c --- /dev/null +++ b/mutagen-selftest/src/mutator/test_lit_str.rs @@ -0,0 +1,217 @@ +mod test_return_non_empty_string { + + use ::mutagen::mutate; + use ::mutagen::MutagenRuntimeConfig; + + #[mutate(conf = local(expected_mutations = 3), mutators = only(lit_str))] + fn return_non_empty_string() -> String { + #[allow(unused_parens)] + let s = "a"; + s.to_string() + } + + #[test] + fn inactive() { + MutagenRuntimeConfig::test_without_mutation(|| { + assert_eq!(return_non_empty_string(), "a".to_string()); + }) + } + + #[test] + fn active_clear() { + let _ = MutagenRuntimeConfig::get_default(); + MutagenRuntimeConfig::test_with_mutation_id(1, || { + assert_eq!(return_non_empty_string(), "".to_string()); + }) + } + + #[test] + fn active_prepend() { + MutagenRuntimeConfig::test_with_mutation_id(2, || { + assert_eq!(return_non_empty_string(), "-a".to_string()); + }) + } + + #[test] + fn active_append() { + MutagenRuntimeConfig::test_with_mutation_id(3, || { + assert_eq!(return_non_empty_string(), "a-".to_string()); + }) + } +} + +mod test_return_check_equals_a { + + use ::mutagen::mutate; + use ::mutagen::MutagenRuntimeConfig; + + #[mutate(conf = local(expected_mutations = 3), mutators = only(lit_str))] + fn check_equals_a(input: &str) -> bool { + "a" == input + } + + #[test] + fn inactive() { + MutagenRuntimeConfig::test_without_mutation(|| { + assert_eq!(check_equals_a("a"), true); + }) + } + + #[test] + fn active_clear() { + let _ = MutagenRuntimeConfig::get_default(); + MutagenRuntimeConfig::test_with_mutation_id(1, || { + assert_eq!(check_equals_a("a"), false); + assert_eq!(check_equals_a(""), true); + }) + } + + #[test] + fn active_prepend() { + MutagenRuntimeConfig::test_with_mutation_id(2, || { + assert_eq!(check_equals_a("a"), false); + assert_eq!(check_equals_a("-a"), true); + }) + } + + #[test] + fn active_append() { + MutagenRuntimeConfig::test_with_mutation_id(3, || { + assert_eq!(check_equals_a("a"), false); + assert_eq!(check_equals_a("a-"), true); + }) + } +} + +mod test_return_empty_string { + + use ::mutagen::mutate; + use ::mutagen::MutagenRuntimeConfig; + + #[mutate(conf = local(expected_mutations = 1), mutators = only(lit_str))] + fn return_empty_string() -> String { + #[allow(unused_parens)] + let s = ""; + s.to_string() + } + + #[test] + fn inactive() { + MutagenRuntimeConfig::test_without_mutation(|| { + assert_eq!(return_empty_string(), "".to_string()); + }) + } + + #[test] + fn active_set() { + let _ = MutagenRuntimeConfig::get_default(); + MutagenRuntimeConfig::test_with_mutation_id(1, || { + assert_eq!(return_empty_string(), "A".to_string()); + }) + } +} + +mod test_return_check_equals_empty_str { + + use ::mutagen::mutate; + use ::mutagen::MutagenRuntimeConfig; + + #[mutate(conf = local(expected_mutations = 1), mutators = only(lit_str))] + fn check_equals_empty_str(input: &str) -> bool { + "" == input + } + + #[test] + fn inactive() { + MutagenRuntimeConfig::test_without_mutation(|| { + assert_eq!(check_equals_empty_str(""), true); + }) + } + + #[test] + fn active_set() { + let _ = MutagenRuntimeConfig::get_default(); + MutagenRuntimeConfig::test_with_mutation_id(1, || { + assert_eq!(check_equals_empty_str(""), false); + assert_eq!(check_equals_empty_str("A"), true); + }) + } +} + +mod test_temporary_variable_1 { + use ::mutagen::mutate; + use ::mutagen::MutagenRuntimeConfig; + + #[mutate(conf = local(expected_mutations = 1), mutators = only(lit_str))] + fn a() -> usize { + #[allow(unused_parens)] + let x = ""; + x.len() + } + + #[test] + fn inactive() { + MutagenRuntimeConfig::test_without_mutation(|| { + assert_eq!(a(), 0); + }) + } + + #[test] + fn active_set() { + let _ = MutagenRuntimeConfig::get_default(); + MutagenRuntimeConfig::test_with_mutation_id(1, || { + assert_eq!(a(), 1); + }) + } +} + +mod test_to_string { + use ::mutagen::mutate; + use ::mutagen::MutagenRuntimeConfig; + + #[mutate(conf = local(expected_mutations = 1), mutators = only(lit_str))] + fn a() -> &'static str { + "" + } + + #[test] + fn inactive() { + MutagenRuntimeConfig::test_without_mutation(|| { + assert_eq!(a(), "".to_string()); + }) + } + + #[test] + fn active_set() { + let _ = MutagenRuntimeConfig::get_default(); + MutagenRuntimeConfig::test_with_mutation_id(1, || { + assert_eq!(a(), "A".to_string()); + }) + } +} + +mod test_temporary_variable_2 { + + use ::mutagen::mutate; + use ::mutagen::MutagenRuntimeConfig; + + #[mutate(conf = local(expected_mutations = 1), mutators = only(lit_str))] + fn a() -> &'static str { + "" + } + + #[test] + fn inactive() { + MutagenRuntimeConfig::test_without_mutation(|| { + assert_eq!(a(), ""); + }) + } + + #[test] + fn active_clear() { + let _ = MutagenRuntimeConfig::get_default(); + MutagenRuntimeConfig::test_with_mutation_id(1, || { + assert_eq!(a(), "A"); + }) + } +} diff --git a/mutagen-selftest/src/test_not_mutated.rs b/mutagen-selftest/src/test_not_mutated.rs index e6dffd8..72ddbd4 100644 --- a/mutagen-selftest/src/test_not_mutated.rs +++ b/mutagen-selftest/src/test_not_mutated.rs @@ -80,7 +80,7 @@ mod tuple_index_access { use ::mutagen::mutate; - #[mutate(conf = local(expected_mutations = 0))] + #[mutate(conf = local(expected_mutations = 0), mutators = not(lit_str))] fn x() -> &'static str { ((), "").1 } @@ -94,7 +94,7 @@ mod int_as_pattern { use ::mutagen::mutate; - #[mutate(conf = local(expected_mutations = 0))] + #[mutate(conf = local(expected_mutations = 0), mutators = not(lit_str))] fn x(i: i8) -> &'static str { match i { 0 => "zero",