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
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, 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        // Bool has only the Not operator as possible target -> we try removing it
38        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}