Skip to content

Commit

Permalink
v0.1.13: long queue fix
Browse files Browse the repository at this point in the history
- fixes bug which caused long queues to behave strangely.
- fixes memory leak for `ByteData` wrapped `SharedBytes` or externals.
- add a couple of fuzzing tests
  • Loading branch information
TimLuq committed Dec 9, 2024
1 parent 0bc0f63 commit cfe1771
Show file tree
Hide file tree
Showing 25 changed files with 824 additions and 37 deletions.
3 changes: 2 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "bytedata"
version = "0.1.12"
version = "0.1.13"
edition = "2021"
rust-version = "1.75"
description = "Representation of a byte slice that is either static, borrowed, or shared."
Expand All @@ -13,6 +13,7 @@ keywords = ["arc", "buffers", "zero-copy", "io"]
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
arbitrary_1 = { package = "arbitrary", version = "1", optional = true, default-features = false }
bytes_1 = { package = "bytes", version = "^1.7.1", optional = true, default-features = false }
http-body_04 = { package = "http-body", version = "0.4.5", optional = true }
http-body_1 = { package = "http-body", version = "1", optional = true }
Expand Down
4 changes: 4 additions & 0 deletions fuzz/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
target
corpus
artifacts
coverage
129 changes: 129 additions & 0 deletions fuzz/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

29 changes: 29 additions & 0 deletions fuzz/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
[package]
name = "bytedata-fuzz"
version = "0.0.0"
publish = false
edition = "2021"

[package.metadata]
cargo-fuzz = true

[dependencies]
libfuzzer-sys = "=0.4.7"

[dependencies.bytedata]
path = ".."
features = ["arbitrary_1", "alloc", "std", "queue"]

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

[[bin]]
name = "bytequeue"
path = "fuzz_targets/bytequeue.rs"
test = false
doc = false
bench = false
46 changes: 46 additions & 0 deletions fuzz/fuzz_targets/bytedata.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
#![no_main]

use libfuzzer_sys::fuzz_target;

use bytedata::ByteData;

fuzz_target!(|input: ByteData| {
let full_len = input.len();
let owned = input.as_slice().to_vec();
assert_eq!(owned.len(), full_len);
let mut cloned_data = input.clone();
assert_eq!(cloned_data.len(), full_len);
let mut sliced_data = input.sliced(0..full_len);
assert_eq!(sliced_data.len(), full_len);
let fst_byte = input.first().copied();
let lst_byte = input.as_slice().last().copied();
assert_eq!(fst_byte, sliced_data.first().copied());
assert_eq!(lst_byte, sliced_data.as_slice().last().copied());
assert_eq!(fst_byte, owned.first().copied());
assert_eq!(lst_byte, owned.last().copied());

if !input.is_empty() {
let mut sliced_data = input.sliced(0..1);
assert_eq!(fst_byte, sliced_data.first().copied());
assert_eq!(fst_byte, sliced_data.last());
let mut sliced_data = input.sliced(full_len - 1..full_len);
assert_eq!(lst_byte, sliced_data.first().copied());
assert_eq!(lst_byte, sliced_data.last());
}

let mut owned_iter = owned.into_iter();
let mut input_iter = input.iter();
loop {
match (owned_iter.next(), input_iter.next()) {
(Some(owned_byte), Some(input_byte)) => {
assert_eq!(owned_byte, *input_byte);
}
(None, None) => {
break;
}
_ => {
panic!("Different lengths");
}
}
}
});
82 changes: 82 additions & 0 deletions fuzz/fuzz_targets/bytequeue.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
#![no_main]

use libfuzzer_sys::{fuzz_target, arbitrary::{Arbitrary, Unstructured}};

use bytedata::ByteQueue;

fn is_same_as(data: &[u8], queue: &ByteQueue) -> bool {
let mut offs = 0;
for chunk in queue.chunks() {
if chunk.as_slice() != &data[offs..offs + chunk.len()] {
return false;
}
offs += chunk.len();
}
offs == data.len()
}

