Skip to main content

verificar/mutator/
mod.rs

1//! AST mutation operators
2//!
3//! This module provides mutation operators for systematic AST mutations.
4//! Based on Jia & Harman (2011) mutation operator catalog.
5//!
6//! # Mutation Operators
7//!
8//! | Operator | Status | Description | Example |
9//! |----------|--------|-------------|---------|
10//! | AOR | ✓ Implemented | Arithmetic operator replacement | `a + b` → `a - b` |
11//! | ROR | ✓ Implemented | Relational operator replacement | `a < b` → `a <= b` |
12//! | BSR | ✓ Implemented | Boundary substitution | `0` → `-1`, `""` → `" "` |
13//! | LOR | Planned | Logical operator replacement | `a and b` → `a or b` |
14//! | UOI | Planned | Unary operator insertion | `x` → `-x` |
15//! | ABS | Planned | Absolute value insertion | `x` → `abs(x)` |
16//! | SDL | Planned | Statement deletion | Delete random statement |
17//! | SVR | Planned | Scalar variable replacement | `x` → `y` (same type) |
18//!
19//! ## Implementation Status
20//!
21//! Current implementation provides 3/8 operators (AOR, ROR, BSR) using string-based
22//! mutations. Full AST-based mutation support is planned for future releases.
23//! See GitHub issues for operator implementation tracking.
24//!
25//! # AST-based Mutations
26//!
27//! The `AstMutator` provides proper AST-level mutations using the `PythonNode`
28//! representation. Unlike string-based mutations, these guarantee syntactic validity.
29//!
30//! ```rust,ignore
31//! use verificar::mutator::AstMutator;
32//! use verificar::generator::PythonNode;
33//!
34//! let ast = PythonNode::BinOp { ... };
35//! let mutator = AstMutator::new();
36//! let mutations = mutator.mutate(&ast);
37//! ```
38
39mod ast_mutator;
40pub mod cwe_bash;
41mod operators;
42
43pub use ast_mutator::{AstMutation, AstMutator};
44pub use cwe_bash::{generate_cwe_mutations, CweMutation};
45pub use operators::MutationOperator;
46
47use crate::{Error, Result};
48
49/// Mutated code with metadata
50#[derive(Debug, Clone)]
51pub struct MutatedCode {
52    /// Original code before mutation
53    pub original: String,
54    /// Mutated code
55    pub mutated: String,
56    /// Operator applied
57    pub operator: MutationOperator,
58    /// Location of mutation (line, column)
59    pub location: (usize, usize),
60    /// Description of the mutation
61    pub description: String,
62}
63
64/// AST mutator for systematic code mutation
65#[derive(Debug, Default)]
66pub struct Mutator {
67    /// Enabled mutation operators
68    enabled_operators: Vec<MutationOperator>,
69}
70
71impl Mutator {
72    /// Create a new mutator with all operators enabled
73    #[must_use]
74    pub fn new() -> Self {
75        Self {
76            enabled_operators: MutationOperator::all(),
77        }
78    }
79
80    /// Create a mutator with specific operators enabled
81    #[must_use]
82    pub fn with_operators(operators: Vec<MutationOperator>) -> Self {
83        Self {
84            enabled_operators: operators,
85        }
86    }
87
88    /// Generate all possible mutations for the given code
89    ///
90    /// # Errors
91    ///
92    /// Returns an error if mutation fails
93    pub fn mutate(&self, code: &str) -> Result<Vec<MutatedCode>> {
94        if code.is_empty() {
95            return Err(Error::Mutation("cannot mutate empty code".to_string()));
96        }
97
98        contract_pre_mutation_soundness!(code);
99        let mut mutations = Vec::new();
100
101        for operator in &self.enabled_operators {
102            let operator_mutations = self.apply_operator(code, operator)?;
103            mutations.extend(operator_mutations);
104        }
105
106        Ok(mutations)
107    }
108
109    /// Apply a single mutation operator to the code
110    ///
111    /// Current implementation uses string-based mutations for AOR, ROR, BSR.
112    /// Other operators return empty vectors (no mutations).
113    fn apply_operator(&self, code: &str, operator: &MutationOperator) -> Result<Vec<MutatedCode>> {
114        let mutations = match operator {
115            MutationOperator::Aor => self.apply_aor(code),
116            MutationOperator::Ror => self.apply_ror(code),
117            MutationOperator::Lor => self.apply_lor(code),
118            MutationOperator::Uoi => self.apply_uoi(code),
119            MutationOperator::Abs => self.apply_abs(code),
120            MutationOperator::Sdl => self.apply_sdl(code),
121            MutationOperator::Svr => self.apply_svr(code),
122            MutationOperator::Bsr => self.apply_bsr(code),
123        };
124
125        Ok(mutations)
126    }
127
128    fn apply_aor(&self, code: &str) -> Vec<MutatedCode> {
129        let mut mutations = Vec::new();
130
131        // Simple string-based replacement for now
132        if code.contains('+') {
133            mutations.push(MutatedCode {
134                original: code.to_string(),
135                mutated: code.replace('+', "-"),
136                operator: MutationOperator::Aor,
137                location: (1, code.find('+').unwrap_or(0)),
138                description: "Replace + with -".to_string(),
139            });
140        }
141
142        mutations
143    }
144
145    fn apply_ror(&self, code: &str) -> Vec<MutatedCode> {
146        let mut mutations = Vec::new();
147
148        if code.contains('<') && !code.contains("<=") {
149            mutations.push(MutatedCode {
150                original: code.to_string(),
151                mutated: code.replace('<', "<="),
152                operator: MutationOperator::Ror,
153                location: (1, code.find('<').unwrap_or(0)),
154                description: "Replace < with <=".to_string(),
155            });
156        }
157
158        mutations
159    }
160
161    fn apply_lor(&self, _code: &str) -> Vec<MutatedCode> {
162        // LOR (Logical operator replacement) not yet implemented
163        Vec::new()
164    }
165
166    fn apply_uoi(&self, _code: &str) -> Vec<MutatedCode> {
167        // UOI (Unary operator insertion) not yet implemented
168        Vec::new()
169    }
170
171    fn apply_abs(&self, _code: &str) -> Vec<MutatedCode> {
172        // ABS (Absolute value insertion) not yet implemented
173        Vec::new()
174    }
175
176    fn apply_sdl(&self, _code: &str) -> Vec<MutatedCode> {
177        // SDL (Statement deletion) not yet implemented
178        Vec::new()
179    }
180
181    fn apply_svr(&self, _code: &str) -> Vec<MutatedCode> {
182        // SVR (Scalar variable replacement) not yet implemented
183        Vec::new()
184    }
185
186    fn apply_bsr(&self, code: &str) -> Vec<MutatedCode> {
187        let mut mutations = Vec::new();
188
189        // Replace 0 with -1 (boundary substitution)
190        if code.contains(" 0") || code.contains("=0") {
191            mutations.push(MutatedCode {
192                original: code.to_string(),
193                mutated: code.replace(" 0", " -1").replace("=0", "=-1"),
194                operator: MutationOperator::Bsr,
195                location: (1, 0),
196                description: "Replace 0 with -1".to_string(),
197            });
198        }
199
200        mutations
201    }
202
203    /// Get the enabled operators
204    #[must_use]
205    pub fn enabled_operators(&self) -> &[MutationOperator] {
206        &self.enabled_operators
207    }
208}
209
210#[cfg(test)]
211mod tests {
212    use super::*;
213
214    #[test]
215    fn test_mutator_new() {
216        let mutator = Mutator::new();
217        assert_eq!(mutator.enabled_operators().len(), 8);
218    }
219
220    #[test]
221    fn test_mutator_default() {
222        let mutator = Mutator::default();
223        assert!(mutator.enabled_operators().is_empty());
224    }
225
226    #[test]
227    fn test_mutator_with_operators() {
228        let ops = vec![MutationOperator::Aor, MutationOperator::Ror];
229        let mutator = Mutator::with_operators(ops);
230        assert_eq!(mutator.enabled_operators().len(), 2);
231    }
232
233    #[test]
234    fn test_mutator_aor() {
235        let mutator = Mutator::with_operators(vec![MutationOperator::Aor]);
236        let mutations = mutator
237            .mutate("x = a + b")
238            .expect("mutation should succeed");
239        assert!(!mutations.is_empty());
240        assert!(mutations[0].mutated.contains('-'));
241    }
242
243    #[test]
244    fn test_mutator_aor_no_operator() {
245        let mutator = Mutator::with_operators(vec![MutationOperator::Aor]);
246        let mutations = mutator.mutate("x = 5").expect("mutation should succeed");
247        assert!(mutations.is_empty());
248    }
249
250    #[test]
251    fn test_mutator_ror() {
252        let mutator = Mutator::with_operators(vec![MutationOperator::Ror]);
253        let mutations = mutator
254            .mutate("if x < 5:")
255            .expect("mutation should succeed");
256        assert!(!mutations.is_empty());
257        assert!(mutations[0].mutated.contains("<="));
258    }
259
260    #[test]
261    fn test_mutator_ror_already_lte() {
262        let mutator = Mutator::with_operators(vec![MutationOperator::Ror]);
263        let mutations = mutator
264            .mutate("if x <= 5:")
265            .expect("mutation should succeed");
266        assert!(mutations.is_empty()); // Should not mutate <= to <==
267    }
268
269    #[test]
270    fn test_mutator_ror_no_operator() {
271        let mutator = Mutator::with_operators(vec![MutationOperator::Ror]);
272        let mutations = mutator.mutate("x = 5").expect("mutation should succeed");
273        assert!(mutations.is_empty());
274    }
275
276    #[test]
277    fn test_mutator_lor() {
278        let mutator = Mutator::with_operators(vec![MutationOperator::Lor]);
279        let mutations = mutator.mutate("x and y").expect("mutation should succeed");
280        assert!(mutations.is_empty()); // LOR not yet implemented
281    }
282
283    #[test]
284    fn test_mutator_uoi() {
285        let mutator = Mutator::with_operators(vec![MutationOperator::Uoi]);
286        let mutations = mutator.mutate("x = 5").expect("mutation should succeed");
287        assert!(mutations.is_empty()); // UOI not yet implemented
288    }
289
290    #[test]
291    fn test_mutator_abs() {
292        let mutator = Mutator::with_operators(vec![MutationOperator::Abs]);
293        let mutations = mutator.mutate("x = -5").expect("mutation should succeed");
294        assert!(mutations.is_empty()); // ABS not yet implemented
295    }
296
297    #[test]
298    fn test_mutator_sdl() {
299        let mutator = Mutator::with_operators(vec![MutationOperator::Sdl]);
300        let mutations = mutator.mutate("x = 5").expect("mutation should succeed");
301        assert!(mutations.is_empty()); // SDL not yet implemented
302    }
303
304    #[test]
305    fn test_mutator_svr() {
306        let mutator = Mutator::with_operators(vec![MutationOperator::Svr]);
307        let mutations = mutator.mutate("x = y").expect("mutation should succeed");
308        assert!(mutations.is_empty()); // SVR not yet implemented
309    }
310
311    #[test]
312    fn test_mutator_bsr_space_zero() {
313        let mutator = Mutator::with_operators(vec![MutationOperator::Bsr]);
314        let mutations = mutator.mutate("x = 0").expect("mutation should succeed");
315        assert!(!mutations.is_empty());
316        assert!(mutations[0].mutated.contains("-1"));
317    }
318
319    #[test]
320    fn test_mutator_bsr_equals_zero() {
321        let mutator = Mutator::with_operators(vec![MutationOperator::Bsr]);
322        let mutations = mutator.mutate("x=0").expect("mutation should succeed");
323        assert!(!mutations.is_empty());
324        assert!(mutations[0].mutated.contains("=-1"));
325    }
326
327    #[test]
328    fn test_mutator_bsr_no_zero() {
329        let mutator = Mutator::with_operators(vec![MutationOperator::Bsr]);
330        let mutations = mutator.mutate("x = 5").expect("mutation should succeed");
331        assert!(mutations.is_empty());
332    }
333
334    #[test]
335    fn test_mutator_empty_code() {
336        let mutator = Mutator::new();
337        let result = mutator.mutate("");
338        assert!(result.is_err());
339    }
340
341    #[test]
342    fn test_mutator_all_operators() {
343        let mutator = Mutator::new();
344        // Code that triggers AOR, ROR, and BSR
345        let mutations = mutator
346            .mutate("x = a + b if y < 0")
347            .expect("mutation should succeed");
348        assert!(!mutations.is_empty());
349        // Should have AOR (+ to -), ROR (< to <=), and BSR (0 to -1)
350        assert!(mutations
351            .iter()
352            .any(|m| m.operator == MutationOperator::Aor));
353        assert!(mutations
354            .iter()
355            .any(|m| m.operator == MutationOperator::Ror));
356        // BSR checks for " 0" pattern
357        let mutations2 = mutator.mutate("x = 0").expect("mutation should succeed");
358        assert!(mutations2
359            .iter()
360            .any(|m| m.operator == MutationOperator::Bsr));
361    }
362
363    #[test]
364    fn test_mutated_code_debug() {
365        let mc = MutatedCode {
366            original: "x + 1".to_string(),
367            mutated: "x - 1".to_string(),
368            operator: MutationOperator::Aor,
369            location: (1, 2),
370            description: "Replace + with -".to_string(),
371        };
372        let debug = format!("{:?}", mc);
373        assert!(debug.contains("MutatedCode"));
374        assert!(debug.contains("Aor"));
375    }
376
377    #[test]
378    fn test_mutated_code_clone() {
379        let mc = MutatedCode {
380            original: "x + 1".to_string(),
381            mutated: "x - 1".to_string(),
382            operator: MutationOperator::Aor,
383            location: (1, 2),
384            description: "Replace + with -".to_string(),
385        };
386        let cloned = mc.clone();
387        assert_eq!(cloned.original, mc.original);
388        assert_eq!(cloned.mutated, mc.mutated);
389    }
390}