Skip to main content

forge/mutation/mutators/
require_mutator.rs

1//! Require/Assert condition mutator.
2//!
3//! This mutator targets security-critical validation patterns in Solidity:
4//! - `require(condition)` / `require(condition, "message")`
5//! - `assert(condition)`
6//!
7//! Mutations generated:
8//! - `require(x)` -> `require(true)` - Always passes (security critical!)
9//! - `require(x)` -> `require(false)` - Always fails
10//! - `require(x)` -> `require(!x)` - Inverted condition
11//!
12//! These mutations are particularly valuable for security testing because:
13//! - Access control checks (onlyOwner patterns)
14//! - Input validation (bounds checking, address validation)
15//! - State preconditions (reentrancy guards, paused checks)
16
17use 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        // Extract function name and arguments
30        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                // Extract the expressions from CallArgs
37                let exprs = match &call_args.kind {
38                    CallArgsKind::Unnamed(exprs) => exprs,
39                    CallArgsKind::Named(_) => return Ok(vec![]), // Named args not supported
40                };
41                (name, exprs)
42            }
43            _ => return Ok(vec![]),
44        };
45
46        // Only handle require and assert
47        if func_name != "require" && func_name != "assert" {
48            return Ok(vec![]);
49        }
50
51        // Need at least one argument (the condition)
52        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        // Build the rest of the call (message and other arguments after the condition,
65        // if any) using span-based extraction so that commas inside the condition
66        // expression (e.g. `require(foo(a, b))`) do not break splitting.
67        let rest_args = if args_exprs.len() > 1 {
68            // Extract from the character right after the condition expression up to
69            // the last argument's end.
70            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        // Mutation 1: require(x) -> require(true)
94        // This is security-critical: if tests pass, the condition was never actually needed
95        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                    // Must have at least one argument
114                    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
129/// Extract text from source given a span
130fn 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}