Skip to content

Commit 6d3e794

Browse files
committed
Implementing grammer enumerator
1 parent c59e61e commit 6d3e794

3 files changed

Lines changed: 225 additions & 0 deletions

File tree

Lines changed: 212 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,212 @@
1+
//! Enumerator for context-free grammar
2+
use alloc::vec::Vec;
3+
4+
/// for more detail see the paper `https://arxiv.org/pdf/2305.00522`
5+
use crate::generators::gramatron::Automaton;
6+
use crate::inputs::Terminal;
7+
8+
/// IntegerizedStack encodes a stack of integers as a single integer.
9+
#[derive(Debug)]
10+
pub struct IntegerizedStack {
11+
value: u64,
12+
}
13+
14+
impl IntegerizedStack {
15+
/// Create a new IntegerizedStack with initial value
16+
pub fn new(v: u64) -> Self {
17+
Self { value: v }
18+
}
19+
20+
/// Removes an integer from self.value
21+
pub fn pop(&mut self) -> u64 {
22+
let (rest, ret) = decode(self.value);
23+
self.value = rest;
24+
ret
25+
}
26+
27+
/// Pop from self.value mod n
28+
pub fn modpop(&mut self, modulus: u64) -> u64 {
29+
let (rest, ret) = mod_decode(self.value, modulus);
30+
self.value = rest;
31+
ret
32+
}
33+
34+
/// Assumes value codes exactly n integers. Zero afterwards.
35+
pub fn split(&mut self, n: usize) -> Vec<u64> {
36+
let mut out = Vec::with_capacity(n);
37+
for _ in 0..(n - 1) {
38+
out.push(self.pop());
39+
}
40+
out.push(self.value);
41+
self.value = 0;
42+
out
43+
}
44+
}
45+
46+
/// Rosenberg-Strong pairing decode
47+
fn decode(z: u64) -> (u64, u64) {
48+
let m = (z as f64).sqrt().floor() as u64;
49+
let msq = m * m;
50+
if z - msq < m {
51+
(z - msq, m)
52+
} else {
53+
(m, msq + 2 * m - z)
54+
}
55+
}
56+
57+
/// Modular pairing decode
58+
/// Returns (z mod k, (z - (z mod k)) / k)
59+
fn mod_decode(z: u64, k: u64) -> (u64, u64) {
60+
let a = z % k;
61+
let b = (z - a) / k;
62+
(b, a)
63+
}
64+
65+
/// Enumerate the n-th derivation directly on a Gramatron [`Automaton`]
66+
/// - Triggers whose `dest` equals `final_state` are treated as terminal rules (base cases).
67+
/// - All other triggers are nonterminal rules (recursive cases).
68+
pub fn enumerate_automaton(state: usize, n: u64, automaton: &Automaton) -> Vec<Terminal> {
69+
let final_state = automaton.final_state;
70+
let triggers = &automaton.pda[state];
71+
72+
// Partitioning triggers into terminals and nonterminals
73+
let terminal_indices: Vec<usize> = triggers
74+
.iter()
75+
.enumerate()
76+
.filter(|(_, t)| t.dest == final_state)
77+
.map(|(i, _)| i)
78+
.collect();
79+
let nonterminal_indices: Vec<usize> = triggers
80+
.iter()
81+
.enumerate()
82+
.filter(|(_, t)| t.dest != final_state)
83+
.map(|(i, _)| i)
84+
.collect();
85+
86+
let num_terminal = terminal_indices.len() as u64;
87+
88+
if n < num_terminal {
89+
// Base case: pick the n-th terminal trigger
90+
let trigger_idx = terminal_indices[n as usize];
91+
let trigger = &triggers[trigger_idx];
92+
return vec![Terminal::new(state, trigger_idx, trigger.term.clone())];
93+
}
94+
95+
// if nonterminals then we need to choose one and recurse
96+
let mut stack = IntegerizedStack::new(n - num_terminal);
97+
let num_nonterminal = nonterminal_indices.len() as u64;
98+
let rule_choice = stack.modpop(num_nonterminal) as usize;
99+
let trigger_idx = nonterminal_indices[rule_choice];
100+
let trigger = &triggers[trigger_idx];
101+
let dest = trigger.dest;
102+
103+
let mut result = vec![Terminal::new(state, trigger_idx, trigger.term.clone())];
104+
105+
let child_terminals = enumerate_automaton(dest, stack.value, automaton);
106+
result.extend(child_terminals);
107+
result
108+
}
109+
110+
#[cfg(test)]
111+
mod tests {
112+
use alloc::string::String;
113+
114+
use super::*;
115+
use crate::generators::gramatron::Trigger;
116+
117+
/// Build a test automaton with two recursive paths:
118+
///
119+
/// ```text
120+
/// State 0 (init): "a"→3(final), "("→1, "["→2
121+
/// State 1: ")"→3(final), "x"→1
122+
/// State 2: "]"→3(final), "y"→2
123+
/// State 3 (final)
124+
/// ```
125+
///
126+
/// This generates: "a", "()", "[]", "(x)", "[y]", "(xx)", "[yy]", ...
127+
fn test_automaton() -> Automaton {
128+
Automaton {
129+
init_state: 0,
130+
final_state: 3,
131+
pda: alloc::vec![
132+
// State 0
133+
alloc::vec![
134+
Trigger {
135+
dest: 3,
136+
term: String::from("a")
137+
},
138+
Trigger {
139+
dest: 1,
140+
term: String::from("(")
141+
},
142+
Trigger {
143+
dest: 2,
144+
term: String::from("[")
145+
},
146+
],
147+
// State 1
148+
alloc::vec![
149+
Trigger {
150+
dest: 3,
151+
term: String::from(")")
152+
},
153+
Trigger {
154+
dest: 1,
155+
term: String::from("x")
156+
},
157+
],
158+
// State 2
159+
alloc::vec![
160+
Trigger {
161+
dest: 3,
162+
term: String::from("]")
163+
},
164+
Trigger {
165+
dest: 2,
166+
term: String::from("y")
167+
},
168+
],
169+
// State 3 (final)
170+
alloc::vec![],
171+
],
172+
}
173+
}
174+
175+
/// Helper: concatenate all terminal symbols into a single string.
176+
fn symbols_to_string(terms: &[Terminal]) -> String {
177+
terms.iter().map(|t| t.symbol.as_str()).collect()
178+
}
179+
180+
#[test]
181+
fn test_enumerate_automaton_known_outputs() {
182+
let automaton = test_automaton();
183+
184+
// n=0: terminal trigger at init → "a"
185+
let terms = enumerate_automaton(0, 0, &automaton);
186+
assert_eq!(symbols_to_string(&terms), "a");
187+
188+
// n=1: "(" then recurse into state 1 depth 0 → "()"
189+
let terms = enumerate_automaton(0, 1, &automaton);
190+
assert_eq!(symbols_to_string(&terms), "()");
191+
192+
// n=2: "[" then recurse into state 2 depth 0 → "[]"
193+
let terms = enumerate_automaton(0, 2, &automaton);
194+
assert_eq!(symbols_to_string(&terms), "[]");
195+
196+
// n=3: "(" then "x" then ")" → "(x)"
197+
let terms = enumerate_automaton(0, 3, &automaton);
198+
assert_eq!(symbols_to_string(&terms), "(x)");
199+
200+
// n=4: "[" then "y" then "]" → "[y]"
201+
let terms = enumerate_automaton(0, 4, &automaton);
202+
assert_eq!(symbols_to_string(&terms), "[y]");
203+
204+
// n=5: "(" then "xx" then ")" → "(xx)"
205+
let terms = enumerate_automaton(0, 5, &automaton);
206+
assert_eq!(symbols_to_string(&terms), "(xx)");
207+
208+
// n=6: "[" then "yy" then "]" → "[yy]"
209+
let terms = enumerate_automaton(0, 6, &automaton);
210+
assert_eq!(symbols_to_string(&terms), "[yy]");
211+
}
212+
}

crates/libafl/src/generators/gramatron.rs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,17 @@ where
6363
}
6464
}
6565

66+
/// Enumerate the n-th input deterministically using the IntegerizedStack algorithm.
67+
/// This produces a unique [`GramatronInput`] for each value of `n`.
68+
pub fn enumerate_nth(&self, n: u64) -> GramatronInput {
69+
let terminals = crate::generators::enumerator::enumerate_automaton(
70+
self.automaton.init_state,
71+
n,
72+
self.automaton,
73+
);
74+
GramatronInput::new(terminals)
75+
}
76+
6677
/// Append the generated terminals
6778
pub fn append_generated_terminals(&self, input: &mut GramatronInput, state: &mut S) -> usize {
6879
let mut counter = 0;

crates/libafl/src/generators/mod.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ use libafl_bolts::rands::Rand;
77

88
use crate::{Error, inputs::bytes::BytesInput, nonzero, state::HasRand};
99

10+
pub mod enumerator;
11+
1012
pub mod gramatron;
1113
use core::cmp::max;
1214

0 commit comments

Comments
 (0)