Skip to content

Commit a6377c4

Browse files
authored
Add str literal mutation (#189)
* 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
1 parent a910410 commit a6377c4

File tree

7 files changed

+439
-2
lines changed

7 files changed

+439
-2
lines changed

docs/mutators.md

+17
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,23 @@ Byte-literals like `b'a'` are not mutated by this mutator.
3333

3434
Customization is WIP
3535

36+
## lit_str
37+
38+
### Target Code
39+
40+
`&str` literals.
41+
42+
Char literals like `'a'` are not mutated by this mutator.
43+
44+
### Mutations
45+
46+
* If not empty:
47+
* Replace the literal with an empty string
48+
* Prepend `'-'`
49+
* Append `'-'`
50+
* If empty:
51+
* Replace the literal with `"A"`
52+
3653
## unop_not
3754

3855
### Target Code

mutagen-core/src/mutator.rs

+1
Original file line numberDiff line numberDiff line change
@@ -6,5 +6,6 @@ pub mod mutator_binop_eq;
66
pub mod mutator_binop_num;
77
pub mod mutator_lit_bool;
88
pub mod mutator_lit_int;
9+
pub mod mutator_lit_str;
910
pub mod mutator_stmt_call;
1011
pub mod mutator_unop_not;
+199
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,199 @@
1+
//! Mutator for str literals.
2+
3+
use std::convert::TryFrom;
4+
use std::ops::Deref;
5+
6+
use proc_macro2::Span;
7+
use quote::quote_spanned;
8+
use syn::{Expr, Lit, LitStr};
9+
10+
use crate::comm::Mutation;
11+
use crate::transformer::transform_info::SharedTransformInfo;
12+
use crate::transformer::TransformContext;
13+
14+
use crate::MutagenRuntimeConfig;
15+
16+
pub fn run(
17+
mutator_id: usize,
18+
original_lit: &'static str,
19+
mutations: &[&'static str],
20+
runtime: &impl Deref<Target = MutagenRuntimeConfig>,
21+
) -> &'static str {
22+
runtime.covered(mutator_id);
23+
if let Some(m) = runtime.get_mutation_for_mutator(mutator_id, &mutations) {
24+
m
25+
} else {
26+
original_lit
27+
}
28+
}
29+
30+
pub fn transform(
31+
e: Expr,
32+
transform_info: &SharedTransformInfo,
33+
context: &TransformContext,
34+
) -> Expr {
35+
let e = match ExprLitStr::try_from(e) {
36+
Ok(e) => e,
37+
Err(e) => return e,
38+
};
39+
40+
let possible_mutations = MutationLitStr::possible_mutations(e.clone().value);
41+
let mutations: Vec<_> = possible_mutations
42+
.iter()
43+
.map(|x| x.mutate(&e.clone().value))
44+
.collect();
45+
46+
let mutator_id = transform_info.add_mutations(
47+
possible_mutations
48+
.into_iter()
49+
.map(|m| m.to_mutation(&e, context)),
50+
);
51+
52+
let original_lit = e.lit.value();
53+
54+
syn::parse2(quote_spanned! {e.span=>
55+
::mutagen::mutator::mutator_lit_str::run(
56+
#mutator_id,
57+
#original_lit,
58+
&[#(&#mutations),*], // Expands to `&[mutations[0], mutations[1], ..., [mutations[n]]]`
59+
&::mutagen::MutagenRuntimeConfig::get_default()
60+
)
61+
})
62+
.expect("transformed code invalid")
63+
}
64+
65+
#[derive(Clone, Debug, PartialEq, Eq)]
66+
enum MutationLitStr {
67+
Clear,
68+
Set(&'static str),
69+
Append(char),
70+
Prepend(char),
71+
}
72+
73+
impl MutationLitStr {
74+
fn possible_mutations(val: String) -> Vec<Self> {
75+
let mut mutations = vec![];
76+
if val.is_empty() {
77+
mutations.push(Self::Set("A"))
78+
} else {
79+
mutations.push(Self::Clear);
80+
mutations.push(Self::Prepend('-'));
81+
mutations.push(Self::Append('-'));
82+
}
83+
mutations
84+
}
85+
86+
fn mutate(&self, val: &str) -> String {
87+
match self {
88+
Self::Clear => "".to_string(),
89+
Self::Set(string) => string.to_string(),
90+
Self::Append(char) => {
91+
let mut new = val.to_string();
92+
new.push(*char);
93+
new
94+
}
95+
Self::Prepend(char) => {
96+
let mut new = val.to_string();
97+
new.insert(0, *char);
98+
new
99+
}
100+
}
101+
}
102+
103+
fn to_mutation(&self, original_lit: &ExprLitStr, context: &TransformContext) -> Mutation {
104+
Mutation::new_spanned(
105+
context,
106+
"lit_str".to_owned(),
107+
original_lit.value.to_string(),
108+
self.mutate(&original_lit.value).to_string(),
109+
original_lit.span,
110+
)
111+
}
112+
}
113+
114+
#[derive(Clone, Debug)]
115+
pub struct ExprLitStr {
116+
pub value: String,
117+
pub lit: LitStr,
118+
pub span: Span,
119+
}
120+
121+
impl TryFrom<Expr> for ExprLitStr {
122+
type Error = Expr;
123+
fn try_from(expr: Expr) -> Result<Self, Expr> {
124+
match expr {
125+
Expr::Lit(expr) => match expr.lit {
126+
Lit::Str(lit) => Ok(ExprLitStr {
127+
value: lit.value(),
128+
span: lit.span(),
129+
lit,
130+
}),
131+
_ => Err(Expr::Lit(expr)),
132+
},
133+
_ => Err(expr),
134+
}
135+
}
136+
}
137+
138+
#[cfg(test)]
139+
mod tests {
140+
141+
use super::*;
142+
use crate::MutagenRuntimeConfig;
143+
144+
#[test]
145+
pub fn mutator_lit_str_empty_inactive() {
146+
let result = run(1, "", &["A"], &&MutagenRuntimeConfig::without_mutation());
147+
assert_eq!(result, "")
148+
}
149+
150+
#[test]
151+
pub fn mutator_lit_str_non_empty_inactive() {
152+
let result = run(
153+
1,
154+
"ABCD",
155+
&["", "-ABCD", "ABCD-"],
156+
&&MutagenRuntimeConfig::without_mutation(),
157+
);
158+
assert_eq!(result, "ABCD")
159+
}
160+
161+
#[test]
162+
pub fn mutator_lit_str_non_empty_active_1() {
163+
let result = run(
164+
1,
165+
"a",
166+
&["", "-ABCD", "ABCD-"],
167+
&&MutagenRuntimeConfig::with_mutation_id(1),
168+
);
169+
assert_eq!(result, "")
170+
}
171+
172+
#[test]
173+
pub fn mutator_lit_str_non_empty_active_2() {
174+
let result = run(
175+
1,
176+
"a",
177+
&["", "-ABCD", "ABCD-"],
178+
&&MutagenRuntimeConfig::with_mutation_id(2),
179+
);
180+
assert_eq!(result, "-ABCD")
181+
}
182+
183+
#[test]
184+
pub fn mutator_lit_str_non_empty_active_3() {
185+
let result = run(
186+
1,
187+
"a",
188+
&["", "-ABCD", "ABCD-"],
189+
&&MutagenRuntimeConfig::with_mutation_id(3),
190+
);
191+
assert_eq!(result, "ABCD-")
192+
}
193+
194+
#[test]
195+
pub fn mutator_lit_str_empty_active_1() {
196+
let result = run(1, "", &["A"], &&MutagenRuntimeConfig::with_mutation_id(1));
197+
assert_eq!(result, "A")
198+
}
199+
}

mutagen-core/src/transformer.rs

+2
Original file line numberDiff line numberDiff line change
@@ -228,6 +228,7 @@ impl MutagenTransformerBundle {
228228
) -> MutagenTransformer {
229229
match transformer_name {
230230
"lit_int" => MutagenTransformer::Expr(Box::new(mutator_lit_int::transform)),
231+
"lit_str" => MutagenTransformer::Expr(Box::new(mutator_lit_str::transform)),
231232
"lit_bool" => MutagenTransformer::Expr(Box::new(mutator_lit_bool::transform)),
232233
"unop_not" => MutagenTransformer::Expr(Box::new(mutator_unop_not::transform)),
233234
"binop_bit" => MutagenTransformer::Expr(Box::new(mutator_binop_bit::transform)),
@@ -244,6 +245,7 @@ impl MutagenTransformerBundle {
244245
pub fn all_transformers() -> Vec<String> {
245246
[
246247
"lit_int",
248+
"lit_str",
247249
"lit_bool",
248250
"unop_not",
249251
"binop_bit",

mutagen-selftest/src/mutator.rs

+1
Original file line numberDiff line numberDiff line change
@@ -5,5 +5,6 @@ mod test_binop_eq;
55
mod test_binop_num;
66
mod test_lit_bool;
77
mod test_lit_int;
8+
mod test_lit_str;
89
mod test_stmt_call;
910
mod test_unop_not;

0 commit comments

Comments
 (0)