forge/mutation/mutators/
binary_op_mutator.rs1use eyre::{OptionExt, Result};
2use solar::ast::{BinOp, BinOpKind, Expr, ExprKind, Span};
3
4use super::{MutationContext, Mutator};
5use crate::mutation::mutant::{Mutant, MutationType};
6
7pub struct BinaryOpMutator;
8
9impl Mutator for BinaryOpMutator {
10 fn generate_mutants(&self, context: &MutationContext<'_>) -> Result<Vec<Mutant>> {
11 let expr = context.expr.ok_or_eyre("BinaryOpMutator: no expression")?;
12 let (bin_op, _op_span, lhs, rhs, compound_assignment) = get_bin_op_parts(expr)?;
13 let op = bin_op.kind;
14
15 let operations_bools = vec![
16 BinOpKind::Lt,
17 BinOpKind::Le,
18 BinOpKind::Gt,
19 BinOpKind::Ge,
20 BinOpKind::Eq,
21 BinOpKind::Ne,
22 BinOpKind::Or,
23 BinOpKind::And,
24 ];
25
26 let operations_num_bitwise = vec![
29 BinOpKind::Shr,
30 BinOpKind::Shl,
31 BinOpKind::BitAnd,
32 BinOpKind::BitOr,
33 BinOpKind::BitXor,
34 BinOpKind::Add,
35 BinOpKind::Sub,
36 BinOpKind::Pow,
37 BinOpKind::Mul,
38 BinOpKind::Div,
39 BinOpKind::Rem,
40 ];
41
42 let operations =
43 if operations_bools.contains(&op) { operations_bools } else { operations_num_bitwise };
44
45 let source = context.source.unwrap_or("");
47 let lhs_text = extract_span_text(source, lhs.span);
48 let rhs_text = extract_span_text(source, rhs.span);
49 let op_str = op.to_str();
50
51 let original_expr = if compound_assignment {
52 format!("{lhs_text} {op_str}= {rhs_text}")
53 } else {
54 format!("{lhs_text} {op_str} {rhs_text}")
55 };
56
57 let expr_span = context.span;
59
60 let source_line = context.source_line();
62 let line_number = context.line_number();
63 let column_number = context.column_number();
64
65 Ok(operations
66 .into_iter()
67 .filter(|&kind| kind != op)
68 .filter(|&kind| !compound_assignment || is_valid_compound_assignment_op(kind))
69 .map(|kind| {
70 let mutated_expr = if compound_assignment {
71 format!("{} {}= {}", lhs_text, kind.to_str(), rhs_text)
72 } else {
73 format!("{} {} {}", lhs_text, kind.to_str(), rhs_text)
74 };
75 Mutant {
76 span: expr_span,
77 mutation: MutationType::BinaryOpExpr { new_op: kind, mutated_expr },
78 path: context.path.clone(),
79 original: original_expr.clone(),
80 source_line: source_line.clone(),
81 line_number,
82 column_number,
83 }
84 })
85 .collect())
86 }
87
88 fn is_applicable(&self, ctxt: &MutationContext<'_>) -> bool {
89 if ctxt.expr.is_none() {
90 return false;
91 }
92
93 matches!(
94 ctxt.expr.unwrap().kind,
95 ExprKind::Binary(_, _, _) | ExprKind::Assign(_, Some(_), _)
96 )
97 }
98}
99
100const fn is_valid_compound_assignment_op(kind: BinOpKind) -> bool {
101 matches!(
102 kind,
103 BinOpKind::BitOr
104 | BinOpKind::BitXor
105 | BinOpKind::BitAnd
106 | BinOpKind::Shl
107 | BinOpKind::Shr
108 | BinOpKind::Add
109 | BinOpKind::Sub
110 | BinOpKind::Mul
111 | BinOpKind::Div
112 | BinOpKind::Rem
113 )
114}
115
116fn get_bin_op_parts<'a>(
118 expr: &'a Expr<'a>,
119) -> Result<(BinOp, Span, &'a Expr<'a>, &'a Expr<'a>, bool)> {
120 match &expr.kind {
121 ExprKind::Assign(lhs, Some(op), rhs) => Ok((*op, op.span, lhs, rhs, true)),
122 ExprKind::Binary(lhs, op, rhs) => Ok((*op, op.span, lhs, rhs, false)),
123 _ => {
124 eyre::bail!("BinaryOpMutator: unexpected expression kind");
125 }
126 }
127}
128
129fn extract_span_text(source: &str, span: Span) -> String {
131 let lo = span.lo().0 as usize;
132 let hi = span.hi().0 as usize;
133 source.get(lo..hi).map(|s| s.trim().to_string()).unwrap_or_default()
134}