Coverage Report

Created: 2025-10-12 07:32

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/wasm-tools/crates/wasm-mutate/src/mutators/codemotion.rs
Line
Count
Source
1
//! This mutator applies a random mutation over the control flow AST of an input
2
//! binary
3
//!
4
//! To extend `wasm-mutate` with another code motion mutator, the new mutator
5
//! struct should implement the [AstMutator] trait and we strongly recommend the
6
//! usage of the [ir::AstWriter] to define how the mutator writes the new AST back
7
//! to Wasm.
8
//!
9
//! For and example take a look at  [IfComplementMutator][IfComplementMutator]
10
//!
11
//! Register the new mutator then in the meta [CodemotionMutator] logic.
12
//! ```ignore
13
//! let mutators: Vec<Box<dyn AstMutator>> = vec![
14
//!    Box::new(IfComplementMutator),
15
//! ];
16
//! ```
17
18
pub mod if_complement;
19
pub mod ir;
20
pub mod loop_unrolling;
21
22
use self::ir::parse_context::Ast;
23
use super::Mutator;
24
use crate::{
25
    Error, Result, WasmMutate,
26
    module::map_type,
27
    mutators::{
28
        OperatorAndByteOffset,
29
        codemotion::{
30
            if_complement::IfComplementMutator, ir::AstBuilder, loop_unrolling::LoopUnrollMutator,
31
        },
32
    },
33
};
34
use rand::{Rng, prelude::*};
35
use wasm_encoder::{CodeSection, Function, Module, ValType};
36
use wasmparser::{CodeSectionReader, FunctionBody};
37
38
/// Code motion meta mutator, it groups all code motion mutators and select a
39
/// valid random one when an input Wasm binary is passed to it.
40
#[derive(Clone, Copy)]
41
pub struct CodemotionMutator;
42
43
impl CodemotionMutator {
44
151
    fn copy_locals(&self, reader: FunctionBody) -> Result<Vec<(u32, ValType)>> {
45
        // Create the new function
46
151
        let mut localreader = reader.get_locals_reader()?;
47
        // Get current locals and map to encoder types
48
151
        let mut local_count = 0;
49
151
        let current_locals = (0..localreader.get_count())
50
3.36k
            .map(|_| {
51
3.36k
                let (count, ty) = localreader.read().unwrap();
52
3.36k
                local_count += count;
53
3.36k
                (count, map_type(ty).unwrap())
54
3.36k
            })
55
151
            .collect::<Vec<(u32, ValType)>>();
56
57
151
        Ok(current_locals)
58
151
    }
59
60
315
    fn random_mutate(
61
315
        &self,
62
315
        config: &mut WasmMutate,
63
315
        mutators: &[Box<dyn AstMutator>],
64
315
    ) -> crate::Result<(Function, u32)> {
65
315
        let original_code_section = config.info().code.unwrap();
66
315
        let reader = config.info().get_binary_reader(original_code_section);
67
315
        let sectionreader = CodeSectionReader::new(reader)?;
68
315
        let function_count = sectionreader.count();
69
315
        let function_to_mutate = config.rng().random_range(0..function_count);
70
71
        // This split strategy will avoid very often mutating the first function
72
        // and very rarely mutating the last function
73
315
        let all_readers = sectionreader.into_iter().collect::<Result<Vec<_>, _>>()?;
74
75
2.96k
        for fidx in (function_to_mutate..function_count).chain(0..function_to_mutate) {
76
2.96k
            config.consume_fuel(1)?;
77
2.96k
            let reader = all_readers[fidx as usize].clone();
78
2.96k
            let operatorreader = reader.get_operators_reader()?;
79
80
2.96k
            let operators = operatorreader
81
2.96k
                .into_iter_with_offsets()
82
2.96k
                .collect::<wasmparser::Result<Vec<OperatorAndByteOffset>>>()?;
83
84
            // build Ast
85
2.96k
            let ast = AstBuilder.build_ast(&operators)?;
86
            // filter mutators by those applicable
87
2.92k
            let filtered = mutators
88
2.92k
                .iter()
89
5.85k
                .filter(|m| m.can_mutate(config, &ast))
90
2.92k
                .collect::<Vec<_>>();
91
            // If no mutator, just continue to the next function
92
2.92k
            if filtered.is_empty() {
93
2.77k
                continue;
94
151
            }
95
96
151
            match filtered.choose(config.rng()) {
97
151
                Some(choosen_mutator) => {
98
151
                    let newfunc = choosen_mutator.mutate(
99
151
                        config,
100
151
                        &ast,
101
151
                        &self.copy_locals(reader)?,
102
151
                        &operators,
103
151
                        config.info().raw_sections[original_code_section].data,
104
0
                    )?;
105
151
                    return Ok((newfunc, fidx));
106
                }
107
0
                None => continue,
108
            }
109
        }
110
111
129
        Err(Error::no_mutations_applicable())
112
315
    }
113
}
114
/// Trait to be implemented by all code motion mutators
115
pub trait AstMutator {
116
    /// Transform the function AST in order to generate a new Wasm module
117
    fn mutate<'a>(
118
        &self,
119
        config: &'a mut WasmMutate,
120
        ast: &Ast,
121
        locals: &[(u32, ValType)],
122
        operators: &Vec<OperatorAndByteOffset>,
123
        input_wasm: &'a [u8],
124
    ) -> Result<Function>;
