smite-ir/mutators: Add SpliceMutator#135
Conversation
The following commit will implement `SpliceMutator`, which uses the decoded program from this buffer.
Refactor the common logic from `GeneratorInsertionMutator` tests that we will need for implementing `SpliceMutator` tests.
| for instr in &self.splice.instructions { | ||
| let shifted_inputs: Vec<usize> = instr | ||
| .inputs | ||
| .iter() | ||
| .map(|&input| input + insert_idx) | ||
| .collect(); | ||
| builder.append(instr.operation.clone(), &shifted_inputs); |
There was a problem hiding this comment.
This logic is the only logical difference between this mutator and GeneratorInsertion. It could also be implemented as:
| for instr in &self.splice.instructions { | |
| let shifted_inputs: Vec<usize> = instr | |
| .inputs | |
| .iter() | |
| .map(|&input| input + insert_idx) | |
| .collect(); | |
| builder.append(instr.operation.clone(), &shifted_inputs); | |
| for instr in &self.splice.instructions { | |
| let mut inputs = vec![]; | |
| for var_type in instr.operation.input_types() { | |
| inputs.push(builder.pick_variable(var_type, rng)); | |
| } | |
| builder.append(instr.operation.clone(), &inputs); | |
| } |
which I guess would "wire it stronger" to the rest of the program, but I'm not sure if that's a worthwhile tradeoff for the simplicity of the current approach.
There was a problem hiding this comment.
It's an interesting idea, but I think the current simpler approach may be better. By doing pick_variable, we end up losing the specific values that the splice program used (which were selected as interesting after many random mutations), which probably hurts fuzzing effectiveness. We'd also need to figure out how to handle variable types for which pick_variable currently panics.
We also can get some of the same behavior already if InputSwapMutator is stacked after the splice.
| "gen-insert" | ||
| } | ||
| 5 => { | ||
| let mutator = SpliceMutator::new(splice.expect("splice is non-empty").clone()); |
There was a problem hiding this comment.
Nit: the splice program is expected to be present, but the program itself could be empty
| let mutator = SpliceMutator::new(splice.expect("splice is non-empty").clone()); | |
| let mutator = SpliceMutator::new(splice.expect("splice is present").clone()); |
| use crate::{Program, ProgramBuilder}; | ||
|
|
||
| /// Inserts a spliced program at a random point in the given program. | ||
| pub struct SpliceMutator { |
There was a problem hiding this comment.
Nit: perhaps SpliceInsertionMutator would be a more descriptive name. It would also make it clear that this mutator parallels GeneratorInsertionMutator.
| for instr in &self.splice.instructions { | ||
| let shifted_inputs: Vec<usize> = instr | ||
| .inputs | ||
| .iter() | ||
| .map(|&input| input + insert_idx) | ||
| .collect(); | ||
| builder.append(instr.operation.clone(), &shifted_inputs); |
There was a problem hiding this comment.
It's an interesting idea, but I think the current simpler approach may be better. By doing pick_variable, we end up losing the specific values that the splice program used (which were selected as interesting after many random mutations), which probably hurts fuzzing effectiveness. We'd also need to figure out how to handle variable types for which pick_variable currently panics.
We also can get some of the same behavior already if InputSwapMutator is stacked after the splice.
| } | ||
|
|
||
| // Insert the spliced program. | ||
| for instr in &self.splice.instructions { |
There was a problem hiding this comment.
I think it would be interesting to do an experiment where we compare full-program insertion with only inserting a random subset (prefix) of the spliced program.
Full-program insertion will tend to create longer (and slower) programs, so it's possible that prefix insertion actually performs better.
| /// larger leaps per execution, similar in spirit to AFL++'s havoc stage. | ||
| /// Records the ordered list of mutator names in `last_sequence`. | ||
| fn mutate_stacked(&mut self, program: &mut Program) { | ||
| fn mutate_stacked(&mut self, program: &mut Program, splice: Option<&Program>) { |
There was a problem hiding this comment.
Nit: doc comment needs to be updated to explain the new splice parameter
| #[test] | ||
| fn splice_optout_symbol_exists() { |
There was a problem hiding this comment.
Now that splicing is enabled, we should add an FFI test for it. Currently all tests set add_buf=NULL.
| "gen-insert" | ||
| } | ||
| 5 => { | ||
| let mutator = SpliceMutator::new(splice.expect("splice is non-empty").clone()); |
There was a problem hiding this comment.
Nit: we can eliminate the program clone by hoisting splice mutator creation out of the loop and having mutate_stacked take ownership of the splice.
fn mutate_stacked(..., splice: Option<Program>) {
...
let splice_mutator = splice.map(SpliceMutator::new);
let upper_bound = if splice_mutator.is_some() { 6 } else { 5 };
for _ in 0..stack {
let name = match self.rng.random_range(0..upper_bound) {
...
5 => {
splice_mutator.as_ref().expect("splice present").mutate(program, &mut self.rng);
"splice"
}
...| assert_graph_shifted_correctly( | ||
| &original, | ||
| &splice_program, | ||
| splice_program.instructions.len(), | ||
| ); |
There was a problem hiding this comment.
This is testing the wrong thing. I'm surprised it actually passes.
| assert_graph_shifted_correctly( | |
| &original, | |
| &splice_program, | |
| splice_program.instructions.len(), | |
| ); | |
| assert_graph_shifted_correctly( | |
| &original, | |
| &program, | |
| splice_program.instructions.len(), | |
| ); |
Add
SpliceMutatorfor smite-IR. Mutates a given program by inserting a spliced input at a random point in the said program.