Skip to main content

forge/mutation/mutators/
mod.rs

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