Skip to content

Commit

Permalink
feat(fuzz): arbitrary implementation for Cell and CellBuilder
Browse files Browse the repository at this point in the history
  • Loading branch information
0xdeafbeef committed Aug 5, 2024
1 parent 0013459 commit c51f801
Show file tree
Hide file tree
Showing 8 changed files with 128 additions and 5 deletions.
2 changes: 2 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ members = ["proc"]
[dependencies]
ahash = "0.8"
anyhow = { version = "1.0", optional = true }
arbitrary = { version = "1", optional = true }
base64 = { version = "0.22", optional = true }
bitflags = "2.3"
blake3 = { version = "1.5", optional = true }
Expand Down Expand Up @@ -62,6 +63,7 @@ serde = { version = "1", features = ["derive"] }
serde_json = "1"

[features]
arbitrary = ["dep:arbitrary"]
default = ["base64", "serde", "models", "sync"]
sync = ["dep:scc"]
stats = []
Expand Down
1 change: 1 addition & 0 deletions fuzz/.gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
target
corpus
artifacts
coverage
14 changes: 14 additions & 0 deletions fuzz/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,12 @@ edition = "2021"
cargo-fuzz = true

[dependencies]
arbitrary = { version = "1.0.1", features = ["derive"] }
libfuzzer-sys = "0.4"

[dependencies.everscale-types]
path = ".."
features = ["arbitrary"]

# Prevent this from interfering with workspaces
[workspace]
Expand Down Expand Up @@ -42,8 +44,20 @@ path = "fuzz_targets/boc_dict.rs"
test = false
doc = false

[[bin]]
name = "boc_dict_arb"
path = "fuzz_targets/boc_dict_arb.rs"
test = false
doc = false

[[bin]]
name = "boc_message"
path = "fuzz_targets/boc_message.rs"
test = false
doc = false

[[bin]]
name = "boc_message_arb"
path = "fuzz_targets/boc_message_arb.rs"
test = false
doc = false
26 changes: 22 additions & 4 deletions fuzz/fuzz_targets/boc_decode_encode.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,28 @@
#![no_main]
use libfuzzer_sys::fuzz_target;
use libfuzzer_sys::{fuzz_target, Corpus};

use everscale_types::prelude::Boc;
use everscale_types::cell::CellTreeStats;
use everscale_types::prelude::*;

