fwd/fuzz/fuzz_targets/json_only_valid_serde.rs
John Doty 35dcf93971 Add fuzzing based on serde_json
This test ensures that we can parse anything that serde_json can
produce, which *ought* to ensure reasonable coverage?
2024-08-12 17:18:26 -07:00

77 lines
2.5 KiB
Rust

#![no_main]
use arbitrary::{Arbitrary, Error, Unstructured};
use libfuzzer_sys::fuzz_target;
use std::collections::HashMap;
extern crate fwd;
use fwd::server::refresh::docker::JsonValue;
/// InputNumber is a JSON number, i.e., a finite 64-bit floating point value
/// that is not NaN. We need to define our own little wrapper here so that we
/// can convince Arbitrary to only make finite f64s.
///
/// Ideally we would actually wrap serde_json::Number but there are rules
/// about mixing 3rd party traits with 3rd party types.
#[derive(Debug, PartialEq)]
struct InputNumber(f64);
impl<'a> Arbitrary<'a> for InputNumber {
fn arbitrary(u: &mut Unstructured<'a>) -> Result<Self, Error> {
let value = f64::arbitrary(u)?;
if value.is_finite() {
Ok(InputNumber(value))
} else {
Err(Error::IncorrectFormat) // REJECT
}
}
#[inline]
fn size_hint(depth: usize) -> (usize, Option<usize>) {
f64::size_hint(depth)
}
}
/// TestInput is basically serde_json::Value, except (a) it has a HashMap and
/// not serde_json's special `Map` structure, and (b) it has `InputNumber`
/// instead of `json_serde::Number` for reasons described above.
#[derive(Debug, PartialEq, Arbitrary)]
enum TestInput {
Null,
Bool(bool),
Number(InputNumber),
String(String),
Object(HashMap<String, TestInput>),
Array(Vec<TestInput>),
}
fn convert(value: &TestInput) -> serde_json::Value {
match value {
TestInput::Null => serde_json::Value::Null,
TestInput::Bool(b) => serde_json::Value::Bool(*b),
TestInput::Number(n) => serde_json::Value::Number(
serde_json::Number::from_f64(n.0).expect("Unable to make an f64"),
),
TestInput::String(s) => serde_json::Value::String(s.clone()),
TestInput::Object(o) => {
let mut out = serde_json::map::Map::new();
for (k, v) in o.into_iter() {
out.insert(k.clone(), convert(v));
}
serde_json::Value::Object(out)
}
TestInput::Array(v) => {
serde_json::Value::Array(v.into_iter().map(convert).collect())
}
}
}
fuzz_target!(|data: TestInput| {
// Convert the arbitrary TestInput into an arbitrary serde_json::Value,
// then use serde_json to write out arbitrary JSON.
let converted = convert(&data).to_string();
// Parse the JSON that serde_json produced. This fuzz test should ensure
// that we can parse anything that serde_json can produce.
let _ = JsonValue::parse(converted.as_bytes());
});