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::NegatedIdentifier(
91                        ident.clone(),
92                    )),
93                    path: context.path.clone(),
94                    original,
95                    source_line,
96                    line_number,
97                    column_number,
98                },
99            ]),
100            AssignVarTypes::NegatedIdentifier(_) => Ok(vec![]),
101        }
102    }
103
104    /// Match is the expr is an assign with a var definition having a literal or identifier as
105    /// initializer
106    fn is_applicable(&self, context: &MutationContext<'_>) -> bool {
107        if let Some(expr) = context.expr {
108            if let ExprKind::Assign(_lhs, _op_opt, rhs_actual_expr) = &expr.kind {
109                matches!(rhs_actual_expr.kind, ExprKind::Lit(..) | ExprKind::Ident(..))
110            } else {
111                false // Not an assign
112            }
113        } else if let Some(var_definition) = context.var_definition {
114            if let Some(init) = &var_definition.initializer {
115                matches!(&init.kind, ExprKind::Lit(..) | ExprKind::Ident(..))
116            } else {
117                false // No initializer
118            }
119        } else {
120            false // Not an expression or var_definition
121        }
122    }
123}
124
125fn extract_rhs_info<'ast>(context: &MutationContext<'ast>) -> Option<(AssignVarTypes, Span)> {
126    let relevant_expr_for_rhs = if let Some(var_definition) = context.var_definition {
127        var_definition.initializer.as_ref()?
128    } else {
129        let expr = context.expr?;
130        match &expr.kind {
131            ExprKind::Assign(_lhs, _op_opt, rhs_actual_expr) => &**rhs_actual_expr,
132            // If the context.expr is already what we want to get the type from
133            // (e.g. a simple Lit or Ident being passed directly, though is_applicable filters this)
134            ExprKind::Lit(..) | ExprKind::Ident(..) => expr,
135            _ => return None,
136        }
137    };
138
139    match &relevant_expr_for_rhs.kind {
140        ExprKind::Lit(kind, _) => {
141            let owned = OwnedLiteral::from(&kind.kind);
142            Some((AssignVarTypes::Literal(owned), relevant_expr_for_rhs.span))
143        }
144        ExprKind::Ident(val) => {
145            Some((AssignVarTypes::Identifier(val.to_string()), relevant_expr_for_rhs.span))
146        }
147        _ => None,
148    }
149}