From 64bb95b4195f022f302ccc285dee67aa4f286433 Mon Sep 17 00:00:00 2001 From: Chandra Pratap Date: Wed, 8 Apr 2026 18:26:57 +0000 Subject: [PATCH 1/5] smite-ir: Add `InstructionDeleteMutator` --- smite-ir-mutator/src/lib.rs | 24 +++++--- smite-ir/src/mutators.rs | 2 + smite-ir/src/mutators/instruction_delete.rs | 64 +++++++++++++++++++++ 3 files changed, 82 insertions(+), 8 deletions(-) create mode 100644 smite-ir/src/mutators/instruction_delete.rs diff --git a/smite-ir-mutator/src/lib.rs b/smite-ir-mutator/src/lib.rs index 0903ee2..5bdc328 100644 --- a/smite-ir-mutator/src/lib.rs +++ b/smite-ir-mutator/src/lib.rs @@ -30,7 +30,7 @@ use rand::rngs::SmallRng; use rand::{RngExt, SeedableRng}; use smite_ir::generators::OpenChannelGenerator; -use smite_ir::mutators::{InputSwapMutator, OperationParamMutator}; +use smite_ir::mutators::{InputSwapMutator, InstructionDeleteMutator, OperationParamMutator}; use smite_ir::{Generator, Mutator, Program, ProgramBuilder}; /// Mutator state owned by AFL++ across calls. Allocated by [`afl_custom_init`], @@ -77,12 +77,20 @@ impl MutatorState { let stack = 1u32 << self.rng.random_range(0..=4); for _ in 0..stack { // Uniform pick between the available mutators. - let name = if self.rng.random() { - OperationParamMutator.mutate(program, &mut self.rng); - "op-param" - } else { - InputSwapMutator.mutate(program, &mut self.rng); - "input-swap" + let name = match self.rng.random_range(0..3) { + 0 => { + OperationParamMutator.mutate(program, &mut self.rng); + "op-param" + } + 1 => { + InputSwapMutator.mutate(program, &mut self.rng); + "input-swap" + } + 2 => { + InstructionDeleteMutator.mutate(program, &mut self.rng); + "instr-delete" + } + _ => unreachable!(), }; self.last_sequence.push(name); } @@ -369,7 +377,7 @@ mod tests { } for name in suffix.split(',') { assert!( - name == "op-param" || name == "input-swap", + name == "op-param" || name == "input-swap" || name == "instr-delete", "unexpected mutator name in description: {name:?} (full: {s:?})", ); } diff --git a/smite-ir/src/mutators.rs b/smite-ir/src/mutators.rs index 2c076f0..3d3d500 100644 --- a/smite-ir/src/mutators.rs +++ b/smite-ir/src/mutators.rs @@ -4,9 +4,11 @@ //! structural validity. Each mutator makes a small, targeted change. mod input_swap; +mod instruction_delete; mod operation_param; pub use input_swap::InputSwapMutator; +pub use instruction_delete::InstructionDeleteMutator; pub use operation_param::OperationParamMutator; use rand::Rng; diff --git a/smite-ir/src/mutators/instruction_delete.rs b/smite-ir/src/mutators/instruction_delete.rs new file mode 100644 index 0000000..e14ccff --- /dev/null +++ b/smite-ir/src/mutators/instruction_delete.rs @@ -0,0 +1,64 @@ +//! Mutator that removes an instruction. + +use rand::{Rng, RngExt, seq::IteratorRandom}; + +use super::Mutator; +use crate::Program; + +/// Deletes a randomly selected instruction by removing it from +/// the instuctions list and reindexing the subsequent instructions. +pub struct InstructionDeleteMutator; + +impl Mutator for InstructionDeleteMutator { + fn mutate(&self, program: &mut Program, rng: &mut impl Rng) -> bool { + if program.instructions.is_empty() { + return false; + } + // Pick a random instruction to delete. + let deleted_idx = rng.random_range(0..program.instructions.len()); + let deleted_type = program.instructions[deleted_idx].operation.output_type(); + + // Heal downstream dependencies by swapping references to a prior, + // type-matching variable. + if program.instructions[(deleted_idx + 1)..] + .iter() + .any(|instr| instr.inputs.contains(&deleted_idx)) + { + // Abort if no valid replacement variable exists in the preceding scope. + let Some(replacement_idx) = program.instructions[..deleted_idx] + .iter() + .enumerate() + .filter_map(|(i, instr)| { + let out_type = instr.operation.output_type(); + (out_type.is_some() && out_type == deleted_type).then_some(i) + }) + .choose(rng) + else { + return false; + }; + // Update dependent inputs. + for instr in &mut program.instructions[(deleted_idx + 1)..] { + for input in &mut instr.inputs { + if *input == deleted_idx { + *input = replacement_idx; + } + } + } + } + + // Delete from the program. + program.instructions.remove(deleted_idx); + + // Decrement subsequent references pointing past the deleted index. + program.instructions[deleted_idx..] + .iter_mut() + .for_each(|instr| { + for input in &mut instr.inputs { + if *input > deleted_idx { + *input -= 1; + } + } + }); + true + } +} From d2f808da900ccc3094f40b1b006f3508fa6dd973 Mon Sep 17 00:00:00 2001 From: Chandra Pratap Date: Sun, 12 Apr 2026 07:55:25 +0000 Subject: [PATCH 2/5] smite-ir: Add tests for `InstructionDeleteMutator` --- smite-ir/src/tests.rs | 136 +++++++++++++++++++++++++++++++++++++++++- 1 file changed, 135 insertions(+), 1 deletion(-) diff --git a/smite-ir/src/tests.rs b/smite-ir/src/tests.rs index b72bb82..6adc76f 100644 --- a/smite-ir/src/tests.rs +++ b/smite-ir/src/tests.rs @@ -6,7 +6,7 @@ use smite::bolt::MAX_MESSAGE_SIZE; use super::*; use generators::OpenChannelGenerator; -use mutators::{InputSwapMutator, OperationParamMutator}; +use mutators::{InputSwapMutator, InstructionDeleteMutator, OperationParamMutator}; use operation::{AcceptChannelField, ChannelTypeVariant, ShutdownScriptVariant}; use program::ValidateError; @@ -1058,3 +1058,137 @@ fn input_swap_preserves_types() { } } } + +// -- InstructionDeleteMutator tests -- + +#[test] +fn instr_delete_changes_values() { + let original = generate_program(0); + let mut program = original.clone(); + let mutator = InstructionDeleteMutator; + let mut rng = SmallRng::seed_from_u64(0); + + for _ in 0..100 { + mutator.mutate(&mut program, &mut rng); + } + assert_ne!( + program, original, + "InstructionDeleteMutator never changed the program" + ); +} + +#[test] +fn instr_delete_returns_false_on_empty_program() { + let mut program = Program { + instructions: vec![], + }; + let mutator = InstructionDeleteMutator; + let mut rng = SmallRng::seed_from_u64(0); + assert!(!mutator.mutate(&mut program, &mut rng)); +} + +#[test] +fn instr_delete_returns_false_if_unhealable() { + let original = Program { + instructions: vec![ + Instruction { + operation: Operation::LoadPrivateKey(key(1)), + inputs: vec![], + }, + Instruction { + operation: Operation::DerivePoint, + inputs: vec![0], + }, + ], + }; + let mutator = InstructionDeleteMutator; + let mut rng = SmallRng::seed_from_u64(0); + let mut found_unhealable = false; + + for _ in 0..100 { + let mut program = original.clone(); + if !mutator.mutate(&mut program, &mut rng) { + found_unhealable = true; + break; + } + } + assert!( + found_unhealable, + "mutator never returned false for unhealable instruction" + ); +} + +#[test] +fn instr_delete_shifts_indices_correctly() { + let original = Program { + instructions: vec![ + Instruction { + operation: Operation::LoadPrivateKey(key(1)), + inputs: vec![], + }, + Instruction { + operation: Operation::LoadPrivateKey(key(2)), + inputs: vec![], + }, + Instruction { + operation: Operation::DerivePoint, + inputs: vec![1, 1], + }, + ], + }; + + let mutator = InstructionDeleteMutator; + let mut rng = SmallRng::seed_from_u64(0); + let mut verified_shift = false; + + for _ in 0..100 { + let mut program = original.clone(); + if mutator.mutate(&mut program, &mut rng) && + program.instructions.len() == 2 && + // Check if it deleted index 1, meaning DerivePoint is now at index 1 + program.instructions[1].operation == Operation::DerivePoint + { + // input references must have shifted from [1, 1] down to [0, 0] + assert_eq!(program.instructions[1].inputs, vec![0, 0]); + verified_shift = true; + break; + } + } + assert!( + verified_shift, + "mutator never took the expected target shift path" + ); +} + +#[test] +fn instr_delete_maintains_validity() { + let original = generate_program(0); + let mutator = InstructionDeleteMutator; + let mut rng = SmallRng::seed_from_u64(0); + + for _ in 0..100 { + let mut program = original.clone(); + if mutator.mutate(&mut program, &mut rng) { + for (i, instr) in program.instructions.iter().enumerate() { + let expected_types = instr.operation.input_types(); + for (j, &input_idx) in instr.inputs.iter().enumerate() { + assert!( + input_idx < i, + "instruction {i} input {j}: references undefined variable {input_idx}", + ); + let actual_type = program.instructions[input_idx] + .operation + .output_type() + .unwrap_or_else(|| { + panic!("instruction {i} input {j}: references void at {input_idx}") + }); + assert_eq!( + actual_type, expected_types[j], + "instruction {i} input {j}: expected {:?}, got {actual_type:?}", + expected_types[j], + ); + } + } + } + } +} From 40e36dadaa3d6d521b773e51972922a9f649609e Mon Sep 17 00:00:00 2001 From: Chandra Pratap Date: Wed, 13 May 2026 05:30:10 +0000 Subject: [PATCH 3/5] fix mutator --- smite-ir/src/mutators/instruction_delete.rs | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/smite-ir/src/mutators/instruction_delete.rs b/smite-ir/src/mutators/instruction_delete.rs index e14ccff..5ffa227 100644 --- a/smite-ir/src/mutators/instruction_delete.rs +++ b/smite-ir/src/mutators/instruction_delete.rs @@ -6,7 +6,7 @@ use super::Mutator; use crate::Program; /// Deletes a randomly selected instruction by removing it from -/// the instuctions list and reindexing the subsequent instructions. +/// the instructions list and reindexing the subsequent instructions. pub struct InstructionDeleteMutator; impl Mutator for InstructionDeleteMutator { @@ -30,7 +30,7 @@ impl Mutator for InstructionDeleteMutator { .enumerate() .filter_map(|(i, instr)| { let out_type = instr.operation.output_type(); - (out_type.is_some() && out_type == deleted_type).then_some(i) + (out_type == deleted_type).then_some(i) }) .choose(rng) else { @@ -50,15 +50,13 @@ impl Mutator for InstructionDeleteMutator { program.instructions.remove(deleted_idx); // Decrement subsequent references pointing past the deleted index. - program.instructions[deleted_idx..] - .iter_mut() - .for_each(|instr| { - for input in &mut instr.inputs { - if *input > deleted_idx { - *input -= 1; - } + for instr in &mut program.instructions[deleted_idx..] { + for input in &mut instr.inputs { + if *input > deleted_idx { + *input -= 1; } - }); + } + } true } } From ec7e79bbcffa7713ed0b47c22184a4d5490b0ef8 Mon Sep 17 00:00:00 2001 From: Chandra Pratap Date: Wed, 13 May 2026 06:14:00 +0000 Subject: [PATCH 4/5] fix tests --- smite-ir/src/tests.rs | 39 ++++++++++++++++++--------------------- 1 file changed, 18 insertions(+), 21 deletions(-) diff --git a/smite-ir/src/tests.rs b/smite-ir/src/tests.rs index 6adc76f..f33c533 100644 --- a/smite-ir/src/tests.rs +++ b/smite-ir/src/tests.rs @@ -1077,6 +1077,20 @@ fn instr_delete_changes_values() { ); } +#[test] +fn instr_delete_false_is_noop() { + let original = generate_program(0); + let mutator = InstructionDeleteMutator; + let mut rng = SmallRng::seed_from_u64(0); + + for _ in 0..100 { + let mut program = original.clone(); + if !mutator.mutate(&mut program, &mut rng) { + assert_eq!(program, original, "program modified on false return"); + } + } +} + #[test] fn instr_delete_returns_false_on_empty_program() { let mut program = Program { @@ -1145,7 +1159,7 @@ fn instr_delete_shifts_indices_correctly() { let mut program = original.clone(); if mutator.mutate(&mut program, &mut rng) && program.instructions.len() == 2 && - // Check if it deleted index 1, meaning DerivePoint is now at index 1 + // Check if it deleted a Load* operation, meaning DerivePoint is now at index 1 program.instructions[1].operation == Operation::DerivePoint { // input references must have shifted from [1, 1] down to [0, 0] @@ -1169,26 +1183,9 @@ fn instr_delete_maintains_validity() { for _ in 0..100 { let mut program = original.clone(); if mutator.mutate(&mut program, &mut rng) { - for (i, instr) in program.instructions.iter().enumerate() { - let expected_types = instr.operation.input_types(); - for (j, &input_idx) in instr.inputs.iter().enumerate() { - assert!( - input_idx < i, - "instruction {i} input {j}: references undefined variable {input_idx}", - ); - let actual_type = program.instructions[input_idx] - .operation - .output_type() - .unwrap_or_else(|| { - panic!("instruction {i} input {j}: references void at {input_idx}") - }); - assert_eq!( - actual_type, expected_types[j], - "instruction {i} input {j}: expected {:?}, got {actual_type:?}", - expected_types[j], - ); - } - } + program + .validate() + .expect("InstructionDeleteMutator produces valid programs"); } } } From a5355c6bb083755c045cec3ea01c7d476f786d28 Mon Sep 17 00:00:00 2001 From: Chandra Pratap Date: Thu, 14 May 2026 03:56:06 +0000 Subject: [PATCH 5/5] optimize replacement/healing logic --- smite-ir/src/mutators/instruction_delete.rs | 37 ++++++++++----------- 1 file changed, 17 insertions(+), 20 deletions(-) diff --git a/smite-ir/src/mutators/instruction_delete.rs b/smite-ir/src/mutators/instruction_delete.rs index 5ffa227..5688246 100644 --- a/smite-ir/src/mutators/instruction_delete.rs +++ b/smite-ir/src/mutators/instruction_delete.rs @@ -18,41 +18,38 @@ impl Mutator for InstructionDeleteMutator { let deleted_idx = rng.random_range(0..program.instructions.len()); let deleted_type = program.instructions[deleted_idx].operation.output_type(); - // Heal downstream dependencies by swapping references to a prior, - // type-matching variable. - if program.instructions[(deleted_idx + 1)..] + // If any instruction downstream depends on the deleted one, pick a prior + // type-matching variable to redirect those inputs to. + let replacement_idx = if program.instructions[(deleted_idx + 1)..] .iter() .any(|instr| instr.inputs.contains(&deleted_idx)) { - // Abort if no valid replacement variable exists in the preceding scope. - let Some(replacement_idx) = program.instructions[..deleted_idx] + match program.instructions[..deleted_idx] .iter() .enumerate() .filter_map(|(i, instr)| { - let out_type = instr.operation.output_type(); - (out_type == deleted_type).then_some(i) + (instr.operation.output_type() == deleted_type).then_some(i) }) .choose(rng) - else { - return false; - }; - // Update dependent inputs. - for instr in &mut program.instructions[(deleted_idx + 1)..] { - for input in &mut instr.inputs { - if *input == deleted_idx { - *input = replacement_idx; - } - } + { + Some(idx) => Some(idx), + // Abort if no valid replacement variable exists in the preceding scope. + None => return false, } - } + } else { + None + }; // Delete from the program. program.instructions.remove(deleted_idx); - // Decrement subsequent references pointing past the deleted index. + // Heal downstream inputs: redirect references to the deleted index, and + // decrement references past it. for instr in &mut program.instructions[deleted_idx..] { for input in &mut instr.inputs { - if *input > deleted_idx { + if *input == deleted_idx { + *input = replacement_idx.expect("dependent input implies replacement"); + } else if *input > deleted_idx { *input -= 1; } }