use alloc::boxed::Box;
use alloc::string::String;
use alloc::vec::Vec;
use crate::nodes::node::{Node, Numeric};
pub struct FuzzRng {
state: u64,
}
impl FuzzRng {
pub fn new(seed: u64) -> Self {
Self {
state: seed.wrapping_add(1),
}
}
pub fn next(&mut self) -> u64 {
self.state = self
.state
.wrapping_mul(6364136223846793005)
.wrapping_add(1442695040888963407);
self.state
}
pub fn next_range(&mut self, max: usize) -> usize {
if max == 0 {
return 0;
}
(self.next() as usize) % max
}
pub fn next_bool(&mut self) -> bool {
(self.next() & 1) == 1
}
pub fn next_byte(&mut self) -> u8 {
(self.next() & 0xFF) as u8
}
}
pub struct YamlFuzzer {
rng: FuzzRng,
max_depth: usize,
max_size: usize,
}
impl YamlFuzzer {
pub fn new(seed: u64) -> Self {
Self {
rng: FuzzRng::new(seed),
max_depth: 10,
max_size: 100,
}
}
pub fn with_max_depth(mut self, depth: usize) -> Self {
self.max_depth = depth;
self
}
pub fn with_max_size(mut self, size: usize) -> Self {
self.max_size = size;
self
}
pub fn generate_yaml(&mut self) -> String {
let node = self.generate_node(0);
let mut buffer = crate::io::destinations::buffer::Buffer::new();
match crate::stringify::default::stringify(&node, &mut buffer) {
Ok(_) => buffer.to_string(),
Err(_) => String::new(),
}
}
pub fn generate_node(&mut self, depth: usize) -> Node {
if depth >= self.max_depth {
return self.generate_scalar();
}
match self.rng.next_range(10) {
0..=3 => self.generate_scalar(),
4..=5 => self.generate_array(depth),
6..=7 => self.generate_mapping(depth),
8 => self.generate_tagged(depth),
_ => Node::None,
}
}
fn generate_scalar(&mut self) -> Node {
match self.rng.next_range(6) {
0 => Node::from(self.generate_string()),
1 => Node::Number(Numeric::Integer(self.rng.next() as i64 % 1000)),
2 => Node::Number(Numeric::Float((self.rng.next() % 1000) as f64 / 10.0)),
3 => Node::Boolean(self.rng.next_bool()),
4 => Node::None,
_ => Node::from(self.generate_identifier()),
}
}
fn generate_string(&mut self) -> String {
let len = self.rng.next_range(20) + 1;
let mut s = String::new();
for _ in 0..len {
let c = match self.rng.next_range(10) {
0 => ' ',
1 => '\n',
2 => '\t',
3 => ':',
4 => '-',
5 => '#',
6 => '"',
7 => '\'',
_ => (b'a' + (self.rng.next_byte() % 26)) as char,
};
s.push(c);
}
s
}
fn generate_identifier(&mut self) -> String {
let len = self.rng.next_range(10) + 1;
let mut s = String::new();
for _ in 0..len {
let c = (b'a' + (self.rng.next_byte() % 26)) as char;
s.push(c);
}
s
}
fn generate_array(&mut self, depth: usize) -> Node {
let size = self.rng.next_range(self.max_size.min(10));
let mut items = Vec::new();
for _ in 0..size {
items.push(self.generate_node(depth + 1));
}
Node::Array(items)
}
fn generate_mapping(&mut self, depth: usize) -> Node {
let size = self.rng.next_range(self.max_size.min(10));
let mut pairs = Vec::new();
for _ in 0..size {
let key = Node::from(self.generate_identifier());
let value = self.generate_node(depth + 1);
pairs.push((key, value));
}
Node::Mapping(pairs)
}
fn generate_tagged(&mut self, _depth: usize) -> Node {
let tags = ["!str", "!int", "!float", "!bool", "!null", "!custom"];
let tag = tags[self.rng.next_range(tags.len())];
let inner = self.generate_scalar();
Node::Tagged(Box::new(inner), tag.to_string())
}
pub fn generate_edge_case(&mut self) -> String {
let cases = [
"",
" ",
"\n\n\n",
"null",
"true",
"false",
"0",
"-1",
"3.14",
"[]",
"{}",
"[[[[[[[[[0]]]]]]]]]",
"a: b: c: d: e: f: g",
"- - - - - - - - -",
&"x".repeat(10000),
"!!str 123",
"&anchor",
"*alias",
"key: value\nkey: value2",
"- item\n- item",
];
cases[self.rng.next_range(cases.len())].to_string()
}
}
#[derive(Debug, Clone, PartialEq)]
pub enum FuzzResult {
Pass,
Crash(String),
Timeout,
Invalid(String),
MemoryLeak,
}
pub fn fuzz_parse(yaml: &str) -> FuzzResult {
use crate::io::sources::buffer::Buffer as BufferSource;
use crate::parser::document::parse;
let mut source = BufferSource::new(yaml.as_bytes());
match parse(&mut source) {
Ok(_) => FuzzResult::Pass,
Err(e) => {
if e.message().contains("panic") || e.message().contains("overflow") {
FuzzResult::Crash(e.to_string())
} else {
FuzzResult::Pass
}
}
}
}
pub fn fuzz_roundtrip(yaml: &str) -> FuzzResult {
use crate::test_helpers::roundtrip_node;
let node1 = match roundtrip_node(&Node::from(yaml)) {
Ok(n) => n,
Err(_) => return FuzzResult::Pass,
};
let node2 = match roundtrip_node(&node1) {
Ok(n) => n,
Err(e) => return FuzzResult::Invalid(format!("Round-trip parse failed: {}", e)),
};
if format!("{:?}", node1) == format!("{:?}", node2) {
FuzzResult::Pass
} else {
FuzzResult::Invalid("Round-trip produced different result".to_string())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_fuzz_rng() {
let mut rng = FuzzRng::new(12345);
let a = rng.next();
let b = rng.next();
assert_ne!(a, b);
}
#[test]
fn test_fuzz_rng_range() {
let mut rng = FuzzRng::new(12345);
for _ in 0..100 {
let val = rng.next_range(10);
assert!(val < 10);
}
}
#[test]
fn test_generate_scalar() {
let mut fuzzer = YamlFuzzer::new(12345);
let node = fuzzer.generate_scalar();
assert!(matches!(
node,
Node::Str(_, _, _) | Node::Number(_) | Node::Boolean(_) | Node::None
));
}
#[test]
fn test_generate_array() {
let mut fuzzer = YamlFuzzer::new(12345);
let node = fuzzer.generate_array(0);
assert!(matches!(node, Node::Array(_)));
}
#[test]
fn test_generate_mapping() {
let mut fuzzer = YamlFuzzer::new(12345);
let node = fuzzer.generate_mapping(0);
assert!(matches!(node, Node::Mapping(_)));
}
#[test]
fn test_generate_yaml() {
let mut fuzzer = YamlFuzzer::new(12345);
let yaml = fuzzer.generate_yaml();
assert!(!yaml.is_empty() || yaml.is_empty()); }
#[test]
fn test_fuzz_parse_empty() {
let result = fuzz_parse("");
assert_eq!(result, FuzzResult::Pass);
}
#[test]
fn test_fuzz_parse_simple() {
let result = fuzz_parse("key: value");
assert_eq!(result, FuzzResult::Pass);
}
#[test]
fn test_fuzz_roundtrip_simple() {
let result = fuzz_roundtrip("key: value");
assert_eq!(result, FuzzResult::Pass);
}
#[test]
fn test_edge_cases() {
let mut fuzzer = YamlFuzzer::new(12345);
for _ in 0..10 {
let yaml = fuzzer.generate_edge_case();
let result = fuzz_parse(&yaml);
assert!(!matches!(result, FuzzResult::Crash(_)));
}
}
#[test]
fn test_max_depth_limit() {
let mut fuzzer = YamlFuzzer::new(12345).with_max_depth(3);
let node = fuzzer.generate_node(0);
let _ = format!("{:?}", node);
}
}