Skip to main content

forge/mutation/mutators/
assignment_mutator.rs

1use alloy_primitives::U256;
2use eyre::Result;
3use solar::ast::{ExprKind, Span};
4
5use crate::mutation::{
6    mutant::{Mutant, MutationType, OwnedLiteral},
7    mutators::{MutationContext, Mutator},
8    visitor::AssignVarTypes,
9};
10
11pub struct AssignmentMutator;
12
13impl Mutator for AssignmentMutator {
14    fn generate_mutants(&self, context: &MutationContext<'_>) -> Result<Vec<Mutant>> {
15        let (assign_var_type, replacement_span) = match extract_rhs_info(context) {
16            Some(info) => info,
17            None => return Ok(vec![]), // is_applicable should filter this
18        };
19
20        let original = context.original_text();
21        let source_line = context.source_line();
22        let line_number = context.line_number();
23        let column_number = context.column_number();
24
25        match assign_var_type {
26            AssignVarTypes::Literal(ref lit) => match lit {
27                OwnedLiteral::Bool(val) => Ok(vec![Mutant {
28                    span: replacement_span,
29                    mutation: MutationType::Assignment(AssignVarTypes::Literal(
30                        OwnedLiteral::Bool(!val),
31                    )),
32                    path: context.path.clone(),
33                    original,
34                    source_line,
35                    line_number,
36                    column_number,
37                }]),
38                OwnedLiteral::Number(val) if *val == U256::ZERO => Ok(vec![]),
39                OwnedLiteral::Number(val) => Ok(vec![
40                    Mutant {
41                        span: replacement_span,
42                        mutation: MutationType::Assignment(AssignVarTypes::Literal(
43                            OwnedLiteral::Number(U256::ZERO),
44                        )),
45                        path: context.path.clone(),
46                        original: original.clone(),
47                        source_line: source_line.clone(),
48                        line_number,
49                        column_number,
50                    },
51                    // Negation of a numeric literal must be carried textually:
52                    // applying `-*val` on a `U256` wraps via two's complement
53                    // and would render as a huge unsigned literal (e.g. `1`
54                    // becomes `2^256 - 1`), producing wrong-source mutants.
55                    Mutant {
56                        span: replacement_span,
57                        mutation: MutationType::Assignment(AssignVarTypes::Literal(
58                            OwnedLiteral::NegatedNumber(*val),
59                        )),
60                        path: context.path.clone(),
61                        original,
62                        source_line,
63                        line_number,
64                        column_number,
65                    },
66                ]),
67                OwnedLiteral::Str { .. } => Ok(vec![]),
68                OwnedLiteral::Rational(_) => Ok(vec![]),
69                OwnedLiteral::Address(_) => Ok(vec![]),
70                OwnedLiteral::Err(_) => Ok(vec![]),
71                // `NegatedNumber` is only ever constructed *as* a mutant; it
72                // does not appear as an original literal in the source AST,
73                // so there is nothing to mutate here.
74                OwnedLiteral::NegatedNumber(_) => Ok(vec![]),
75            },
76            AssignVarTypes::Identifier(ref ident) => Ok(vec![
77                Mutant {
78                    span: replacement_span,
79                    mutation: MutationType::Assignment(AssignVarTypes::Literal(
80                        OwnedLiteral::Number(U256::ZERO),
81                    )),
82                    path: context.path.clone(),
83                    original: original.clone(),
84                    source_line: source_line.clone(),
85                    line_number,
86                    column_number,
87                },
88                Mutant {
89                    span: replacement_span,
90                    mutation: MutationType::Assignment(AssignVarTypes::Identifier(format!(
91                        "-{ident}"
92                    ))),
93                    path: context.path.clone(),
94                    original,
95                    source_line,
96                    line_number,
97                    column_number,
98                },
99            ]),
100        }
101    }
102
103    /// Match is the expr is an assign with a var definition having a literal or identifier as
104    /// initializer
105    fn is_applicable(&self, context: &MutationContext<'_>) -> bool {
106        if let Some(expr) = context.expr {
107            if let ExprKind::Assign(_lhs, _op_opt, rhs_actual_expr) = &expr.kind {
108                matches!(rhs_actual_expr.kind, ExprKind::Lit(..) | ExprKind::Ident(..))
109            } else {
110                false // Not an assign
111            }
112        } else if let Some(var_definition) = context.var_definition {
113            if let Some(init) = &var_definition.initializer {
114                matches!(&init.kind, ExprKind::Lit(..) | ExprKind::Ident(..))
115            } else {
116                false // No initializer
117            }
118        } else {
119            false // Not an expression or var_definition
120        }
121    }
122}
123
124fn extract_rhs_info<'ast>(context: &MutationContext<'ast>) -> Option<(AssignVarTypes, Span)> {
125    let relevant_expr_for_rhs = if let Some(var_definition) = context.var_definition {
126        var_definition.initializer.as_ref()?
127    } else {
128        let expr = context.expr?;
129        match &expr.kind {
130            ExprKind::Assign(_lhs, _op_opt, rhs_actual_expr) => &**rhs_actual_expr,
131            // If the context.expr is already what we want to get the type from
132            // (e.g. a simple Lit or Ident being passed directly, though is_applicable filters this)
133            ExprKind::Lit(..) | ExprKind::Ident(..) => expr,
134            _ => return None,
135        }
136    };
137
138    match &relevant_expr_for_rhs.kind {
139        ExprKind::Lit(kind, _) => {
140            let owned = OwnedLiteral::from(&kind.kind);
141            Some((AssignVarTypes::Literal(owned), relevant_expr_for_rhs.span))
142        }
143        ExprKind::Ident(val) => {
144            Some((AssignVarTypes::Identifier(val.to_string()), relevant_expr_for_rhs.span))
145        }
146        _ => None,
147    }
148}