Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(fuzz): arbitrary implementation for Cell and CellBuilder #15

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
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
5 changes: 5 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 All @@ -85,6 +87,9 @@ tycho = []
[profile.release]
debug = true

[lints.rust]
unexpected_cfgs = { level = "warn", check-cfg = ['cfg(fuzzing)'] }

[profile.dev.package.hex]
opt-level = 3
[profile.dev.package.base64]
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
2 changes: 1 addition & 1 deletion src/dict/aug.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1019,7 +1019,7 @@ mod tests {
(4258889371, SomeValue(4956495), 3256452222),
],
] {
let result = AugDict::<u32, SomeValue, u32>::try_from_sorted_slice(&entries).unwrap();
let result = AugDict::<u32, SomeValue, u32>::try_from_sorted_slice(entries).unwrap();

let mut dict = AugDict::<u32, SomeValue, u32>::new();
for (k, a, v) in entries {
Expand Down
36 changes: 18 additions & 18 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,18 +13,18 @@
//! ## `Cell` vs `CellSlice` vs `CellBuilder`
//!
//! - [`Cell`] is an immutable tree and provides only basic methods for accessing
//! nodes and some meta info.
//! nodes and some meta info.
//!
//! - [`CellSlice`] is a read-only view for a part of some cell. It can only
//! be obtained from an existing cell. A cell contains **up to 1023 bits** and
//! **up to 4 references**. Minimal data unit is bit, so a cell slice is similar
//! to a couple of ranges (bit range and refs range).
//! be obtained from an existing cell. A cell contains **up to 1023 bits** and
//! **up to 4 references**. Minimal data unit is bit, so a cell slice is similar
//! to a couple of ranges (bit range and refs range).
//!
//! - [`CellBuilder`] is used to create a new cell. It is used as an append-only
//! data structure and is the only way to create a new cell with the provided data.
//! Cell creation depends on a context (e.g. message creation in a wallet or a
//! TVM execution with gas tracking), so [`CellBuilder::build_ext`] accepts
//! a [`CellContext`] parameter which can be used to track and modify cells creation.
//! data structure and is the only way to create a new cell with the provided data.
//! Cell creation depends on a context (e.g. message creation in a wallet or a
//! TVM execution with gas tracking), so [`CellBuilder::build_ext`] accepts
//! a [`CellContext`] parameter which can be used to track and modify cells creation.
//!
//! ## BOC
//!
Expand All @@ -36,15 +36,15 @@
//! ### Merkle stuff
//!
//! - Pruned branch is a "building block" of merkle structures. A single pruned branch
//! cell replaces a whole subtree and contains just the hash of its root cell hash.
//! cell replaces a whole subtree and contains just the hash of its root cell hash.
//!
//! - [`MerkleProof`] contains a subset of original tree of cells. In most cases
//! it is created from [`UsageTree`] of some visited cells. Merkle proof is used
//! to proof that something was presented in the origin tree and provide some additional
//! context.
//! it is created from [`UsageTree`] of some visited cells. Merkle proof is used
//! to proof that something was presented in the origin tree and provide some additional
//! context.
//!
//! - [`MerkleUpdate`] describes a difference between two trees of cells. It can be
//! applied to old cell to create a new cell.
//! applied to old cell to create a new cell.
//!
//! ### Numeric stuff
//!
Expand All @@ -69,15 +69,15 @@
//! All models implement [`Load`] and [`Store`] traits for conversion from/to cells.
//!
//! - [`RawDict`] constrains only key size in bits. It is useful when a dictionary
//! can contain multiple types of values.
//! can contain multiple types of values.
//!
//! - [`Dict`] is a strongly typed version of definition and is a preferable way
//! of working with this data structure. Key type must implement [`DictKey`] trait,
//! which is implemented for numbers and addresses.
//! of working with this data structure. Key type must implement [`DictKey`] trait,
//! which is implemented for numbers and addresses.
//!
//! - [`AugDict`] adds additional values for all nodes. You can use it to quickly
//! access a subtotal of values for each subtree.
//! NOTE: this type is partially implemented due to its complexity.
//! access a subtotal of values for each subtree.
//! NOTE: this type is partially implemented due to its complexity.
//!
//! ## Supported Rust Versions
//!
Expand Down
Loading