125
126
    /// Checks if this mutator can be applied to the passed `ast`
127
    fn can_mutate<'a>(&self, config: &'a crate::WasmMutate, ast: &Ast) -> bool;
128
}
129
130
/// Meta mutator for peephole
131
impl Mutator for CodemotionMutator {
132
315
    fn mutate<'a>(
133
315
        &self,
134
315
        config: &mut WasmMutate<'a>,
135
315
    ) -> Result<Box<dyn Iterator<Item = Result<Module>> + 'a>> {
136
        // Initialize mutators
137
315
        let mutators: Vec<Box<dyn AstMutator>> = vec![
138
315
            Box::new(IfComplementMutator),
139
315
            Box::new(LoopUnrollMutator), // Add the other here
140
        ];
141
142
315
        let (newfunc, function_to_mutate) = self.random_mutate(config, &mutators)?;
143
144
151
        let mut codes = CodeSection::new();
145
151
        let code_section = config.info().get_binary_reader(config.info().code.unwrap());
146
151
        let sectionreader = CodeSectionReader::new(code_section)?;
147
148
2.02k
        for (fidx, reader) in sectionreader.into_iter().enumerate() {
149
2.02k
            let reader = reader?;
150
2.02k
            if fidx as u32 == function_to_mutate {
151
151
                log::trace!("Mutating function {fidx}");
152
151
                codes.function(&newfunc);
153
1.87k
            } else {
154
1.87k
                codes.raw(reader.as_bytes());
155
1.87k
            }
156
        }
157
151
        let module = config
158
151
            .info()
159
151
            .replace_section(config.info().code.unwrap(), &codes);
160
151
        Ok(Box::new(std::iter::once(Ok(module))))
161
315
    }
162
163
626
    fn can_mutate<'a>(&self, config: &'a WasmMutate) -> bool {
164
626
        config.info().has_code() && config.info().num_local_functions() > 0
165
626
    }