fuzz_target!(|data: &[u8]| {
fuzz_target!(|data: &[u8]| -> Corpus {
if let Ok(cell) = Boc::decode(data) {
_ = Boc::encode(cell.as_ref());
let res = Boc::encode(cell.as_ref());
let redecoded = Boc::decode(&res).unwrap();
assert_eq!(cell.as_ref(), redecoded.as_ref());
let l = call_all_cell_methods(&cell);
let r = call_all_cell_methods(&redecoded);
assert_eq!(l, r);
return Corpus::Keep;
}
Corpus::Reject
});

fn call_all_cell_methods(cell: &Cell) -> CellTreeStats {
let hash = cell.hash(0);
let hash = cell.hash(1);
let hash = cell.hash(2);
let hash = cell.hash(3);

let _ = cell.virtualize();
cell.compute_unique_stats(usize::MAX).unwrap()
}
12 changes: 12 additions & 0 deletions fuzz/fuzz_targets/boc_dict_arb.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
#![no_main]
use libfuzzer_sys::{fuzz_target, Corpus};

use everscale_types::prelude::{Cell, RawDict};

fuzz_target!(|data: Cell| -> Corpus {
if let Ok(map) = data.parse::<RawDict<32>>() {
_ = map.iter().count();
return Corpus::Keep;
}
Corpus::Reject
});
13 changes: 13 additions & 0 deletions fuzz/fuzz_targets/boc_message_arb.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
#![no_main]
use libfuzzer_sys::{fuzz_target, Corpus};

use everscale_types::models::Message;
use everscale_types::prelude::Cell;

fuzz_target!(|cell: Cell| -> Corpus {
if cell.parse::<Message>().is_ok() {
return Corpus::Keep;
}

Corpus::Reject
});
55 changes: 54 additions & 1 deletion src/cell/builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ use crate::util::{ArrayVec, Bitstring};
use super::CellFamily;
#[cfg(feature = "stats")]
use super::CellTreeStats;
#[cfg(feature = "arbitrary")]
use arbitrary::{Arbitrary, Result as ArbitraryResult, Unstructured};

/// A data structure that can be serialized into cells.
pub trait Store {
Expand Down Expand Up @@ -786,6 +788,30 @@ impl CellBuilder {
Err(Error::CellOverflow)
}
}

#[cfg(feature = "arbitrary")]
fn arbitrary_with_depth(u: &mut Unstructured, depth: usize) -> ArbitraryResult<Self> {
let mut builder = CellBuilder::new();

// Generate a random bit length within the valid range
let random_bit_len = u.int_in_range(0..=MAX_BIT_LEN)?;
let random_bytes = u.bytes(random_bit_len as usize / 8 + 1)?;
builder
.store_raw(random_bytes, random_bit_len)
.expect("valid bit length");

if depth > 0 {
let ref_count = u.int_in_range(0..=MAX_REF_COUNT as u8)?;
for _ in 0..ref_count {
let child = Self::arbitrary_with_depth(u, depth - 1)?
.build()
.map_err(|_| arbitrary::Error::IncorrectFormat)?;
builder.store_reference(child).expect("reference fits");
}
}

Ok(builder)
}
}

#[inline]
Expand Down Expand Up @@ -882,7 +908,7 @@ impl CellBuilder {
}

/// Tries to append a builder (its data and references),
/// returning `false` if there is not enough remaining capacity.
/// returning `Error::CellOverflow` if there is not enough remaining capacity.
pub fn store_builder(&mut self, builder: &Self) -> Result<(), Error> {
if self.bit_len + builder.bit_len <= MAX_BIT_LEN
&& self.references.len() + builder.references.len() <= MAX_REF_COUNT
Expand Down Expand Up @@ -1219,6 +1245,33 @@ impl CellImpl for IntermediateFullCell {
}
}

#[cfg(feature = "arbitrary")]
impl<'a> Arbitrary<'a> for CellBuilder {
fn arbitrary(u: &mut Unstructured<'a>) -> ArbitraryResult<Self> {
let depth = u.int_in_range(0..=5)?;
Self::arbitrary_with_depth(u, depth)
}

fn size_hint(depth: usize) -> (usize, Option<usize>) {
let bit_len_hint = (0, Some((MAX_BIT_LEN / 8 + 1 + 2) as usize)); // from 0 to MAX_BIT_LEN bits + bit len

// Base case: if depth is zero, we do not include recursive cell hints
if depth == 0 {
return bit_len_hint;
}

// Recursive case: include recursive cell hints
let child_hint = <CellBuilder as Arbitrary>::size_hint(depth - 1);

let lower = bit_len_hint.0 + child_hint.0 * MAX_REF_COUNT;
let upper = bit_len_hint
.1
.and_then(|x| child_hint.1.map(|y| x + y * MAX_REF_COUNT));

(lower, upper)
}
}

#[cfg(test)]
mod tests {
use super::*;
Expand Down
10 changes: 10 additions & 0 deletions src/cell/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1527,6 +1527,16 @@ pub const MAX_BIT_LEN: u16 = 1023;
/// Maximum number of child cells
pub const MAX_REF_COUNT: usize = 4;

#[cfg(feature = "arbitrary")]
impl arbitrary::Arbitrary<'_> for Cell {
fn arbitrary(u: &mut arbitrary::Unstructured) -> arbitrary::Result<Self> {
let builder = CellBuilder::arbitrary(u)?;
builder
.build()
.map_err(|_| arbitrary::Error::IncorrectFormat)
}
}

#[cfg(test)]
mod tests {
use super::*;
Expand Down

0 comments on commit c51f801

Please sign in to comment.