Skip to main content

forge/mutation/mutators/
function_body_mutator.rs

1use 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                // Skip the value the original body already returns: the default for an empty
33                // body, or the expression of a lone `return` statement.
34                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
72/// Recognizes alternate spellings of the constants emitted by this mutator.
73fn 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}