Skip to main content

forge/mutation/mutators/
unary_op_mutator.rs

1use 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, // number
13            UnOpKind::PreDec, // n
14            UnOpKind::Neg,    // n @todo filter this one only for int
15            UnOpKind::BitNot, // n
16        ];
17
18        let post_fixed_operations = vec![UnOpKind::PostInc, UnOpKind::PostDec];
19
20        let expr = context.expr.unwrap();
21
22        let op;
23        let target_span;
24
25        match &expr.kind {
26            ExprKind::Unary(un_op, target) => {
27                op = un_op.kind;
28                target_span = target.span;
29            }
30            _ => unreachable!(),
31        };
32
33        let target_content = extract_span_text(context.source.unwrap_or(""), target_span);
34        if target_content.is_empty() {
35            return Ok(vec![]);
36        }
37
38        let original = context.original_text();
39        let source_line = context.source_line();
40        let line_number = context.line_number();
41        let column_number = context.column_number();
42
43        // Bool has only the Not operator as possible target -> we try removing it
44        if op == UnOpKind::Not {
45            return Ok(vec![Mutant {
46                span: expr.span,
47                mutation: MutationType::UnaryOperator(UnaryOpMutated::new(
48                    target_content,
49                    UnOpKind::Not,
50                )),
51                path: context.path.clone(),
52                original,
53                source_line,
54                line_number,
55                column_number,
56            }]);
57        }
58
59        let mut mutations: Vec<Mutant>;
60
61        mutations = operations
62            .into_iter()
63            .filter(|&kind| kind != op)
64            .map(|kind| {
65                let new_expression = format!("{}{}", kind.to_str(), target_content);
66
67                let mutated = UnaryOpMutated::new(new_expression, kind);
68
69                Mutant {
70                    span: expr.span,
71                    mutation: MutationType::UnaryOperator(mutated),
72                    path: context.path.clone(),
73                    original: original.clone(),
74                    source_line: source_line.clone(),
75                    line_number,
76                    column_number,
77                }
78            })
79            .collect();
80
81        mutations.extend(post_fixed_operations.into_iter().filter(|&kind| kind != op).map(
82            |kind| {
83                let new_expression = format!("{}{}", target_content, kind.to_str());
84
85                let mutated = UnaryOpMutated::new(new_expression, kind);
86
87                Mutant {
88                    span: expr.span,
89                    mutation: MutationType::UnaryOperator(mutated),
90                    path: context.path.clone(),
91                    original: original.clone(),
92                    source_line: source_line.clone(),
93                    line_number,
94                    column_number,
95                }
96            },
97        ));
98
99        Ok(mutations)
100    }
101
102    fn is_applicable(&self, ctxt: &MutationContext<'_>) -> bool {
103        if let Some(expr) = ctxt.expr
104            && let ExprKind::Unary(_, _) = &expr.kind
105        {
106            return true;
107        }
108
109        false
110    }
111}
112
113fn extract_span_text(source: &str, span: Span) -> String {
114    let lo = span.lo().0 as usize;
115    let hi = span.hi().0 as usize;
116    source.get(lo..hi).map(str::trim).unwrap_or_default().to_string()
117}