Skip to content

Commit

Permalink
Add str literal mutation (#189)
Browse files Browse the repository at this point in the history
* Add str literal mutation

* Run cargo fmt

* Change type to &'static str

* Add temporary variable test

* Change back to string dependent mutations

* Revert documentation changes for lit_str
samgoldman authored Jul 29, 2022
1 parent a910410 commit a6377c4
Showing 7 changed files with 439 additions and 2 deletions.
17 changes: 17 additions & 0 deletions docs/mutators.md
Original file line number Diff line number Diff line change
@@ -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
1 change: 1 addition & 0 deletions mutagen-core/src/mutator.rs
Original file line number Diff line number Diff line change
@@ -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;
199 changes: 199 additions & 0 deletions mutagen-core/src/mutator/mutator_lit_str.rs
Original file line number Diff line number Diff line change
@@ -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<Target = MutagenRuntimeConfig>,
) -> &'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<Self> {
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<Expr> for ExprLitStr {
type Error = Expr;
fn try_from(expr: Expr) -> Result<Self, Expr> {
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")
}
}
2 changes: 2 additions & 0 deletions mutagen-core/src/transformer.rs
Original file line number Diff line number Diff line change
@@ -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<String> {
[
"lit_int",
"lit_str",
"lit_bool",
"unop_not",
"binop_bit",
1 change: 1 addition & 0 deletions mutagen-selftest/src/mutator.rs
Original file line number Diff line number Diff line change
@@ -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;
217 changes: 217 additions & 0 deletions mutagen-selftest/src/mutator/test_lit_str.rs
Original file line number Diff line number Diff line change
@@ -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");
})
}
}
4 changes: 2 additions & 2 deletions mutagen-selftest/src/test_not_mutated.rs
Original file line number Diff line number Diff line change
@@ -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",

0 comments on commit a6377c4

Please sign in to comment.