forge/mutation/mutators/
function_body_mutator.rs1use eyre::Result;
2use solar::ast::{
3 CallArgsKind, ElementaryType, Expr, ExprKind, FunctionKind, LitKind, StmtKind, TypeKind,
4 UnOpKind,
5};
6
7use super::{MutationContext, Mutator};
8use crate::mutation::mutant::{Mutant, MutationType};
9
10pub struct FunctionBodyMutator;
11
12impl Mutator for FunctionBodyMutator {
13 fn generate_mutants(&self, ctxt: &MutationContext<'_>) -> Result<Vec<Mutant>> {
14 let Some(function) = ctxt.function else { return Ok(Vec::new()) };
15 let Some(body) = &function.body else { return Ok(Vec::new()) };
16 let bodies = match function.header.returns() {
17 [] if body.is_empty() => Vec::new(),
18 [] => vec!["{}".to_string()],
19 [ret] => {
20 let (values, default) = match ret.ty.kind {
21 TypeKind::Elementary(ElementaryType::Bool) => (&["true", "false"][..], "false"),
22 TypeKind::Elementary(ElementaryType::UInt(_)) => (&["0", "1"][..], "0"),
23 TypeKind::Elementary(ElementaryType::Int(_)) => (&["0", "1", "-1"][..], "0"),
24 TypeKind::Elementary(ElementaryType::Address(false)) => {
25 (&["address(0)"][..], "address(0)")
26 }
27 TypeKind::Elementary(ElementaryType::Address(true)) => {
28 (&["payable(address(0))"][..], "payable(address(0))")
29 }
30 _ => return Ok(Vec::new()),
31 };
32 let returned = if body.is_empty() {
35 Some(default)
36 } else if let [stmt] = &body[..]
37 && let StmtKind::Return(Some(expr)) = &stmt.kind
38 {
39 constant_return(expr)
40 } else {
41 None
42 };
43 values
44 .iter()
45 .filter(|value| returned != Some(**value))
46 .map(|value| format!("{{ return {value}; }}"))
47 .collect()
48 }
49 _ => Vec::new(),
50 };
51 Ok(bodies
52 .into_iter()
53 .map(|body| Mutant {
54 span: ctxt.span,
55 mutation: MutationType::FunctionBody(body),
56 path: ctxt.path.clone(),
57 original: ctxt.original_text(),
58 source_line: ctxt.source_line(),
59 line_number: ctxt.line_number(),
60 column_number: ctxt.column_number(),
61 })
62 .collect())
63 }
64
65 fn is_applicable(&self, ctxt: &MutationContext<'_>) -> bool {
66 ctxt.function.is_some_and(|function| {
67 function.kind == FunctionKind::Function && function.body.is_some()
68 })
69 }
70}
71
72fn constant_return(expr: &Expr<'_>) -> Option<&'static str> {
74 match &expr.peel_parens().kind {
75 ExprKind::Lit(lit, _) => match &lit.kind {
76 LitKind::Bool(true) => Some("true"),
77 LitKind::Bool(false) => Some("false"),
78 LitKind::Number(n) if n.is_zero() => Some("0"),
79 LitKind::Number(n) if *n == alloy_primitives::U256::from(1) => Some("1"),
80 _ => None,
81 },
82 ExprKind::Unary(op, inner) if op.kind == UnOpKind::Neg => match constant_return(inner)? {
83 "0" => Some("0"),
84 "1" => Some("-1"),
85 "-1" => Some("1"),
86 _ => None,
87 },
88 ExprKind::Call(callee, args)
89 if let ExprKind::Type(ty) = &callee.peel_parens().kind
90 && let CallArgsKind::Unnamed(args) = &args.kind
91 && let [arg] = &args[..] =>
92 {
93 let value = constant_return(arg)?;
94 match (&ty.kind, value) {
95 (TypeKind::Elementary(ElementaryType::Bool), "true" | "false")
96 | (TypeKind::Elementary(ElementaryType::UInt(_)), "0" | "1")
97 | (TypeKind::Elementary(ElementaryType::Int(_)), "0" | "1" | "-1") => Some(value),
98 (TypeKind::Elementary(ElementaryType::Address(false)), "0" | "address(0)") => {
99 Some("address(0)")
100 }
101 _ => None,
102 }
103 }
104 ExprKind::Payable(args)
105 if let CallArgsKind::Unnamed(args) = &args.kind
106 && let [arg] = &args[..] =>
107 {
108 (constant_return(arg)? == "address(0)").then_some("payable(address(0))")
109 }
110 _ => None,
111 }
112}