Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion fuzz/.gitignore
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
artifacts/
corpus/
corpus/*
!corpus/fuzz_f32_literal/
!corpus/fuzz_f32_literal/*.txt
!corpus/fuzz_value/
!corpus/fuzz_value/*.json
coverage/
target/
6 changes: 6 additions & 0 deletions fuzz/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -56,3 +56,9 @@ doc = false
name = "fuzz_serde_roundtrip"
path = "fuzz_targets/serde_roundtrip.rs"
test = false

[[bin]]
doc = false
name = "fuzz_f32_literal"
path = "fuzz_targets/f32_literal.rs"
test = false
1 change: 1 addition & 0 deletions fuzz/corpus/fuzz_f32_literal/100e11.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
100e11
1 change: 1 addition & 0 deletions fuzz/corpus/fuzz_f32_literal/17005001_000000000000130.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
17005001.000000000000130
1 change: 1 addition & 0 deletions fuzz/corpus/fuzz_f32_literal/f32_max_finite.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
3.4028235e38
1 change: 1 addition & 0 deletions fuzz/corpus/fuzz_f32_literal/f32_overflow.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
3.4028236e38
1 change: 1 addition & 0 deletions fuzz/corpus/fuzz_f32_literal/f32_overflow_exp.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
1e39
1 change: 1 addition & 0 deletions fuzz/corpus/fuzz_value/deep_array.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
[[[[[[[[[[[[[[[[null]]]]]]]]]]]]]]]]
1 change: 1 addition & 0 deletions fuzz/corpus/fuzz_value/deep_object.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"k":{"k":{"k":{"k":{"k":{"k":{"k":{"k":null}}}}}}}}
1 change: 1 addition & 0 deletions fuzz/corpus/fuzz_value/duplicate_keys.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"k":1,"k":2,"nested":{"k":3,"arr":[{"k":4},5,{"inner":[1,{"k":"leaf"}]}]}}
1 change: 1 addition & 0 deletions fuzz/corpus/fuzz_value/mixed_numbers.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
[0,-0,18446744073709551615,9007199254740993,0.00000000000000001,100e11,17005001.000000000000130,3.4028235e38,3.4028236e38]
1 change: 1 addition & 0 deletions fuzz/corpus/fuzz_value/nested_paths.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"alpha":1,"beta":{"gamma":{"delta":"leaf","epsilon":[0,{"z":true}]}},"root_arr":[{"name":"node"},[false,null,{"tail":"value"}]]}
1 change: 1 addition & 0 deletions fuzz/corpus/fuzz_value/string_escapes.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"text":"line\n\tslash\/quote\"backslash\\","unicode":"\u4E2D\u6587","emoji":"\uD83D\uDE00","controls":"\u0000\u001F"}
94 changes: 1 addition & 93 deletions fuzz/fuzz_targets/deep_nesting.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,96 +5,4 @@
use libfuzzer_sys::fuzz_target;
use sonic_rs_fuzz::gen::DeepNestInput;

fuzz_target!(|input: DeepNestInput| {
let json = input.to_json();
let json_bytes = json.as_bytes();

// --- Strategy 1: Parse as Value (exercises NodeBuf allocation) ---
let sv_result = sonic_rs::from_str::<sonic_rs::Value>(&json);
let jv_result = serde_json::from_str::<serde_json::Value>(&json);

match (&jv_result, &sv_result) {
(Ok(jv), Ok(sv)) => {
sonic_rs_fuzz::compare_value(jv, sv);

// Round-trip
let out = sonic_rs::to_string(sv).unwrap();
let sv2: sonic_rs::Value = sonic_rs::from_str(&out).unwrap();
let jv2: serde_json::Value = serde_json::from_str(&out).unwrap();
sonic_rs_fuzz::compare_value(&jv2, &sv2);
}
(Err(_), Err(_)) => {} // Both reject — OK (e.g., too deep)
(Ok(_), Err(e)) => {
panic!(
"sonic-rs rejected valid deep JSON: {}\njson len: {}",
e,
json.len()
);
}
(Err(_), Ok(_)) => {
// sonic-rs accepted something serde_json rejected — may be depth limit difference
}
}

// --- Strategy 2: Parse as OwnedLazyValue (different allocation path) ---
if jv_result.is_ok() {
if let Ok(olv) = sonic_rs::from_str::<sonic_rs::OwnedLazyValue>(&json) {
// Walk the structure to exercise recursive access
walk_owned_lazy(&olv, 0);
}
}

// --- Strategy 3: Lazy iteration on deep structures ---
if let Ok(jv) = &jv_result {
if jv.is_array() {
for ret in sonic_rs::to_array_iter(json_bytes) {
let lv = ret.unwrap();
// Re-parse each element
let _ = sonic_rs::from_str::<sonic_rs::Value>(lv.as_raw_str()).unwrap();
}
} else if jv.is_object() {
for ret in sonic_rs::to_object_iter(json_bytes) {
let (_, lv) = ret.unwrap();
let _ = sonic_rs::from_str::<sonic_rs::Value>(lv.as_raw_str()).unwrap();
}
}
}

// --- Strategy 4: from_slice (exercises PaddedSliceRead path) ---
if jv_result.is_ok() {
let sv: sonic_rs::Value = sonic_rs::from_slice(json_bytes).unwrap();
let out = sonic_rs::to_string(&sv).unwrap();
let _ = sonic_rs::from_str::<sonic_rs::Value>(&out).unwrap();
}
});

/// Recursively walk an OwnedLazyValue to exercise deep access patterns.
fn walk_owned_lazy(v: &sonic_rs::OwnedLazyValue, depth: usize) {
use sonic_rs::JsonValueTrait;

// Limit recursion to avoid stack overflow in the fuzzer itself
if depth > 600 {
return;
}

if v.is_object() {
// Try index-based access with known key
if let Some(child) = v.get("k") {
walk_owned_lazy(child, depth + 1);
}
} else if v.is_array() {
// Walk first few elements by index
for i in 0..16 {
match v.get(i) {
Some(child) => walk_owned_lazy(child, depth + 1),
None => break,
}
}
} else if v.is_str() {
let _ = v.as_str();
} else if v.is_number() {
let _ = v.as_f64();
let _ = v.as_i64();
let _ = v.as_u64();
}
}
fuzz_target!(|input: DeepNestInput| sonic_rs_fuzz::fuzz_deep_nesting_input(&input));
8 changes: 8 additions & 0 deletions fuzz/fuzz_targets/f32_literal.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
//! Fuzz target for direct f32 literal parsing against std's parser semantics.
#![no_main]

use libfuzzer_sys::fuzz_target;

fuzz_target!(|data: &[u8]| {
sonic_rs_fuzz::fuzz_f32_literal_bytes(data);
});
86 changes: 1 addition & 85 deletions fuzz/fuzz_targets/number_parse.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,90 +3,6 @@
#![no_main]

use libfuzzer_sys::fuzz_target;
use sonic_rs::{JsonNumberTrait, JsonValueTrait};
use sonic_rs_fuzz::gen::NumberInput;

fuzz_target!(|input: NumberInput| {
let json = input.to_json_bytes();

// --- Strategy 1: Compare sonic-rs vs serde_json for type-level consistency ---
macro_rules! cmp_number {
($ty:ty) => {
match serde_json::from_slice::<$ty>(&json) {
Ok(expected) => {
let got: $ty = sonic_rs::from_slice(&json).unwrap_or_else(|e| {
panic!(
"sonic-rs failed to parse {:?} as {}: {}",
std::str::from_utf8(&json).unwrap_or("<non-utf8>"),
stringify!($ty),
e
)
});
assert_eq!(
got, expected,
"mismatch for {} on {:?}",
stringify!($ty),
std::str::from_utf8(&json).unwrap_or("<non-utf8>")
);
}
Err(_) => {
// serde_json rejects it — sonic-rs should too
let _ = sonic_rs::from_slice::<$ty>(&json);
}
}
};
}

cmp_number!(u8);
cmp_number!(u16);
cmp_number!(u32);
cmp_number!(u64);
cmp_number!(u128);
cmp_number!(i8);
cmp_number!(i16);
cmp_number!(i32);
cmp_number!(i64);
cmp_number!(i128);
cmp_number!(f32);
cmp_number!(f64);

// --- Strategy 2: Value-level number parsing ---
if let Ok(sv) = sonic_rs::from_slice::<sonic_rs::Value>(&json) {
if let Ok(jv) = serde_json::from_slice::<serde_json::Value>(&json) {
if let (Some(sn), Some(jn)) = (sv.as_number(), jv.as_number()) {
// Compare all numeric representations
if jn.is_u64() {
assert_eq!(sn.as_u64(), jn.as_u64(), "u64 mismatch on {:?}", json);
}
if jn.is_i64() {
assert_eq!(sn.as_i64(), jn.as_i64(), "i64 mismatch on {:?}", json);
}
if jn.is_f64() {
assert_eq!(sn.as_f64(), jn.as_f64(), "f64 mismatch on {:?}", json);
}
}
}
}

// --- Strategy 3: Round-trip consistency ---
if let Ok(sv) = sonic_rs::from_slice::<sonic_rs::Value>(&json) {
let serialized = sonic_rs::to_string(&sv).unwrap();
let sv2: sonic_rs::Value = sonic_rs::from_str(&serialized).unwrap();
// Numbers should round-trip
if let Some(n1) = sv.as_number() {
let n2 = sv2.as_number().expect("round-trip lost number type");
assert_eq!(n1.as_f64(), n2.as_f64(), "f64 round-trip mismatch");
assert_eq!(n1.as_u64(), n2.as_u64(), "u64 round-trip mismatch");
assert_eq!(n1.as_i64(), n2.as_i64(), "i64 round-trip mismatch");
}
}

// --- Strategy 4: Raw bytes fuzzing (non-structured) ---
// Also test with the raw json bytes directly for the unchecked path
if let Ok(s) = std::str::from_utf8(&json) {
let _ = sonic_rs::from_str::<f64>(s);
let _ = sonic_rs::from_str::<i64>(s);
let _ = sonic_rs::from_str::<u64>(s);
let _ = sonic_rs::from_str::<sonic_rs::Value>(s);
}
});
fuzz_target!(|input: NumberInput| sonic_rs_fuzz::fuzz_number_input(&input));
37 changes: 3 additions & 34 deletions fuzz/fuzz_targets/serde_roundtrip.rs
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,7 @@ fuzz_target!(|input: FuzzInput| {
FuzzInput::Enum(v) => roundtrip_test(&v),
FuzzInput::VecComplex(v) => roundtrip_test(&v),
FuzzInput::MapComplex(v) => roundtrip_test(&v),
FuzzInput::Raw(data) => raw_roundtrip_test(&data),
FuzzInput::Raw(data) => sonic_rs_fuzz::fuzz_serde_roundtrip_raw(&data),
}
});

Expand Down Expand Up @@ -162,7 +162,8 @@ where

// Self-roundtrip should be identity
assert_eq!(
&sonic_parsed, value,
&sonic_parsed,
value,
"sonic-rs roundtrip changed value\njson: {}",
&sonic_json[..sonic_json.len().min(500)]
);
Expand Down Expand Up @@ -192,35 +193,3 @@ where
let jv: serde_json::Value = serde_json::from_str(&sonic_json).unwrap();
sonic_rs_fuzz::compare_value(&jv, &sv);
}

/// Test raw bytes that might or might not be valid JSON.
fn raw_roundtrip_test(data: &[u8]) {
// Try parsing as various types
macro_rules! try_type {
($ty:ty) => {
match serde_json::from_slice::<$ty>(data) {
Ok(expected) => {
if let Ok(got) = sonic_rs::from_slice::<$ty>(data) {
assert_eq!(
got, expected,
"type {} mismatch on raw input",
stringify!($ty)
);
}
}
Err(_) => {
let _ = sonic_rs::from_slice::<$ty>(data);
}
}
};
}

try_type!(SimpleStruct);
try_type!(TestEnum);
try_type!(Vec<i64>);
try_type!(HashMap<String, String>);
try_type!(Option<String>);
try_type!(String);
try_type!(f64);
try_type!(bool);
}
57 changes: 1 addition & 56 deletions fuzz/fuzz_targets/string_escape.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,61 +3,6 @@
#![no_main]

use libfuzzer_sys::fuzz_target;
use sonic_rs::JsonValueTrait;
use sonic_rs_fuzz::gen::JsonValue;

fuzz_target!(|input: JsonValue| {
let json = input.to_json();
let json_bytes = json.as_bytes();

// --- Strategy 1: Parse and compare with serde_json ---
match serde_json::from_str::<serde_json::Value>(&json) {
Ok(jv) => {
let sv: sonic_rs::Value = sonic_rs::from_str(&json).unwrap_or_else(|e| {
panic!("sonic-rs failed on valid JSON: {}\njson: {}", e, &json[..json.len().min(200)])
});

// Use the existing compare_value from fuzz lib (handles duplicate keys)
sonic_rs_fuzz::compare_value(&jv, &sv);

// Round-trip: serialize and re-parse
let out = sonic_rs::to_string(&sv).unwrap();
let sv2: sonic_rs::Value = sonic_rs::from_str(&out).unwrap();
let jv2: serde_json::Value = serde_json::from_str(&out).unwrap();
sonic_rs_fuzz::compare_value(&jv2, &sv2);
}
Err(_) => {
// serde_json rejects — sonic-rs should also reject
let _ = sonic_rs::from_str::<sonic_rs::Value>(&json);
}
}

// --- Strategy 2: String type deserialization ---
if let Ok(expected) = serde_json::from_str::<String>(&json) {
let got: String = sonic_rs::from_str(&json).unwrap_or_else(|e| {
panic!("sonic-rs String deser failed: {}\njson: {}", e, &json[..json.len().min(200)])
});
assert_eq!(got, expected, "String mismatch on: {}", &json[..json.len().min(200)]);
}

// --- Strategy 3: UTF-8 lossy mode ---
if let Ok(_) = sonic_rs::from_slice::<sonic_rs::Value>(json_bytes) {
let json_str = unsafe { std::str::from_utf8_unchecked(json_bytes) };
let mut de = sonic_rs::Deserializer::from_str(json_str).utf8_lossy();
let _: Result<sonic_rs::Value, _> = serde::Deserialize::deserialize(&mut de);
}

// --- Strategy 4: LazyValue string access ---
if let Ok(jv) = serde_json::from_str::<serde_json::Value>(&json) {
if jv.is_string() {
if let Ok(lv) = sonic_rs::from_str::<sonic_rs::LazyValue>(&json) {
let raw = lv.as_raw_str();
// Re-parse the raw string
let sv: sonic_rs::Value = sonic_rs::from_str(raw).unwrap();
assert!(sv.is_str());
assert_eq!(sv.as_str().unwrap(), jv.as_str().unwrap());
}
}
}
});

fuzz_target!(|input: JsonValue| sonic_rs_fuzz::fuzz_string_value(&input));
Loading
Loading