diff --git a/Cargo.toml b/Cargo.toml index 3bc9322e..6b767ad4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -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 } @@ -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 = [] @@ -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] diff --git a/fuzz/.gitignore b/fuzz/.gitignore index a0925114..1a45eee7 100644 --- a/fuzz/.gitignore +++ b/fuzz/.gitignore @@ -1,3 +1,4 @@ target corpus artifacts +coverage diff --git a/fuzz/Cargo.toml b/fuzz/Cargo.toml index 7b2cd142..7ea31c97 100644 --- a/fuzz/Cargo.toml +++ b/fuzz/Cargo.toml @@ -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] @@ -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 diff --git a/fuzz/fuzz_targets/boc_decode_encode.rs b/fuzz/fuzz_targets/boc_decode_encode.rs index 4eddac4e..c1a8feed 100644 --- a/fuzz/fuzz_targets/boc_decode_encode.rs +++ b/fuzz/fuzz_targets/boc_decode_encode.rs @@ -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() +} diff --git a/fuzz/fuzz_targets/boc_dict_arb.rs b/fuzz/fuzz_targets/boc_dict_arb.rs new file mode 100644 index 00000000..4681450c --- /dev/null +++ b/fuzz/fuzz_targets/boc_dict_arb.rs @@ -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::>() { + _ = map.iter().count(); + return Corpus::Keep; + } + Corpus::Reject +}); diff --git a/fuzz/fuzz_targets/boc_message_arb.rs b/fuzz/fuzz_targets/boc_message_arb.rs new file mode 100644 index 00000000..aae93028 --- /dev/null +++ b/fuzz/fuzz_targets/boc_message_arb.rs @@ -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::().is_ok() { + return Corpus::Keep; + } + + Corpus::Reject +}); diff --git a/src/cell/builder.rs b/src/cell/builder.rs index 52d69955..c4a556dd 100644 --- a/src/cell/builder.rs +++ b/src/cell/builder.rs @@ -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 { @@ -786,6 +788,30 @@ impl CellBuilder { Err(Error::CellOverflow) } } + + #[cfg(feature = "arbitrary")] + fn arbitrary_with_depth(u: &mut Unstructured, depth: usize) -> ArbitraryResult { + 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] @@ -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 @@ -1219,6 +1245,33 @@ impl CellImpl for IntermediateFullCell { } } +#[cfg(feature = "arbitrary")] +impl<'a> Arbitrary<'a> for CellBuilder { + fn arbitrary(u: &mut Unstructured<'a>) -> ArbitraryResult { + let depth = u.int_in_range(0..=5)?; + Self::arbitrary_with_depth(u, depth) + } + + fn size_hint(depth: usize) -> (usize, Option) { + 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 = ::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::*; diff --git a/src/cell/mod.rs b/src/cell/mod.rs index 02bc25ad..7376dc8e 100644 --- a/src/cell/mod.rs +++ b/src/cell/mod.rs @@ -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 { + let builder = CellBuilder::arbitrary(u)?; + builder + .build() + .map_err(|_| arbitrary::Error::IncorrectFormat) + } +} + #[cfg(test)] mod tests { use super::*; diff --git a/src/dict/aug.rs b/src/dict/aug.rs index 930172ba..c6e539ae 100644 --- a/src/dict/aug.rs +++ b/src/dict/aug.rs @@ -1019,7 +1019,7 @@ mod tests { (4258889371, SomeValue(4956495), 3256452222), ], ] { - let result = AugDict::::try_from_sorted_slice(&entries).unwrap(); + let result = AugDict::::try_from_sorted_slice(entries).unwrap(); let mut dict = AugDict::::new(); for (k, a, v) in entries { diff --git a/src/lib.rs b/src/lib.rs index 2c20b95c..a22d1eae 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -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 //! @@ -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 //! @@ -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 //!