166
}
167
168
#[cfg(test)]
169
mod tests {
170
    use crate::{WasmMutate, mutators::codemotion::CodemotionMutator};
171
172
    fn test_motion_mutator(original: &str, expected: &str, seed: u64) {
173
        let mut config = WasmMutate::default();
174
        config.seed(seed);
175
        config.match_mutation(original, CodemotionMutator, expected);
176
    }
177
178
    #[test]
179
    fn test_if_swap() {
180
        test_motion_mutator(
181
            r#"
182
        (module
183
            (memory 1)
184
            (func (export "exported_func") (param i32) (result i32)
185
                (local i32 i32)
186
                local.get 0
187
                if (result i32)
188
                    i32.const 50
189
                else
190
                    i32.const 41
191
                end
192
            )
193
        )
194
        "#,
195
            r#"
196
            (module
197
                (type (;0;) (func (param i32) (result i32)))
198
                (func (;0;) (type 0) (param i32) (result i32)
199
                (local i32 i32)
200
                  local.get 0
201
                  i32.eqz
202
                  if (result i32)  ;; label = @1
203
                    i32.const 41
204
                  else
205
                    i32.const 50
206
                  end)
207
                (memory (;0;) 1)
208
                (export "exported_func" (func 0)))
209
        "#,
210
            0,
211
        );
212
    }
213
214
    #[test]
215
    fn test_if_swap2() {
216
        test_motion_mutator(
217
            r#"
218
        (module
219
            (memory 1)
220
            (func (export "exported_func") (param i32) (result i32)
221
                local.get 0
222
                local.get 0
223
                i32.add
224
                local.get 0
225
                if
226
                    i32.const 150
227
                    drop
228
                else
229
                    i32.const 200
230
                    drop
231
                end
232
                if (result i32)
233
                    i32.const 50
234
                else
235
                    i32.const 41
236
                end
237
            )
238
        )
239
        "#,
240
            r#"
241
            (module
242
                (type (;0;) (func (param i32) (result i32)))
243
                (func (;0;) (type 0) (param i32) (result i32)
244
                  local.get 0
245
                  local.get 0
246
                  i32.add
247
                  local.get 0
248
                  if  ;; label = @1
249
                    i32.const 150
250
                    drop
251
                  else
252
                    i32.const 200
253
                    drop
254
                  end
255
                  i32.eqz
256
                  if (result i32)  ;; label = @1
257
                    i32.const 41
258
                  else
259
                    i32.const 50
260
                  end)
261
                (memory (;0;) 1)
262
                (export "exported_func" (func 0)))
263
        "#,
264
            1,
265
        );
266
    }
267
268
    #[test]
269
    fn test_if_swap3() {
270
        test_motion_mutator(
271
            r#"
272
        (module
273
            (memory 1)
274
            (func (export "exported_func") (param i32) (result i32)
275
                local.get 0
276
                local.get 0
277
                i32.add
278
                local.get 0
279
                if
280
                    i32.const 150
281
                    drop
282
                else
283
                    i32.const 200
284
                    drop
285
                end
286
                if (result i32)
287
                    i32.const 50
288
                else
289
                    unreachable
290
                end
291
            )
292
        )
293
        "#,
294
            r#"
295
            (module
296
                (type (;0;) (func (param i32) (result i32)))
297
                (func (;0;) (type 0) (param i32) (result i32)
298
                  local.get 0
299
                  local.get 0
300
                  i32.add
301
                  local.get 0
302
                  if  ;; label = @1
303
                    i32.const 150
304
                    drop
305
                  else
306
                    i32.const 200
307
                    drop
308
                  end
309
                  i32.eqz
310
                  if (result i32)  ;; label = @1
311
                    unreachable
312
                  else
313
                    i32.const 50
314
                  end)
315
                (memory (;0;) 1)
316
                (export "exported_func" (func 0)))
317
        "#,
318
            1,
319
        );
320
    }
321
322
    #[test]
323
    fn test_unrolling1() {
324
        test_motion_mutator(
325
            r#"
326
        (module
327
            (memory 1)
328
            (func (export "exported_func") (param i32) (result i32)
329
                local.get 0
330
                local.get 0
331
                i32.add
332
                drop
333
                loop
334
                    i32.const 1
335
                    local.get 0
336
                    i32.add
337
                    local.tee 0
338
                    i32.const 100
339
                    i32.le_u
340
                    br_if 0
341
                end
342
                local.get 0
343
            )
344
        )
345
        "#,
346
            r#"
347
            (module
348
                (type (;0;) (func (param i32) (result i32)))
349
                (func (;0;) (type 0) (param i32) (result i32)
350
                  local.get 0
351
                  local.get 0
352
                  i32.add
353
                  drop
354
                  block  ;; label = @1
355
                    block  ;; label = @2
356
                      i32.const 1
357
                      local.get 0
358
                      i32.add
359
                      local.tee 0
360
                      i32.const 100
361
                      i32.le_u
362
                      br_if 0 (;@2;)
363
                      br 1 (;@1;)
364
                    end
365
                    loop  ;; label = @2
366
                      i32.const 1
367
                      local.get 0
368
                      i32.add
369
                      local.tee 0
370
                      i32.const 100
371
                      i32.le_u
372
                      br_if 0 (;@2;)
373
                    end
374
                  end
375
                  local.get 0)
376
                (memory (;0;) 1)
377
                (export "exported_func" (func 0)))
378
379
        "#,
380
            1,
381
        );
382
    }
383
384
    #[test]
385
    fn test_unrolling2() {
386
        test_motion_mutator(
387
            r#"
388
        (module
389
            (memory 1)
390
            (func (export "exported_func") (param i32) (result i32)
391
                local.get 0
392
                local.get 0
393
                i32.add
394
                drop
395
                loop
396
                    i32.const 1
397
                    local.get 0
398
                    i32.add
399
                    local.tee 0
400
                    i32.const 100
401
                    i32.le_u
402
                    br_if 0
403
                    local.get 0
404
                    if
405
                        i32.const 200
406
                        local.get 0
407
                        i32.add
408
                        local.set 0
409
                    else
410
                        i32.const 300
411
                        local.get 0
412
                        i32.add
413
                        local.set 0
414
                    end
415
                end
416
                local.get 0
417
            )
418
        )
419
        "#,
420
            r#"
421
            (module
422
                (type (;0;) (func (param i32) (result i32)))
423
                (func (;0;) (type 0) (param i32) (result i32)
424
                  local.get 0
425
                  local.get 0
426
                  i32.add
427
                  drop
428
                  block  ;; label = @1
429
                    block  ;; label = @2
430
                      i32.const 1
431
                      local.get 0
432
                      i32.add
433
                      local.tee 0
434
                      i32.const 100
435
                      i32.le_u
436
                      br_if 0 (;@2;)
437
                      local.get 0
438
                      if  ;; label = @3
439
                        i32.const 200
440
                        local.get 0
441
                        i32.add
442
                        local.set 0
443
                      else
444
                        i32.const 300
445
                        local.get 0
446
                        i32.add
447
                        local.set 0
448
                      end
449
                      br 1 (;@1;)
450
                    end
451
                    loop  ;; label = @2
452
                      i32.const 1
453
                      local.get 0
454
                      i32.add
455
                      local.tee 0
456
                      i32.const 100
457
                      i32.le_u
458
                      br_if 0 (;@2;)
459
                      local.get 0
460
                      if  ;; label = @3
461
                        i32.const 200
462
                        local.get 0
463
                        i32.add
464
                        local.set 0
465
                      else
466
                        i32.const 300
467
                        local.get 0
468
                        i32.add
469
                        local.set 0
470
                      end
471
                    end
472
                  end
473
                  local.get 0)
474
                (memory (;0;) 1)
475
                (export "exported_func" (func 0)))
476
477
        "#,
478
            1,
479
        );
480
    }
481
482
    #[test]
483
    fn test_unrolling3() {
484
        test_motion_mutator(
485
            r#"
486
        (module
487
            (memory 1)
488
            (func (export "exported_func") (param i32) (result i32)
489
                local.get 0
490
                local.get 0
491
                i32.add
492
                drop
493
                loop
494
                    i32.const 1
495
                    local.get 0
496
                    i32.add
497
                    local.tee 0
498
                    if
499
                        i32.const 200
500
                        local.get 0
501
                        i32.add
502
                        local.tee 0
503
                        br_if 1
504
                    else
505
                        local.get 0
506
                        br_if 1
507
                    end
508
                    local.get 0
509
                    br_if 0
510
                end
511
                local.get 0
512
            )
513
        )
514
        "#,
515
            r#"
516
            (module
517
                (type (;0;) (func (param i32) (result i32)))
518
                (func (;0;) (type 0) (param i32) (result i32)
519
                  local.get 0
520
                  local.get 0
521
                  i32.add
522
                  drop
523
                  block  ;; label = @1
524
                    block  ;; label = @2
525
                      i32.const 1
526
                      local.get 0
527
                      i32.add
528
                      local.tee 0
529
                      if  ;; label = @3
530
                        i32.const 200
531
                        local.get 0
532
                        i32.add
533
                        local.tee 0
534
                        br_if 1 (;@2;)
535
                      else
536
                        local.get 0
537
                        br_if 1 (;@2;)
538
                      end
539
                      local.get 0
540
                      br_if 0 (;@2;)
541
                      br 1 (;@1;)
542
                    end
543
                    loop  ;; label = @2
544
                      i32.const 1
545
                      local.get 0
546
                      i32.add
547
                      local.tee 0
548
                      if  ;; label = @3
549
                        i32.const 200
550
                        local.get 0
551
                        i32.add
552
                        local.tee 0
553
                        br_if 1 (;@2;)
554
                      else
555
                        local.get 0
556
                        br_if 1 (;@2;)
557
                      end
558
                      local.get 0
559
                      br_if 0 (;@2;)
560
                    end
561
                  end
562
                  local.get 0)
563
                (memory (;0;) 1)
564
                (export "exported_func" (func 0)))
565
566
        "#,
567
            1,
568
        );
569
    }
570
571
    #[test]
572
    fn test_unrolling4() {
573
        test_motion_mutator(
574
            r#"
575
        (module
576
            (memory 1)
577
            (func (export "exported_func") (param i32) (result i32)
578
                block
579
                    loop
580
                        loop
581
                            local.get 0
582
                            i32.const 100
583
                            i32.ge_s
584
                            br_if 2
585
                        end
586
                        local.get 0
587
                        i32.const 200
588
                        i32.le_s
589
                        br_if 0
590
                    end
591
                end
592
                local.get 0
593
            )
594
        )
595
        "#,
596
            r#"
597
            (module
598
                (type (;0;) (func (param i32) (result i32)))
599
                (func (;0;) (type 0) (param i32) (result i32)
600
                  block  ;; label = @1
601
                    block  ;; label = @2
602
                      block  ;; label = @3
603
                        loop  ;; label = @4
604
                          local.get 0
605
                          i32.const 100
606
                          i32.ge_s
607
                          br_if 3 (;@1;)
608
                        end
609
                        local.get 0
610
                        i32.const 200
611
                        i32.le_s
612
                        br_if 0 (;@3;)
613
                        br 1 (;@2;)
614
                      end
615
                      loop  ;; label = @3
616
                        loop  ;; label = @4
617
                          local.get 0
618
                          i32.const 100
619
                          i32.ge_s
620
                          br_if 3 (;@1;)
621
                        end
622
                        local.get 0
623
                        i32.const 200
624
                        i32.le_s
625
                        br_if 0 (;@3;)
626
                      end
627
                    end
628
                  end
629
                  local.get 0)
630
                (memory (;0;) 1)
631
                (export "exported_func" (func 0)))
632
633
        "#,
634
            1,
635
        );
636
    }
637
638
    #[test]
639
    fn test_unrolling5() {
640
        test_motion_mutator(
641
            r#"
642
        (module
643
            (memory 1)
644
            (func (export "exported_func") (param i32) (result i32)
645
                block
646
                    loop
647
                        loop
648
                            local.get 0
649
                            br_table 1 2 2 2 2
650
                        end
651
                        local.get 0
652
                        i32.const 200
653
                        i32.le_s
654
                        br_if 0
655
                    end
656
                end
657
                local.get 0
658
            )
659
        )
660
        "#,
661
            r#"
662
            (module
663
                (type (;0;) (func (param i32) (result i32)))
664
                (func (;0;) (type 0) (param i32) (result i32)
665
                  block  ;; label = @1
666
                    block  ;; label = @2
667
                      block  ;; label = @3
668
                        loop  ;; label = @4
669
                          local.get 0
670
                          br_table 1 (;@3;) 3 (;@1;) 3 (;@1;) 3 (;@1;) 3 (;@1;)
671
                        end
672
                        local.get 0
673
                        i32.const 200
674
                        i32.le_s
675
                        br_if 0 (;@3;)
676
                        br 1 (;@2;)
677
                      end
678
                      loop  ;; label = @3
679
                        loop  ;; label = @4
680
                          local.get 0
681
                          br_table 1 (;@3;) 3 (;@1;) 3 (;@1;) 3 (;@1;) 3 (;@1;)
682
                        end
683
                        local.get 0
684
                        i32.const 200
685
                        i32.le_s
686
                        br_if 0 (;@3;)
687
                      end
688
                    end
689
                  end
690
                  local.get 0)
691
                (memory (;0;) 1)
692
                (export "exported_func" (func 0)))
693
        "#,
694
            1,
695
        );
696
    }
697
}