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![
27 BinOpKind::Shr,
28 BinOpKind::Shl,
29 BinOpKind::Sar,
30 BinOpKind::BitAnd,
31 BinOpKind::BitOr,
32 BinOpKind::BitXor,
33 BinOpKind::Add,
34 BinOpKind::Sub,
35 BinOpKind::Pow,
36 BinOpKind::Mul,
37 BinOpKind::Div,
38 BinOpKind::Rem,
39 ];
40
41 let operations =
42 if operations_bools.contains(&op) { operations_bools } else { operations_num_bitwise };
43
44 let source = context.source.unwrap_or("");
46 let lhs_text = extract_span_text(source, lhs.span);
47 let rhs_text = extract_span_text(source, rhs.span);
48 let op_str = op.to_str();
49
50 let original_expr = if compound_assignment {
51 format!("{lhs_text} {op_str}= {rhs_text}")
52 } else {
53 format!("{lhs_text} {op_str} {rhs_text}")
54 };
55
56 let expr_span = context.span;
58
59 let source_line = context.source_line();
61 let line_number = context.line_number();
62 let column_number = context.column_number();
63
64 Ok(operations
65 .into_iter()
66 .filter(|&kind| kind != op)
67 .filter(|&kind| !compound_assignment || is_valid_compound_assignment_op(kind))
68 .map(|kind| {
69 let mutated_expr = if compound_assignment {
70 format!("{} {}= {}", lhs_text, kind.to_str(), rhs_text)
71 } else {
72 format!("{} {} {}", lhs_text, kind.to_str(), rhs_text)
73 };
74 Mutant {
75 span: expr_span,
76 mutation: MutationType::BinaryOpExpr { new_op: kind, mutated_expr },
77 path: context.path.clone(),
78 original: original_expr.clone(),
79 source_line: source_line.clone(),
80 line_number,
81 column_number,
82 }
83 })
84 .collect())
85 }
86
87 fn is_applicable(&self, ctxt: &MutationContext<'_>) -> bool {
88 if ctxt.expr.is_none() {
89 return false;
90 }
91
92 matches!(
93 ctxt.expr.unwrap().kind,
94 ExprKind::Binary(_, _, _) | ExprKind::Assign(_, Some(_), _)
95 )
96 }
97}
98
99const fn is_valid_compound_assignment_op(kind: BinOpKind) -> bool {
100 matches!(
101 kind,
102 BinOpKind::BitOr
103 | BinOpKind::BitXor
104 | BinOpKind::BitAnd
105 | BinOpKind::Shl
106 | BinOpKind::Shr
107 | BinOpKind::Add
108 | BinOpKind::Sub
109 | BinOpKind::Mul
110 | BinOpKind::Div
111 | BinOpKind::Rem
112 )
113}
114
115fn get_bin_op_parts<'a>(
117 expr: &'a Expr<'a>,
118) -> Result<(BinOp, Span, &'a Expr<'a>, &'a Expr<'a>, bool)> {
119 match &expr.kind {
120 ExprKind::Assign(lhs, Some(op), rhs) => Ok((*op, op.span, lhs, rhs, true)),
121 ExprKind::Binary(lhs, op, rhs) => Ok((*op, op.span, lhs, rhs, false)),
122 _ => eyre::bail!("BinaryOpMutator: unexpected expression kind"),
123 }
124}
125
126fn extract_span_text(source: &str, span: Span) -> String {
128 let lo = span.lo().0 as usize;
129 let hi = span.hi().0 as usize;
130 source.get(lo..hi).map(|s| s.trim().to_string()).unwrap_or_default()
131}