Skip to content

Commit 46bdf2b

Browse files
authored
Merge pull request #61 from Chand-ra/instr_reorder
smite-ir: Add `InstructionReorderMutator`
2 parents 4274637 + c93da82 commit 46bdf2b

4 files changed

Lines changed: 245 additions & 13 deletions

File tree

smite-ir-mutator/src/lib.rs

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,9 @@ use smite_ir::generators::{
4444
OpenChannelGenerator,
4545
};
4646
use smite_ir::minimizers::{CommonSubexpressionEliminator, DeadCodeEliminator, Minimizer};
47-
use smite_ir::mutators::{InputSwapMutator, InstructionDeleteMutator, OperationParamMutator};
47+
use smite_ir::mutators::{
48+
InputSwapMutator, InstructionDeleteMutator, InstructionReorderMutator, OperationParamMutator,
49+
};
4850
use smite_ir::{Generator, Mutator, Program, ProgramBuilder};
4951

5052
/// Mutator state owned by AFL++ across calls. Allocated by [`afl_custom_init`],
@@ -98,7 +100,7 @@ impl MutatorState {
98100
let stack = 1u32 << self.rng.random_range(0..=4);
99101
for _ in 0..stack {
100102
// Uniform pick between the available mutators.
101-
let name = match self.rng.random_range(0..3) {
103+
let name = match self.rng.random_range(0..4) {
102104
0 => {
103105
OperationParamMutator.mutate(program, &mut self.rng);
104106
"op-param"
@@ -111,6 +113,10 @@ impl MutatorState {
111113
InstructionDeleteMutator.mutate(program, &mut self.rng);
112114
"instr-delete"
113115
}
116+
3 => {
117+
InstructionReorderMutator.mutate(program, &mut self.rng);
118+
"instr-reorder"
119+
}
114120
_ => unreachable!("random_range() bound out of sync with match arms"),
115121
};
116122
self.last_sequence.push(name);
@@ -548,7 +554,10 @@ mod tests {
548554
}
549555
for name in suffix.split(',') {
550556
assert!(
551-
name == "op-param" || name == "input-swap" || name == "instr-delete",
557+
name == "op-param"
558+
|| name == "input-swap"
559+
|| name == "instr-delete"
560+
|| name == "instr-reorder",
552561
"unexpected mutator name in description: {name:?} (full: {s:?})",
553562
);
554563
}

smite-ir/src/mutators.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,12 @@
55
66
mod input_swap;
77
mod instruction_delete;
8+
mod instruction_reorder;
89
mod operation_param;
910

1011
pub use input_swap::InputSwapMutator;
1112
pub use instruction_delete::InstructionDeleteMutator;
13+
pub use instruction_reorder::InstructionReorderMutator;
1214
pub use operation_param::OperationParamMutator;
1315

1416
use rand::Rng;
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
//! Mutator that swaps Act instructions.
2+
3+
use rand::{Rng, seq::IteratorRandom};
4+
5+
use super::Mutator;
6+
use crate::Program;
7+
8+
/// Swaps two `Act` instructions that have no data dependencies between them.
9+
/// This explores alternative execution orderings while preserving SSA invariants.
10+
pub struct InstructionReorderMutator;
11+
12+
impl Mutator for InstructionReorderMutator {
13+
fn mutate(&self, program: &mut Program, rng: &mut impl Rng) -> bool {
14+
// Select an Act instruction at random (say Act_1).
15+
let Some(act1_idx) = program
16+
.instructions
17+
.iter()
18+
.enumerate()
19+
.filter_map(|(i, instr)| instr.operation.has_side_effects().then_some(i))
20+
.choose(rng)
21+
else {
22+
return false;
23+
};
24+
25+
// Find the first instruction that consumes Act_1. We cannot move
26+
// Act_1 past this point without breaking def-before-use.
27+
let mut usage_boundary = act1_idx;
28+
for instr in &program.instructions[(act1_idx + 1)..] {
29+
if instr.inputs.contains(&act1_idx) {
30+
break;
31+
}
32+
usage_boundary += 1;
33+
}
34+
35+
// Uniformly sample a valid Act_2 from the safe range.
36+
let Some(act2_idx) = ((act1_idx + 1)..=usage_boundary)
37+
.filter(|&i| {
38+
// Ensure the candidate is an Act and does not depend on anything
39+
// defined at or after Act_1, guaranteeing it can be safely moved up.
40+
program.instructions[i].operation.has_side_effects()
41+
&& !program.instructions[i]
42+
.inputs
43+
.iter()
44+
.any(|&input| input >= act1_idx)
45+
})
46+
.choose(rng)
47+
else {
48+
// Abort if no valid independent Act instructions exist in the range.
49+
return false;
50+
};
51+
52+
// Swap Act_1 and Act_2.
53+
program.instructions.swap(act1_idx, act2_idx);
54+
55+
// Healing: Update downstream references to Act_1 and Act_2.
56+
for instr in &mut program.instructions[(act2_idx + 1)..] {
57+
for input in &mut instr.inputs {
58+
if *input == act1_idx {
59+
*input = act2_idx;
60+
} else if *input == act2_idx {
61+
*input = act1_idx;
62+
}
63+
}
64+
}
65+
true
66+
}
67+
}

smite-ir/src/tests.rs

Lines changed: 164 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,9 @@ use generators::{
1111
OpenChannelGenerator,
1212
};
1313
use minimizers::{CommonSubexpressionEliminator, DeadCodeEliminator, Minimizer};
14-
use mutators::{InputSwapMutator, InstructionDeleteMutator, OperationParamMutator};
14+
use mutators::{
15+
InputSwapMutator, InstructionDeleteMutator, InstructionReorderMutator, OperationParamMutator,
16+
};
1517
use operation::{AcceptChannelField, ChannelTypeVariant, ShutdownScriptVariant};
1618

1719
/// Helper to build a private key with a single distinguishing byte.
@@ -1361,6 +1363,16 @@ fn assert_false_is_noop<M: Mutator>(mutator: &M, original: &Program) {
13611363
}
13621364
}
13631365

1366+
fn assert_mutator_preserves_well_formedness<M: Mutator>(mutator: &M, original: &Program) {
1367+
let mut rng = SmallRng::seed_from_u64(0);
1368+
for _ in 0..100 {
1369+
let mut program = original.clone();
1370+
if mutator.mutate(&mut program, &mut rng) {
1371+
assert_well_formed(&program);
1372+
}
1373+
}
1374+
}
1375+
13641376
#[test]
13651377
fn param_mutator_false_is_noop() {
13661378
let original = Program {
@@ -1650,15 +1662,7 @@ fn input_swap_returns_false_when_no_alternatives() {
16501662
#[test]
16511663
fn input_swap_preserves_well_formedness() {
16521664
let original = generate_open_channel_program(0);
1653-
let mutator = InputSwapMutator;
1654-
let mut rng = SmallRng::seed_from_u64(0);
1655-
1656-
for _ in 0..100 {
1657-
let mut program = original.clone();
1658-
if mutator.mutate(&mut program, &mut rng) {
1659-
assert_well_formed(&program);
1660-
}
1661-
}
1665+
assert_mutator_preserves_well_formedness(&InputSwapMutator, &original);
16621666
}
16631667

16641668
#[test]
@@ -1922,6 +1926,156 @@ fn instr_delete_maintains_validity() {
19221926
}
19231927
}
19241928

1929+
// -- InstructionReorderMutator tests --
1930+
1931+
#[test]
1932+
fn instr_reorder_false_is_noop() {
1933+
let original = generate_open_channel_program(0);
1934+
assert_false_is_noop(&InstructionReorderMutator, &original);
1935+
}
1936+
1937+
#[test]
1938+
fn instr_reorder_returns_false_on_empty() {
1939+
let mut program = Program {
1940+
instructions: vec![],
1941+
};
1942+
let mut rng = SmallRng::seed_from_u64(0);
1943+
let mutator = InstructionReorderMutator;
1944+
assert!(!mutator.mutate(&mut program, &mut rng));
1945+
}
1946+
1947+
#[test]
1948+
fn instr_reorder_returns_false_on_single_or_no_act() {
1949+
// ChannelAnnouncementGenerator produces a single Act instruction: SendMessage.
1950+
let mut program = generate_channel_announcement_program(0);
1951+
let mutator = InstructionReorderMutator;
1952+
let mut rng = SmallRng::seed_from_u64(0);
1953+
1954+
assert!(!mutator.mutate(&mut program, &mut rng));
1955+
// Delete the SendMessage instruction at the end. The program is now
1956+
// non-empty with 0 Act instructions.
1957+
program.instructions.pop();
1958+
assert!(!mutator.mutate(&mut program, &mut rng));
1959+
}
1960+
1961+
#[test]
1962+
fn instr_reorder_returns_false_if_act2_is_past_usage_boundary() {
1963+
// Invalid program, but mutators shouldn't care about program validity.
1964+
// Two of the three Act instructions are immediately consumed.
1965+
let mut program = Program {
1966+
instructions: vec![
1967+
Instruction {
1968+
operation: Operation::BuildOpenChannel,
1969+
inputs: vec![],
1970+
},
1971+
Instruction {
1972+
operation: Operation::SendOpenChannel,
1973+
inputs: vec![0],
1974+
},
1975+
Instruction {
1976+
operation: Operation::RecvAcceptChannel,
1977+
inputs: vec![1],
1978+
},
1979+
Instruction {
1980+
operation: Operation::ExtractAcceptChannel(AcceptChannelField::ChannelType),
1981+
inputs: vec![2],
1982+
},
1983+
Instruction {
1984+
operation: Operation::SendOpenChannel,
1985+
inputs: vec![0],
1986+
},
1987+
],
1988+
};
1989+
let mut rng = SmallRng::seed_from_u64(0);
1990+
let mutator = InstructionReorderMutator;
1991+
for _ in 0..100 {
1992+
assert!(!mutator.mutate(&mut program, &mut rng));
1993+
}
1994+
}
1995+
1996+
#[test]
1997+
fn instr_reorder_swaps_and_heals() {
1998+
// The only swappable instructions are RecvAcceptChannel and SendMessage.
1999+
let mut program = Program {
2000+
instructions: vec![
2001+
Instruction {
2002+
operation: Operation::BuildOpenChannel,
2003+
inputs: vec![],
2004+
},
2005+
Instruction {
2006+
operation: Operation::SendOpenChannel,
2007+
inputs: vec![0],
2008+
},
2009+
Instruction {
2010+
operation: Operation::RecvAcceptChannel,
2011+
inputs: vec![1],
2012+
},
2013+
Instruction {
2014+
operation: Operation::LoadChainHashFromContext,
2015+
inputs: vec![],
2016+
},
2017+
Instruction {
2018+
operation: Operation::SendMessage,
2019+
inputs: vec![0],
2020+
},
2021+
Instruction {
2022+
operation: Operation::ExtractAcceptChannel(AcceptChannelField::ChannelType),
2023+
inputs: vec![2],
2024+
},
2025+
],
2026+
};
2027+
let mut rng = SmallRng::seed_from_u64(0);
2028+
let mutator = InstructionReorderMutator;
2029+
let mut mutated = false;
2030+
for _ in 0..100 {
2031+
if mutator.mutate(&mut program, &mut rng) {
2032+
mutated = true;
2033+
break;
2034+
}
2035+
}
2036+
assert!(
2037+
mutated,
2038+
"InstructionReorderMutator never mutated the program"
2039+
);
2040+
2041+
// RecvAcceptChannel and SendMessage should've been swapped.
2042+
assert_eq!(program.instructions[2].operation, Operation::SendMessage);
2043+
assert_eq!(
2044+
program.instructions[4].operation,
2045+
Operation::RecvAcceptChannel
2046+
);
2047+
// The inteleaved instruction shouldn't have been changed.
2048+
assert_eq!(
2049+
program.instructions[3].operation,
2050+
Operation::LoadChainHashFromContext
2051+
);
2052+
// Downstream instructions should be healed.
2053+
assert_eq!(program.instructions[5].inputs[0], 4);
2054+
}
2055+
2056+
#[test]
2057+
fn instr_reorder_preserves_well_formedness() {
2058+
let mut original = generate_open_channel_program(0);
2059+
// The generic OpenChannelGenerator program doesn't have swappable Act
2060+
// instructions, so add some.
2061+
let open_channel_msg = original.instructions.len() - 3;
2062+
original.instructions.extend([
2063+
Instruction {
2064+
operation: Operation::MineBlocks(6),
2065+
inputs: vec![],
2066+
},
2067+
Instruction {
2068+
operation: Operation::SendOpenChannel,
2069+
inputs: vec![open_channel_msg],
2070+
},
2071+
Instruction {
2072+
operation: Operation::MineBlocks(42),
2073+
inputs: vec![],
2074+
},
2075+
]);
2076+
assert_mutator_preserves_well_formedness(&InstructionReorderMutator, &original);
2077+
}
2078+
19252079
// -- DeadCodeEliminator tests --
19262080

19272081
#[test]

0 commit comments

Comments
 (0)