Skip to main content

forge/mutation/mutators/
mod.rs

1use std::path::PathBuf;
2
3use eyre::Result;
4use solar::ast::{Expr, ItemFunction, Span, VariableDefinition, yul};
5
6use crate::mutation::Mutant;
7
8pub mod assembly_mutator;
9pub mod assignment_mutator;
10pub mod binary_op_mutator;
11pub mod delete_expression_mutator;
12pub mod elim_delegate_mutator;
13pub mod function_body_mutator;
14pub mod mutator_registry;
15pub mod require_mutator;
16pub mod unary_op_mutator;
17
18pub trait Mutator: Send + Sync {
19    /// Generate all mutant corresponding to a given context
20    fn generate_mutants(&self, ctxt: &MutationContext<'_>) -> Result<Vec<Mutant>>;
21    /// True if a mutator can be applied to an expression/node
22    fn is_applicable(&self, ctxt: &MutationContext<'_>) -> bool;
23}
24
25#[derive(Debug)]
26pub struct MutationContext<'a> {
27    pub path: PathBuf,
28    pub span: Span,
29    /// The expression to mutate
30    pub expr: Option<&'a Expr<'a>>,
31    pub var_definition: Option<&'a VariableDefinition<'a>>,
32    pub function: Option<&'a ItemFunction<'a>>,
33    /// Yul expression for assembly block mutations
34    pub yul_expr: Option<&'a yul::Expr<'a>>,
35    /// The full source code (used to extract original text for mutations)
36    pub source: Option<&'a str>,
37}
38
39impl MutationContext<'_> {
40    /// Extract the original source text covered by this context's span
41    pub fn original_text(&self) -> String {
42        self.source
43            .and_then(|src| {
44                let lo = self.span.lo().0 as usize;
45                let hi = self.span.hi().0 as usize;
46                src.get(lo..hi).map(|s| s.to_string())
47            })
48            .unwrap_or_default()
49    }
50
51    /// Get the line number (1-indexed) for this context's span
52    pub fn line_number(&self) -> usize {
53        self.source
54            .map(|src| {
55                let pos = self.span.lo().0 as usize;
56                src.get(..pos).map(|s| s.lines().count()).unwrap_or(0).max(1)
57            })
58            .unwrap_or(1)
59    }
60
61    /// Get the column number (1-indexed) for this context's span
62    pub fn column_number(&self) -> usize {
63        self.source
64            .map(|src| {
65                let pos = self.span.lo().0 as usize;
66                let line_start =
67                    src.get(..pos).and_then(|s| s.rfind('\n')).map(|i| i + 1).unwrap_or(0);
68                pos - line_start + 1
69            })
70            .unwrap_or(1)
71    }
72
73    /// Get the full source line containing this span
74    pub fn source_line(&self) -> String {
75        self.source
76            .and_then(|src| {
77                let pos = self.span.lo().0 as usize;
78                // Find line start
79                let line_start = src[..pos].rfind('\n').map(|i| i + 1).unwrap_or(0);
80                // Find line end
81                let line_end = src[pos..].find('\n').map(|i| pos + i).unwrap_or(src.len());
82                src.get(line_start..line_end).map(|s| s.trim().to_string())
83            })
84            .unwrap_or_default()
85    }
86}
87
88impl<'a> MutationContext<'a> {
89    #[must_use]
90    pub const fn builder() -> MutationContextBuilder<'a> {
91        MutationContextBuilder::new()
92    }
93}
94
95pub struct MutationContextBuilder<'a> {
96    path: Option<PathBuf>,
97    span: Option<Span>,
98    expr: Option<&'a Expr<'a>>,
99    var_definition: Option<&'a VariableDefinition<'a>>,
100    function: Option<&'a ItemFunction<'a>>,
101    yul_expr: Option<&'a yul::Expr<'a>>,
102    source: Option<&'a str>,
103}
104
105impl<'a> MutationContextBuilder<'a> {
106    // Create a new empty builder
107    pub const fn new() -> Self {
108        MutationContextBuilder {
109            path: None,
110            span: None,
111            expr: None,
112            var_definition: None,
113            function: None,
114            yul_expr: None,
115            source: None,
116        }
117    }
118
119    // Required
120    pub fn with_path(mut self, path: PathBuf) -> Self {
121        self.path = Some(path);
122        self
123    }
124
125    // Required
126    pub const fn with_span(mut self, span: Span) -> Self {
127        self.span = Some(span);
128        self
129    }
130
131    // Optional
132    pub const fn with_expr(mut self, expr: &'a Expr<'a>) -> Self {
133        self.expr = Some(expr);
134        self
135    }
136
137    // Optional
138    pub const fn with_var_definition(mut self, var_definition: &'a VariableDefinition<'a>) -> Self {
139        self.var_definition = Some(var_definition);
140        self
141    }
142
143    pub const fn with_function(mut self, function: &'a ItemFunction<'a>) -> Self {
144        self.function = Some(function);
145        self
146    }
147
148    // Optional
149    pub const fn with_yul_expr(mut self, yul_expr: &'a yul::Expr<'a>) -> Self {
150        self.yul_expr = Some(yul_expr);
151        self
152    }
153
154    // Optional - provide source code for extracting original text
155    pub const fn with_source(mut self, source: &'a str) -> Self {
156        self.source = Some(source);
157        self
158    }
159
160    pub fn build(self) -> Result<MutationContext<'a>, &'static str> {
161        let span = self.span.ok_or("Span is required for MutationContext")?;
162        let path = self.path.ok_or("Path is required for MutationContext")?;
163
164        Ok(MutationContext {
165            path,
166            span,
167            expr: self.expr,
168            var_definition: self.var_definition,
169            function: self.function,
170            yul_expr: self.yul_expr,
171            source: self.source,
172        })
173    }
174}
175
176#[cfg(test)]
177mod tests;