wave 7: fuzz sidecar scaffold (fuzz/ workspace, shared crate, runner, dict, seeds skeleton)
This commit is contained in:
1 parent
67d1d7e34f
commit
62ff18d1ed
14 files changed
+614
-15
No files matched your search
@@ -0,0 +1,13 @@
|
||||
[package]
|
||||
name = "alkstore-fuzz-shared"
|
||||
version = "0.0.0"
|
||||
publish = false
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
alkstore = { path = "../../alkstore" }
|
||||
serde_json = "1"
|
||||
|
||||
[dependencies.arbitrary]
|
||||
version = "1"
|
||||
features = ["derive"]
|
||||
@@ -0,0 +1,108 @@
|
||||
//! `Arbitrary` support for `serde_json::Value` — a bounded-depth
|
||||
//! recursive generator shared by the semantic targets. Depth and size
|
||||
//! bounds keep exec/s high (serde_json's own 128-depth recursion limit
|
||||
//! is the decode-side guard; this generator explores structure).
|
||||
|
||||
use arbitrary::{Arbitrary, Unstructured};
|
||||
use serde_json::{Map, Number, Value};
|
||||
|
||||
#[derive(Debug, arbitrary::Arbitrary, Clone)]
|
||||
#[allow(dead_code)]
|
||||
enum JsonShape {
|
||||
Null,
|
||||
Bool(bool),
|
||||
Number {
|
||||
mantissa: u32,
|
||||
negative: bool,
|
||||
},
|
||||
String {
|
||||
len: u8,
|
||||
byte: u8,
|
||||
},
|
||||
Array {
|
||||
len: u8,
|
||||
items: Vec<JsonShape>,
|
||||
},
|
||||
Object {
|
||||
len: u8,
|
||||
keys: Vec<(u8, u8)>,
|
||||
values: Vec<JsonShape>,
|
||||
},
|
||||
}
|
||||
|
||||
/// Generate a `Value` with bounded depth: arrays/objects nest at most
|
||||
/// 6 levels, each holding at most 6 items. The `u8`-bounded shapes
|
||||
/// keep the generator cheap; the semantic target's value space is
|
||||
/// structure, not size.
|
||||
impl<'a> Arbitrary<'a> for JsonValue {
|
||||
fn arbitrary(u: &mut Unstructured<'a>) -> arbitrary::Result<Self> {
|
||||
let shape = JsonShape::arbitrary(u)?;
|
||||
Ok(JsonValue(shape_to_value(shape, 6)))
|
||||
}
|
||||
}
|
||||
|
||||
fn shape_to_value(shape: JsonShape, depth: u32) -> Value {
|
||||
if depth == 0 {
|
||||
return Value::Null;
|
||||
}
|
||||
match shape {
|
||||
JsonShape::Null => Value::Null,
|
||||
JsonShape::Bool(b) => Value::Bool(b),
|
||||
JsonShape::Number { mantissa, negative } => {
|
||||
let n = i64::from(mantissa % 1_000_000);
|
||||
Number::from(if negative { -n } else { n }).into()
|
||||
}
|
||||
JsonShape::String { len, byte } => {
|
||||
// Repeat a printable-ish char; embedded control bytes and
|
||||
// invalid UTF-8 come from the fuzzer's byte mutations of
|
||||
// the raw input in the payload targets, not here.
|
||||
let n = (len % 64) as usize;
|
||||
std::iter::repeat_n((0x41u8 + (byte % 26)) as char, n)
|
||||
.collect::<String>()
|
||||
.into()
|
||||
}
|
||||
JsonShape::Array { len, items } => {
|
||||
let items: Vec<Value> = items
|
||||
.into_iter()
|
||||
.take(6)
|
||||
.map(|s| shape_to_value(s, depth - 1))
|
||||
.collect();
|
||||
let _ = len;
|
||||
Value::Array(items)
|
||||
}
|
||||
JsonShape::Object { keys, values, .. } => {
|
||||
let mut map = Map::new();
|
||||
for (i, (k_len, k_byte)) in keys.into_iter().take(6).enumerate() {
|
||||
let key: String = std::iter::repeat_n(
|
||||
(0x61u8 + (k_byte % 26)) as char,
|
||||
(k_len % 16) as usize + 1,
|
||||
)
|
||||
.collect();
|
||||
let value = values
|
||||
.get(i)
|
||||
.map(|s| shape_to_value(s.clone(), depth - 1))
|
||||
.unwrap_or(Value::Null);
|
||||
map.insert(key, value);
|
||||
}
|
||||
Value::Object(map)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Newtype so `impl Arbitrary` applies (serde_json re-exports `Value`
|
||||
/// as `serde_json::Value`; a foreign-type impl is not allowed).
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct JsonValue(pub Value);
|
||||
|
||||
impl std::ops::Deref for JsonValue {
|
||||
type Target = Value;
|
||||
fn deref(&self) -> &Value {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl From<JsonValue> for Value {
|
||||
fn from(value: JsonValue) -> Value {
|
||||
value.0
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
//! Invariant logic shared by alkstore's fuzz targets. Kept out of the
|
||||
//! fuzz-target binaries so the corpus replay unit tests can exercise the
|
||||
//! same invariant checks against every committed corpus entry (the quinn
|
||||
//! CI pattern) without a nightly toolchain.
|
||||
|
||||
pub mod arbitrary_value;
|
||||
Reference in new issue
Block a user