#![allow(dead_code)]
use crate::compiler::{compile_source, compile_to_proto};
use crate::vm::libs::json::decode_from_str;
use crate::vm::machine::VM;
use crate::vm::value::Value;
fn execute_source(source: &str) -> Result<Value, String> {
let stmts = compile_source(source)?;
let proto = compile_to_proto(&stmts);
let mut vm = VM::new();
vm.execute(proto)
}
pub fn fuzz_json_parser_and_nesting() {
for depth in [50, 100, 200, 255, 260, 300] {
let mut json = "1".to_string();
for _ in 0..depth {
json = format!("[{}]", json);
}
let res = decode_from_str(&json);
if depth > 256 {
assert!(
res.is_err(),
"JSON nesting depth {} must be rejected by depth limit",
depth
);
} else {
assert!(
res.is_ok(),
"JSON nesting depth {} should decode cleanly",
depth
);
}
}
let malformed_json = [
"",
"{",
"}",
"[",
"]",
"{ \"key\": }",
"{ \"key\": 123, }", "[1, 2, 3, ]", "\"unclosed string",
"\"escaped quote \\\"",
"{\"a\": {\"b\": [1, 2, {\"c\": 3}}",
"true false",
"12.34.56",
"{\"\\uZZZZ\": 1}",
"{\"\\uD800\": 1}",
"undefined",
"NaN",
"Infinity",
];
for s in &malformed_json {
let res = decode_from_str(s);
assert!(res.is_err(), "malformed JSON '{}' must be rejected", s);
}
let roundtrip_script = r#"
local json = require("@neyuki/json")
local orig = { a = 1, b = "test", c = { true, false, nil, 3.14 } }
local enc = json.encode(orig)
local dec = json.decode(enc)
assert(dec.a == 1)
assert(dec.b == "test")
assert(dec.c[1] == true)
assert(dec.c[2] == false)
"#;
assert!(execute_source(roundtrip_script).is_ok());
}
pub fn fuzz_string_and_utf8_edge_cases() {
let sub_script = r#"
local string = require("@neyuki/string")
local s = "Hello, Neyuki!"
assert(string.sub(s, 1, 5) == "Hello")
assert(string.sub(s, -7, -2) == "Neyuki")
assert(string.sub(s, 10, 5) == "") -- inverted indices
assert(string.sub(s, 100, 200) == "") -- out of range
assert(string.sub(s, -500, -100) == "") -- negative out of range
"#;
let res = execute_source(sub_script);
assert!(res.is_ok(), "failed: {:?}", res.err());
let utf8_script = r#"
local utf8 = require("@neyuki/utf8")
local string = require("@neyuki/string")
local vi = "Xin chào thế giới 🚀"
assert(utf8.len(vi) == 19)
local c1 = utf8.char(65) -- 'A'
assert(c1 == "A")
local c2 = utf8.char(128512) -- 0x1F680: 🚀
assert(utf8.len(c2) == 1)
"#;
let res = execute_source(utf8_script);
assert!(res.is_ok(), "utf8_script error: {:?}", res.err());
let bad_utf8_script = r#"
local utf8 = require("@neyuki/utf8")
assert(pcall(utf8.char, 55296) == false) -- 0xD800: surrogate
assert(pcall(utf8.char, 57343) == false) -- 0xDFFF: surrogate
assert(pcall(utf8.char, 1114112) == false) -- 0x110000: out of range
assert(pcall(utf8.char, -1) == false) -- negative
"#;
assert!(execute_source(bad_utf8_script).is_ok());
let rep_script = r#"
local string = require("@neyuki/string")
assert(string.rep("a", 0) == "")
assert(string.rep("a", -5) == "")
-- Huge repetition exceeding limits must fail with error, not OOM panic
local ok, _ = pcall(string.rep, "hello", 100000000)
assert(ok == false)
"#;
assert!(execute_source(rep_script).is_ok());
let format_script = r#"
local string = require("@neyuki/string")
assert(string.format("num: %d, str: %s, hex: %x", 42, "abc", 255) == "num: 42, str: abc, hex: ff")
assert(string.format("percent: %%") == "percent: %")
assert(string.format("hex: %X", 255) == "hex: FF")
"#;
let res = execute_source(format_script);
assert!(res.is_ok(), "format error: {:?}", res.err());
}
pub fn fuzz_number_parsing_and_radix() {
let tonumber_script = r#"
assert(tonumber("1010", 2) == 10)
assert(tonumber("ff", 16) == 255)
assert(tonumber("0xFF", 16) == 255)
assert(tonumber("z", 36) == 35)
assert(tonumber("-101", 2) == -5)
assert(tonumber(" +42 ") == 42)
assert(tonumber(" -42 ") == -42)
assert(tonumber("1e3") == 1000.0)
-- Invalid bases must be rejected
assert(pcall(tonumber, "10", 1) == false)
assert(pcall(tonumber, "10", 37) == false)
assert(pcall(tonumber, "10", -5) == false)
-- Invalid digits for base must return nil
assert(tonumber("12", 2) == nil)
assert(tonumber("xyz", 10) == nil)
"#;
assert!(execute_source(tonumber_script).is_ok());
let huge_str = "1".repeat(70_000);
let cap_script = format!("assert(tonumber(\"{}\") == nil)", huge_str);
assert!(execute_source(&cap_script).is_ok());
}
pub fn fuzz_crypto_boundaries() {
for len in [0, 1, 55, 56, 63, 64, 65, 119, 120, 127, 128, 512, 1024] {
let script = format!(
"local crypto = require(\"@neyuki/crypto\")
return crypto.hash(string.rep(\"a\", {}))",
len
);
let hash = execute_source(&script).expect("hash should succeed");
assert_eq!(
hash.to_string().len(),
64,
"SHA-256 hash must be 64 hex characters"
);
}
let crypto_script = r#"
local crypto = require("@neyuki/crypto")
assert(crypto.hash("") == "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855")
assert(crypto.hash("abc", "sha256") == "ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad")
-- Unknown algorithms and encodings must error, not panic
assert(pcall(crypto.hash, "abc", "rot13") == false)
assert(pcall(crypto.hash, "abc", "sha256", "base32") == false)
-- Malformed ciphertexts, keys and signatures must error, not panic
local key = crypto.generateKey()
for _, bad in {"", "!", "AAAA", crypto.randomBytes(5), crypto.randomBytes(64)} do
assert(pcall(crypto.decrypt, bad, key) == false)
assert(pcall(crypto.encrypt, "x", bad) == false)
end
local pair = crypto.generateKeyPair()
for _, bad in {"", "!", "AAAA", crypto.randomBytes(64), crypto.randomBytes(100)} do
assert(crypto.verify("x", bad, pair.publicKey) == false)
end
for _, bad in {"", "-----BEGIN PUBLIC KEY-----
AAAA
-----END PUBLIC KEY-----
", pair.publicKey .. "x"} do
assert(pcall(crypto.sign, "x", bad) == false)
assert(pcall(crypto.verify, "x", "AAAA", bad) == false)
end
assert(pcall(crypto.verifyPassword, "x", "$argon2id$garbage") == false)
assert(pcall(crypto.verifyPassword, "x", "$2b$garbage") == false)
assert(pcall(crypto.verifyPassword, "x", "plain") == false)
"#;
assert!(execute_source(crypto_script).is_ok());
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_libs_fuzz_all() {
fuzz_json_parser_and_nesting();
fuzz_string_and_utf8_edge_cases();
fuzz_number_parsing_and_radix();
fuzz_crypto_boundaries();
}
}