fuzz_target!(|input: &[u8]| {
let uns = Unstructured::new(input);
let mut queue = <ByteQueue as Arbitrary>::arbitrary_take_rest(uns).unwrap();
let static_ref_data: Vec<u8> = queue.bytes().collect();
assert_eq!(queue.len(), static_ref_data.len());
if queue.is_empty() {
return;
}
if !is_same_as(&static_ref_data, &queue) {
let ref_data = bytedata::ByteStringRender::from_slice(&static_ref_data);
panic!("queue is not the same as the input data (from start)\r\n queue: {queue:?}\r\n ref_data: {ref_data:?}");
}
let mut ref_data = static_ref_data.clone();

{
let mut chunks = Vec::with_capacity(queue.chunk_len());
for chunk in queue.chunks() {
chunks.push(chunk.len());
}
let end = queue.len() - 1;
let end_byte = queue.split_off(end);
assert_eq!(end_byte.len(), 1);
assert_eq!(queue.len(), end);
queue.push_back(end_byte);
assert_eq!(queue.len(), end + 1);

if !is_same_as(&ref_data, &queue) {
let ref_data = bytedata::ByteStringRender::from_slice(&ref_data);
panic!("queue is not the same as the input data (after re-push)\r\n queue: {queue:?}\r\n ref_data: {ref_data:?}\r\n chunks: {chunks:?}");
}
}

{
queue.push_front(b"test".as_ref());
assert_eq!(queue.len(), ref_data.len() + 4);
_ = queue.drain(0..5);
assert_eq!(queue.len(), ref_data.len() - 1);
let len = ref_data.len();
core::mem::drop(ref_data.drain(..1));

if !is_same_as(&ref_data, &queue) {
let ref_data = bytedata::ByteStringRender::from_slice(&ref_data);
panic!("queue is not the same as the input data (after push-drain)\r\n queue: {queue:?}\r\n ref_data: {ref_data:?}");
}

let mut dr = ref_data.drain(..);
let mut n = 0;
let mut dr2 = queue.drain(..);
while let Some(b) = dr2.next() {
n += 1;
let ref_b = dr.next().unwrap();
if b != ref_b {
let dr = dr.collect::<Vec<u8>>();
let dr2 = dr2.collect::<Vec<u8>>();
let part0 = &static_ref_data[..n];
let ref_data = [part0, core::slice::from_ref(&ref_b), &dr];
let ref_data = bytedata::MultiByteStringRender::new(&ref_data);
let queue = [part0, core::slice::from_ref(&b), &dr2];
let queue = bytedata::MultiByteStringRender::new(&queue);
panic!("byte {n}/{len} differs, {b} != {ref_b}\r\n queue: {queue:?}\r\n ref_data: {ref_data:?}");
return;
}
}
}
});
47 changes: 47 additions & 0 deletions src/arbitrary_1/byte_queue.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
use arbitrary_1::{Arbitrary, Error, Unstructured};

use crate::{ByteData, ByteQueue};

#[cfg_attr(docsrs, doc(cfg(feature = "queue")))]
#[cfg_attr(docsrs, doc(cfg(feature = "arbitrary_1")))]
impl<'a> Arbitrary<'a> for ByteQueue<'a> {
#[allow(
clippy::missing_inline_in_public_items,
clippy::unwrap_in_result,
clippy::min_ident_chars
)]
fn arbitrary(u: &mut Unstructured<'a>) -> Result<Self, Error> {
let mut buffer = Self::new();
let mut maxlen = u.arbitrary_len::<ByteData<'a>>()?;
while !u.is_empty() && maxlen != 0 {
let data = u.arbitrary::<ByteData<'a>>()?;
maxlen -= 1;
let dir = u.arbitrary::<bool>().unwrap_or_default();
if dir {
buffer.push_front(data);
} else {
buffer.push_back(data);
}
}
Ok(buffer)
}

#[allow(
clippy::missing_inline_in_public_items,
clippy::unwrap_in_result,
clippy::min_ident_chars
)]
fn arbitrary_take_rest(mut u: Unstructured<'a>) -> Result<Self, Error> {
let mut buffer = Self::new();
while !u.is_empty() {
let data = u.arbitrary::<ByteData<'a>>()?;
let dir = u.arbitrary::<bool>().unwrap_or_default();
if dir {
buffer.push_front(data);
} else {
buffer.push_back(data);
}
}
Ok(buffer)
}
}
Loading

0 comments on commit cfe1771

Please sign in to comment.