forge/mutation/mutators/
unary_op_mutator.rs1use eyre::Result;
2use solar::ast::{ExprKind, Span, UnOpKind};
3
4use super::{MutationContext, Mutator};
5use crate::mutation::mutant::{Mutant, MutationType, UnaryOpMutated};
6
7pub struct UnaryOpMutator;
8
9impl Mutator for UnaryOpMutator {
10 fn generate_mutants(&self, context: &MutationContext<'_>) -> Result<Vec<Mutant>> {
11 let operations = vec![
12 UnOpKind::PreInc, UnOpKind::PreDec, UnOpKind::Neg, UnOpKind::BitNot, ];
17
18 let post_fixed_operations = vec![UnOpKind::PostInc, UnOpKind::PostDec];
19
20 let expr = context.expr.unwrap();
21
22 let (op, target) = match &expr.kind {
23 ExprKind::Unary(un_op, target) => (un_op.kind, &**target),
24 _ => unreachable!(),
25 };
26
27 let target_content = extract_span_text(context.source.unwrap_or(""), target.span);
28 if target_content.is_empty() {
29 return Ok(vec![]);
30 }
31
32 let original = context.original_text();
33 let source_line = context.source_line();
34 let line_number = context.line_number();
35 let column_number = context.column_number();
36
37 if op == UnOpKind::Not {
39 return Ok(vec![Mutant {
40 span: expr.span,
41 mutation: MutationType::UnaryOperator(UnaryOpMutated::new(
42 target_content,
43 UnOpKind::Not,
44 )),
45 path: context.path.clone(),
46 original,
47 source_line,
48 line_number,
49 column_number,
50 }]);
51 }
52
53 let mut mutations: Vec<Mutant>;
54
55 mutations = operations
56 .into_iter()
57 .filter(|&kind| kind != op)
58 .filter(|&kind| {
59 !matches!(kind, UnOpKind::PreInc | UnOpKind::PreDec)
60 || !is_definitely_non_lvalue(target)
61 })
62 .map(|kind| {
63 let new_expression = format!("{}{}", kind.to_str(), target_content);
64
65 let mutated = UnaryOpMutated::new(new_expression, kind);
66
67 Mutant {
68 span: expr.span,
69 mutation: MutationType::UnaryOperator(mutated),
70 path: context.path.clone(),
71 original: original.clone(),
72 source_line: source_line.clone(),
73 line_number,
74 column_number,
75 }
76 })
77 .collect();
78
79 mutations.extend(
80 post_fixed_operations
81 .into_iter()
82 .filter(|&kind| kind != op && !is_definitely_non_lvalue(target))
83 .map(|kind| {
84 let new_expression = format!("{}{}", target_content, kind.to_str());
85
86 let mutated = UnaryOpMutated::new(new_expression, kind);
87
88 Mutant {
89 span: expr.span,
90 mutation: MutationType::UnaryOperator(mutated),
91 path: context.path.clone(),
92 original: original.clone(),
93 source_line: source_line.clone(),
94 line_number,
95 column_number,
96 }
97 }),
98 );
99
100 Ok(mutations)
101 }
102
103 fn is_applicable(&self, ctxt: &MutationContext<'_>) -> bool {
104 if let Some(expr) = ctxt.expr
105 && let ExprKind::Unary(_, _) = &expr.kind
106 {
107 return true;
108 }
109
110 false
111 }
112}
113
114fn is_definitely_non_lvalue(expr: &solar::ast::Expr<'_>) -> bool {
115 if let ExprKind::Call(callee, args) = &expr.peel_parens().kind {
116 return !(args.is_empty()
117 && matches!(
118 &callee.peel_parens().kind,
119 ExprKind::Member(_, member) if member.as_str() == "push"
120 ));
121 }
122
123 matches!(
124 &expr.peel_parens().kind,
125 ExprKind::Array(_)
126 | ExprKind::Assign(..)
127 | ExprKind::Binary(..)
128 | ExprKind::CallOptions(..)
129 | ExprKind::Delete(_)
130 | ExprKind::Lit(..)
131 | ExprKind::New(_)
132 | ExprKind::Payable(_)
133 | ExprKind::Ternary(..)
134 | ExprKind::Tuple(_)
135 | ExprKind::TypeCall(_)
136 | ExprKind::Type(_)
137 | ExprKind::Unary(..)
138 )
139}
140
141fn extract_span_text(source: &str, span: Span) -> String {
142 let lo = span.lo().0 as usize;
143 let hi = span.hi().0 as usize;
144 source.get(lo..hi).map(str::trim).unwrap_or_default().to_string()
145}