babbel_yaml 0.1.1

Fast, modular YAML 1.2 parser and emitter with anchors, aliases, and tags
//! YAML Fuzzing Infrastructure
//!
//! This module provides infrastructure for fuzz testing YAML parsing and serialization.
//! It includes tools for generating random YAML inputs and detecting crashes, hangs, and incorrect behavior.
//!
//! # Features
//! - Random YAML input generation
//! - Crash and hang detection
//! - Utilities for fuzz-based robustness testing
//!
//! # Usage
//! Use the provided fuzzing utilities to test the resilience of YAML parsing and serialization code.

use alloc::boxed::Box;
use alloc::string::String;
use alloc::vec::Vec;

use crate::nodes::node::{Node, Numeric};

/// Random number generator for fuzzing (simple LCG)
pub struct FuzzRng {
    state: u64,
}

impl FuzzRng {
    pub fn new(seed: u64) -> Self {
        Self {
            state: seed.wrapping_add(1),
        }
    }

    /// Generate next random number
    pub fn next(&mut self) -> u64 {
        // Linear congruential generator
        self.state = self
            .state
            .wrapping_mul(6364136223846793005)
            .wrapping_add(1442695040888963407);
        self.state
    }

    /// Generate random number in range [0, max)
    pub fn next_range(&mut self, max: usize) -> usize {
        if max == 0 {
            return 0;
        }
        (self.next() as usize) % max
    }

    /// Generate random boolean
    pub fn next_bool(&mut self) -> bool {
        (self.next() & 1) == 1
    }

    /// Generate random byte
    pub fn next_byte(&mut self) -> u8 {
        (self.next() & 0xFF) as u8
    }
}

/// YAML fuzzer for generating random inputs
pub struct YamlFuzzer {
    rng: FuzzRng,
    max_depth: usize,
    max_size: usize,
}

impl YamlFuzzer {
    /// Create new fuzzer with seed
    pub fn new(seed: u64) -> Self {
        Self {
            rng: FuzzRng::new(seed),
            max_depth: 10,
            max_size: 100,
        }
    }

    /// Set maximum nesting depth
    pub fn with_max_depth(mut self, depth: usize) -> Self {
        self.max_depth = depth;
        self
    }

    /// Set maximum collection size
    pub fn with_max_size(mut self, size: usize) -> Self {
        self.max_size = size;
        self
    }

    /// Generate random YAML string
    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(),
        }
    }

    /// Generate random Node
    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,
        }
    }

    /// Generate random scalar
    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()),
        }
    }

    /// Generate random string
    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
    }

    /// Generate random identifier (safe string)
    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
    }

    /// Generate random array
    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)
    }

    /// Generate random mapping
    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)
    }

    /// Generate random tagged node
    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())
    }

    /// Generate edge case YAML
    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()
    }
}

/// Fuzz test result
#[derive(Debug, Clone, PartialEq)]
pub enum FuzzResult {
    /// Test passed
    Pass,
    /// Parser crashed
    Crash(String),
    /// Parser hung (timeout)
    Timeout,
    /// Invalid output
    Invalid(String),
    /// Memory leak detected
    MemoryLeak,
}

/// Run fuzz test
pub fn fuzz_parse(yaml: &str) -> FuzzResult {
    use crate::io::sources::buffer::Buffer as BufferSource;
    use crate::parser::document::parse;

    // Try to parse
    let mut source = BufferSource::new(yaml.as_bytes());
    match parse(&mut source) {
        Ok(_) => FuzzResult::Pass,
        Err(e) => {
            // Parser returned error - this is fine
            if e.message().contains("panic") || e.message().contains("overflow") {
                FuzzResult::Crash(e.to_string())
            } else {
                FuzzResult::Pass
            }
        }
    }
}

/// Run round-trip fuzz test (parse -> stringify -> parse)
pub fn fuzz_roundtrip(yaml: &str) -> FuzzResult {
    use crate::test_helpers::roundtrip_node;

    // First round-trip: if parse or stringify fails, treat as a non-crashing
    // input (Pass), since fuzzing is only interested in crashes/invalid
    // structural behavior here.
    let node1 = match roundtrip_node(&Node::from(yaml)) {
        Ok(n) => n,
        Err(_) => return FuzzResult::Pass,
    };

    // Second round-trip: failures here are treated as invalid behavior.
    let node2 = match roundtrip_node(&node1) {
        Ok(n) => n,
        Err(e) => return FuzzResult::Invalid(format!("Round-trip parse failed: {}", e)),
    };

    // Compare (basic comparison - not perfect)
    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();
        // Just verify it doesn't crash
        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();
        // Just verify it produces something
        assert!(!yaml.is_empty() || yaml.is_empty()); // Always true, just checking no crash
    }

    #[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);
            // Should not crash
            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);
        // Node should be generated without panic
        let _ = format!("{:?}", node);
    }
}