forge/mutation/mutators/
require_mutator.rs1use eyre::Result;
18use solar::ast::{CallArgsKind, Expr, ExprKind, UnOpKind};
19
20use super::{MutationContext, Mutator};
21use crate::mutation::mutant::{Mutant, MutationType};
22
23pub struct RequireMutator;
24
25impl Mutator for RequireMutator {
26 fn generate_mutants(&self, context: &MutationContext<'_>) -> Result<Vec<Mutant>> {
27 let expr = context.expr.ok_or_else(|| eyre::eyre!("RequireMutator: no expression"))?;
28
29 let (func_name, args_exprs) = match &expr.kind {
31 ExprKind::Call(callee, call_args) => {
32 let name = match &callee.kind {
33 ExprKind::Ident(ident) => ident.to_string(),
34 _ => return Ok(vec![]),
35 };
36 let exprs = match &call_args.kind {
38 CallArgsKind::Unnamed(exprs) => exprs,
39 CallArgsKind::Named(_) => return Ok(vec![]), };
41 (name, exprs)
42 }
43 _ => return Ok(vec![]),
44 };
45
46 if func_name != "require" && func_name != "assert" {
48 return Ok(vec![]);
49 }
50
51 if args_exprs.is_empty() {
53 return Ok(vec![]);
54 }
55
56 let condition_expr = &args_exprs[0];
57 let original = context.original_text();
58 let source_line = context.source_line();
59 let line_number = context.line_number();
60 let column_number = context.column_number();
61
62 let source = context.source.unwrap_or("");
63
64 let rest_args = if args_exprs.len() > 1 {
68 let start = condition_expr.span.hi().0 as usize;
71 let end = args_exprs.last().map(|e| e.span.hi().0 as usize).unwrap_or(start);
72 source.get(start..end).map(|s| s.to_string()).unwrap_or_default()
73 } else {
74 String::new()
75 };
76
77 let mut mutants = Vec::new();
78 let mut push_mutant = |mutated_call: String, original: String, source_line: String| {
79 if mutated_call.trim() == original.trim() {
80 return;
81 }
82 mutants.push(Mutant {
83 span: expr.span,
84 mutation: MutationType::RequireCondition { mutated_call },
85 path: context.path.clone(),
86 original,
87 source_line,
88 line_number,
89 column_number,
90 });
91 };
92
93 let mutated_true = format!("{func_name}(true{rest_args})");
96 push_mutant(mutated_true, original.clone(), source_line.clone());
97
98 let mutated_false = format!("{func_name}(false{rest_args})");
99 push_mutant(mutated_false, original.clone(), source_line.clone());
100
101 let inverted_condition = invert_condition_text(source, condition_expr);
102 let mutated_inverted = format!("{func_name}({inverted_condition}{rest_args})");
103 push_mutant(mutated_inverted, original, source_line);
104
105 Ok(mutants)
106 }
107
108 fn is_applicable(&self, ctxt: &MutationContext<'_>) -> bool {
109 ctxt.expr
110 .as_ref()
111 .and_then(|expr| match &expr.kind {
112 ExprKind::Call(callee, call_args) => {
113 match &call_args.kind {
115 CallArgsKind::Unnamed(exprs) if !exprs.is_empty() => {}
116 _ => return None,
117 }
118 match &callee.kind {
119 ExprKind::Ident(ident) => Some(ident.to_string()),
120 _ => None,
121 }
122 }
123 _ => None,
124 })
125 .is_some_and(|name| name == "require" || name == "assert")
126 }
127}
128
129fn extract_span_text(source: &str, span: solar::ast::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.to_string()).unwrap_or_default()
134}
135
136fn invert_condition_text(source: &str, condition_expr: &Expr<'_>) -> String {
137 match &condition_expr.kind {
138 ExprKind::Unary(op, inner) if op.kind == UnOpKind::Not => {
139 extract_span_text(source, inner.span)
140 }
141 _ => {
142 let condition = extract_span_text(source, condition_expr.span);
143 format!("!({})", condition.trim())
144 }
145 }
146}