diff --git a/CHANGELOG.md b/CHANGELOG.md index 50c3ff8..e86fe2c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,16 @@ # Changelog ## Unreleased +- **fix soundness**: changed `Bytes` and `View` internal data fields from + `&'static [u8]`/`&'static T` to raw pointers (`*const [u8]`/`*const T`) + to fix undefined behavior under both Stacked Borrows and Tree Borrows when + `Bytes` or `View` is passed by value to a function (including + `std::mem::drop`) while holding the last strong reference to the owner +- removed `erase_lifetime` helper, now unnecessary with raw pointer storage +- added Miri test suite (`tests/miri.rs`) with 36 tests covering all unsafe + code paths: lifetime erasure, weak reference upgrades, `try_unwrap_owner` + data pointer reconstruction, view operations, and complex drop orderings +- added `scripts/miri.sh` for running Miri tests with Tree Borrows - added optional `burn` feature with `ByteSource` support for `burn_tensor::Bytes` - added zero-copy conversion from `anybytes::Bytes` to `burn_tensor::Bytes` - added burn-feature tests covering both conversion directions and sliced views diff --git a/INVENTORY.md b/INVENTORY.md index 0996435..4f34d4a 100644 --- a/INVENTORY.md +++ b/INVENTORY.md @@ -9,4 +9,11 @@ - Explore how to model `ByteArea` for Kani or fuzzing without depending on OS-backed memory maps. ## Discovered Issues -- None at the moment. +- `Bytes::from_source` with `Box` triggers a Stacked Borrows violation + because moving the Box invalidates prior tags on its heap allocation. This is + a known limitation of Stacked Borrows with Box; Tree Borrows handles it + correctly. The Miri tests use Tree Borrows (`-Zmiri-tree-borrows`) + accordingly. +- The `test_winnow_view_parser` test fails under Miri because it assumes the + `Vec` allocation is 2-byte aligned for a `u16`-containing struct; Miri + doesn't guarantee this alignment. diff --git a/scripts/miri.sh b/scripts/miri.sh new file mode 100755 index 0000000..8d86c0f --- /dev/null +++ b/scripts/miri.sh @@ -0,0 +1,13 @@ +#!/usr/bin/env bash +set -euo pipefail + +# Move to repository root +cd "$(dirname "$0")/.." + +# Run Miri tests with Tree Borrows. +# +# Tree Borrows is the successor to Stacked Borrows and is the recommended +# model for new code. The from_source path has a known Stacked Borrows +# incompatibility with Box (moving a Box invalidates prior tags on its +# allocation) that Tree Borrows handles correctly. +MIRIFLAGS="-Zmiri-tree-borrows" cargo +nightly miri test --test miri "$@" diff --git a/src/bytes.rs b/src/bytes.rs index 9d4c88f..33bf259 100644 --- a/src/bytes.rs +++ b/src/bytes.rs @@ -63,8 +63,6 @@ use std::ops::Deref; use std::slice::SliceIndex; use std::sync::{Arc, Weak}; -use crate::erase_lifetime; - pub(crate) fn is_subslice(slice: &[u8], subslice: &[u8]) -> bool { let slice_start = slice.as_ptr() as usize; let slice_end = slice_start + slice.len(); @@ -121,7 +119,12 @@ impl ByteOwner for T {} /// /// See [ByteOwner] for an exhaustive list and more details. pub struct Bytes { - data: &'static [u8], + // Raw pointer instead of a reference to avoid Stacked/Tree Borrows + // violations when `Bytes` is passed by value to a function (including + // `std::mem::drop`) and this happens to be the last strong reference. + // A `&'static [u8]` would be "protected" as a function argument while + // the `Arc` owner is deallocated, which is UB under both borrow models. + data: *const [u8], // Actual owner of the bytes. owner: Arc, } @@ -138,6 +141,8 @@ pub struct WeakBytes { } // ByteOwner is Send + Sync and Bytes is immutable. +// Raw pointers are !Send + !Sync by default, but the owner guarantees +// the backing data is accessible from any thread. unsafe impl Send for Bytes {} unsafe impl Sync for Bytes {} @@ -153,13 +158,18 @@ impl Clone for Bytes { // Core implementation of Bytes. impl Bytes { #[inline] - pub(crate) unsafe fn get_data(&self) -> &'static [u8] { + pub(crate) fn data_ptr(&self) -> *const [u8] { self.data } #[inline] - pub(crate) unsafe fn set_data(&mut self, data: &'static [u8]) { - self.data = data; + pub(crate) unsafe fn get_data(&self) -> &[u8] { + unsafe { &*self.data } + } + + #[inline] + pub(crate) unsafe fn set_data(&mut self, data: &[u8]) { + self.data = data as *const [u8]; } #[inline] @@ -183,19 +193,18 @@ impl Bytes { /// # Safety /// The caller must ensure that `data` remains valid for the lifetime of /// `owner`. No lifetime checks are performed. - pub unsafe fn from_raw_parts(data: &'static [u8], owner: Arc) -> Self { - Self { data, owner } + pub unsafe fn from_raw_parts(data: &[u8], owner: Arc) -> Self { + Self { + data: data as *const [u8], + owner, + } } /// Creates `Bytes` from a [`ByteSource`] (for example, `Vec`). pub fn from_source(source: impl ByteSource) -> Self { - let data = source.as_bytes(); - // Erase the lifetime. - let data = unsafe { erase_lifetime(data) }; - + let data = source.as_bytes() as *const [u8]; let owner = source.get_owner(); let owner = Arc::new(owner); - Self { data, owner } } @@ -207,9 +216,7 @@ impl Bytes { /// sadly we can't provide a blanked implementation for those types /// because of the orphane rule. pub fn from_owning_source_arc(arc: Arc) -> Self { - let data = arc.as_bytes(); - // Erase the lifetime. - let data = unsafe { erase_lifetime(data) }; + let data = arc.as_bytes() as *const [u8]; Self { data, owner: arc } } @@ -259,8 +266,9 @@ impl Bytes { } #[inline] - pub(crate) fn as_slice<'a>(&'a self) -> &'a [u8] { - self.data + pub(crate) fn as_slice(&self) -> &[u8] { + // SAFETY: The owner keeps the data alive for the lifetime of self. + unsafe { &*self.data } } /// Returns the owner of the Bytes as a `Arc`. @@ -297,7 +305,6 @@ impl Bytes { where T: ByteOwner + Send + Sync + 'static, { - // Destructure to make it explicit that `self` is moved. let Self { data, owner } = self; // Verify the concrete type without releasing the owner yet. @@ -307,10 +314,6 @@ impl Bytes { Err(_) => return Err(Self { data, owner }), }; - // Avoid dangling `data` when dropping the owner by switching to a - // raw pointer right before we release our dynamic reference. - let data_ptr = data as *const [u8]; - // Drop our dynamic reference so the downcasted `Arc` becomes unique. drop(owner); @@ -319,7 +322,6 @@ impl Bytes { Err(arc_t) => { // Another strong reference exists; rebuild the `Bytes`. let owner: Arc = arc_t; - let data = unsafe { &*data_ptr }; Err(Self { data, owner }) } } @@ -328,8 +330,9 @@ impl Bytes { /// Returns a slice of self for the provided range. /// This operation is `O(1)`. pub fn slice(&self, range: impl SliceIndex<[u8], Output = [u8]>) -> Self { + let sliced = &self.as_slice()[range]; Self { - data: &self.data[range], + data: sliced as *const [u8], owner: self.owner.clone(), } } @@ -342,10 +345,12 @@ impl Bytes { /// This is similar to `bytes::Bytes::slice_ref` from `bytes 0.5.4`, /// but does not panic. pub fn slice_to_bytes(&self, slice: &[u8]) -> Option { - if is_subslice(self.data, slice) { - let data = unsafe { erase_lifetime(slice) }; + if is_subslice(self.as_slice(), slice) { let owner = self.owner.clone(); - Some(Self { data, owner }) + Some(Self { + data: slice as *const [u8], + owner, + }) } else { None } @@ -356,13 +361,15 @@ impl Bytes { /// Returns `None` if `len` is greater than the length of `self`. /// This operation is `O(1)`. pub fn take_prefix(&mut self, len: usize) -> Option { - if len > self.data.len() { + // Copy the pointer to avoid borrowing self. + let slice = unsafe { &*self.data }; + if len > slice.len() { return None; } - let (data, rest) = self.data.split_at(len); - self.data = rest; + let (data, rest) = slice.split_at(len); + self.data = rest as *const [u8]; Some(Self { - data, + data: data as *const [u8], owner: self.owner.clone(), }) } @@ -372,35 +379,38 @@ impl Bytes { /// Returns `None` if `len` is greater than the length of `self`. /// This operation is `O(1)`. pub fn take_suffix(&mut self, len: usize) -> Option { - if len > self.data.len() { + let slice = unsafe { &*self.data }; + if len > slice.len() { return None; } - let (rest, data) = self.data.split_at(self.data.len() - len); - self.data = rest; + let (rest, data) = slice.split_at(slice.len() - len); + self.data = rest as *const [u8]; Some(Self { - data, + data: data as *const [u8], owner: self.owner.clone(), }) } /// Removes and returns the first byte of `self`. pub fn pop_front(&mut self) -> Option { - let (&b, rest) = self.data.split_first()?; - self.data = rest; + let slice = unsafe { &*self.data }; + let (&b, rest) = slice.split_first()?; + self.data = rest as *const [u8]; Some(b) } /// Removes and returns the last byte of `self`. pub fn pop_back(&mut self) -> Option { - let (last, rest) = self.data.split_last()?; - self.data = rest; + let slice = unsafe { &*self.data }; + let (last, rest) = slice.split_last()?; + self.data = rest as *const [u8]; Some(*last) } /// Create a weak pointer. pub fn downgrade(&self) -> WeakBytes { WeakBytes { - data: self.data as *const [u8], + data: self.data, owner: Arc::downgrade(&self.owner), } } @@ -410,8 +420,10 @@ impl WeakBytes { /// The reverse of `downgrade`. Returns `None` if the value was dropped. pub fn upgrade(&self) -> Option { let arc = self.owner.upgrade()?; - let data = unsafe { &*(self.data) }; - Some(Bytes { data, owner: arc }) + Some(Bytes { + data: self.data, + owner: arc, + }) } } @@ -512,20 +524,21 @@ impl fmt::Debug for Bytes { impl bytes::Buf for Bytes { #[inline] fn remaining(&self) -> usize { - self.data.len() + self.as_slice().len() } #[inline] fn chunk(&self) -> &[u8] { - self.data + self.as_slice() } #[inline] fn advance(&mut self, cnt: usize) { - if cnt > self.data.len() { - panic!("advance out of bounds: {} > {}", cnt, self.data.len()); + let slice = unsafe { &*self.data }; + if cnt > slice.len() { + panic!("advance out of bounds: {} > {}", cnt, slice.len()); } - self.data = &self.data[cnt..]; + self.data = &slice[cnt..] as *const [u8]; } } diff --git a/src/lib.rs b/src/lib.rs index b1a4a70..0dfe6ac 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -40,12 +40,3 @@ pub use crate::bytes::WeakBytes; pub use crate::pyanybytes::PyAnyBytes; #[cfg(feature = "zerocopy")] pub use crate::view::View; - -/// Erase the lifetime of a reference. -/// -/// # Safety -/// The caller must guarantee that the referenced data remains valid for the -/// `'static` lifetime. -unsafe fn erase_lifetime<'a, T: ?Sized>(slice: &'a T) -> &'static T { - &*(slice as *const T) -} diff --git a/src/view.rs b/src/view.rs index dedd4ba..2d6fa47 100644 --- a/src/view.rs +++ b/src/view.rs @@ -10,7 +10,6 @@ use std::borrow::Borrow; use std::cmp::Ordering; use crate::bytes::is_subslice; -use crate::erase_lifetime; use crate::{bytes::ByteOwner, Bytes}; use std::any::Any; use std::sync::{Arc, Weak}; @@ -75,7 +74,7 @@ impl Bytes { unsafe { match ::try_ref_from_bytes(self.get_data()) { Ok(data) => Ok(View { - data, + data: data as *const T, owner: self.take_owner(), }), Err(err) => Err(ViewError::from_cast_error(&self, err)), @@ -88,17 +87,18 @@ impl Bytes { where T: ?Sized + TryFromBytes + KnownLayout + Immutable, { - unsafe { - match ::try_ref_from_prefix(self.get_data()) { - Ok((data, rest)) => { - self.set_data(rest); - Ok(View { - data, - owner: self.get_owner(), - }) - } - Err(err) => Err(ViewError::from_cast_error(self, err)), + // SAFETY: Read through the raw pointer to avoid borrowing self, + // allowing the subsequent set_data call. + let slice = unsafe { &*self.data_ptr() }; + match ::try_ref_from_prefix(slice) { + Ok((data, rest)) => { + unsafe { self.set_data(rest) }; + Ok(View { + data: data as *const T, + owner: self.get_owner(), + }) } + Err(err) => Err(ViewError::from_cast_error(self, err)), } } @@ -108,17 +108,16 @@ impl Bytes { where T: ?Sized + TryFromBytes + KnownLayout + Immutable, { - unsafe { - match ::try_ref_from_prefix_with_elems(self.get_data(), count) { - Ok((data, rest)) => { - self.set_data(rest); - Ok(View { - data, - owner: self.get_owner(), - }) - } - Err(err) => Err(ViewError::from_cast_error(self, err)), + let slice = unsafe { &*self.data_ptr() }; + match ::try_ref_from_prefix_with_elems(slice, count) { + Ok((data, rest)) => { + unsafe { self.set_data(rest) }; + Ok(View { + data: data as *const T, + owner: self.get_owner(), + }) } + Err(err) => Err(ViewError::from_cast_error(self, err)), } } @@ -127,17 +126,16 @@ impl Bytes { where T: ?Sized + TryFromBytes + KnownLayout + Immutable, { - unsafe { - match ::try_ref_from_suffix(self.get_data()) { - Ok((rest, data)) => { - self.set_data(rest); - Ok(View { - data, - owner: self.get_owner(), - }) - } - Err(err) => Err(ViewError::from_cast_error(self, err)), + let slice = unsafe { &*self.data_ptr() }; + match ::try_ref_from_suffix(slice) { + Ok((rest, data)) => { + unsafe { self.set_data(rest) }; + Ok(View { + data: data as *const T, + owner: self.get_owner(), + }) } + Err(err) => Err(ViewError::from_cast_error(self, err)), } } @@ -147,17 +145,16 @@ impl Bytes { where T: ?Sized + TryFromBytes + KnownLayout + Immutable, { - unsafe { - match ::try_ref_from_suffix_with_elems(self.get_data(), count) { - Ok((rest, data)) => { - self.set_data(rest); - Ok(View { - data, - owner: self.get_owner(), - }) - } - Err(err) => Err(ViewError::from_cast_error(self, err)), + let slice = unsafe { &*self.data_ptr() }; + match ::try_ref_from_suffix_with_elems(slice, count) { + Ok((rest, data)) => { + unsafe { self.set_data(rest) }; + Ok(View { + data: data as *const T, + owner: self.get_owner(), + }) } + Err(err) => Err(ViewError::from_cast_error(self, err)), } } } @@ -171,7 +168,9 @@ impl Bytes { /// /// See [ByteOwner] for an exhaustive list and more details. pub struct View { - pub(crate) data: &'static T, + // Raw pointer instead of a reference to avoid Stacked/Tree Borrows + // violations when `View` is passed by value (same rationale as `Bytes`). + pub(crate) data: *const T, // Actual owner of the bytes. pub(crate) owner: Arc, } @@ -204,6 +203,8 @@ impl Debug for WeakView { } // ByteOwner is Send + Sync and View is immutable. +// Raw pointers are !Send + !Sync by default, but the owner guarantees +// the backing data is accessible from any thread. unsafe impl Send for View {} unsafe impl Sync for View {} @@ -223,8 +224,11 @@ impl View { /// # Safety /// The caller must guarantee that `data` remains valid for the lifetime of /// `owner`. - pub unsafe fn from_raw_parts(data: &'static T, owner: Arc) -> Self { - Self { data, owner } + pub unsafe fn from_raw_parts(data: &T, owner: Arc) -> Self { + Self { + data: data as *const T, + owner, + } } /// Returns the owner of the View in an `Arc`. @@ -242,7 +246,7 @@ impl View { /// Create a weak pointer. pub fn downgrade(&self) -> WeakView { WeakView { - data: self.data as *const T, + data: self.data, owner: Arc::downgrade(&self.owner), } } @@ -251,7 +255,9 @@ impl View { impl View { /// Converts this view back into [`Bytes`]. pub fn bytes(self) -> Bytes { - let bytes = IntoBytes::as_bytes(self.data); + // SAFETY: The owner keeps the data alive. + let data = unsafe { &*self.data }; + let bytes = IntoBytes::as_bytes(data); unsafe { Bytes::from_raw_parts(bytes, self.owner) } } @@ -262,12 +268,16 @@ impl View { /// /// This is similar to `Bytes::slice_to_bytes` but for `View`. pub fn field_to_view(&self, field: &F) -> Option> { - let self_bytes = IntoBytes::as_bytes(self.data); + // SAFETY: The owner keeps the data alive. + let data = unsafe { &*self.data }; + let self_bytes = IntoBytes::as_bytes(data); let field_bytes = IntoBytes::as_bytes(field); if is_subslice(self_bytes, field_bytes) { - let data = unsafe { erase_lifetime(field) }; let owner = self.owner.clone(); - Some(View:: { data, owner }) + Some(View:: { + data: field as *const F, + owner, + }) } else { None } @@ -278,8 +288,10 @@ impl WeakView { /// The reverse of `downgrade`. Returns `None` if the value was dropped. pub fn upgrade(&self) -> Option> { let arc = self.owner.upgrade()?; - let data = unsafe { &*(self.data) }; - Some(View { data, owner: arc }) + Some(View { + data: self.data, + owner: arc, + }) } } @@ -291,7 +303,8 @@ where #[inline] fn deref(&self) -> &Self::Target { - self.data + // SAFETY: The owner keeps the data alive for the lifetime of self. + unsafe { &*self.data } } } diff --git a/tests/miri.rs b/tests/miri.rs new file mode 100644 index 0000000..1ebd4c2 --- /dev/null +++ b/tests/miri.rs @@ -0,0 +1,480 @@ +//! Miri test suite targeting unsafe code paths in anybytes. +//! +//! These tests exercise the lifetime erasure, raw pointer dereferences, and +//! ownership tricks that Kani cannot verify. Run with: +//! +//! ```sh +//! cargo +nightly miri test --test miri +//! ``` +//! +//! Miri detects undefined behavior such as use-after-free, dangling pointer +//! dereferences, and Stacked Borrows violations, complementing Kani's +//! functional correctness proofs. + +use anybytes::Bytes; +use std::sync::Arc; + +// --------------------------------------------------------------------------- +// erase_lifetime / from_source soundness +// --------------------------------------------------------------------------- + +/// The fundamental invariant: data obtained from a `ByteSource` remains valid +/// as long as the `Bytes` (and its Arc owner) is alive. +#[test] +fn from_source_vec_data_valid() { + let bytes = Bytes::from_source(vec![1u8, 2, 3, 4]); + assert_eq!(bytes.as_ref(), &[1, 2, 3, 4]); +} + +/// Slicing creates a new Bytes sharing the owner. The original can be dropped +/// and the slice must still be valid (erase_lifetime on the subslice). +#[test] +fn slice_survives_original_drop() { + let bytes = Bytes::from_source(vec![10u8, 20, 30, 40, 50]); + let slice = bytes.slice(1..4); + drop(bytes); + assert_eq!(slice.as_ref(), &[20, 30, 40]); +} + +/// Multiple levels of slicing, each sharing the same owner. +#[test] +fn nested_slices_survive_drops() { + let bytes = Bytes::from_source(vec![0u8, 1, 2, 3, 4, 5, 6, 7]); + let a = bytes.slice(2..6); + let b = a.slice(1..3); + drop(bytes); + drop(a); + assert_eq!(b.as_ref(), &[3, 4]); +} + +/// `slice_to_bytes` erases the lifetime of a derived subslice. +#[test] +fn slice_to_bytes_lifetime_erasure() { + let bytes = Bytes::from_source(vec![10u8, 20, 30, 40]); + let inner = &bytes.as_ref()[1..3]; + let sub = bytes.slice_to_bytes(inner).expect("subslice"); + drop(bytes); + assert_eq!(sub.as_ref(), &[20, 30]); +} + +/// Empty source is a valid edge case for lifetime erasure. +#[test] +fn empty_source_is_sound() { + let bytes = Bytes::from_source(Vec::::new()); + assert!(bytes.is_empty()); + let clone = bytes.clone(); + drop(bytes); + assert!(clone.is_empty()); +} + +/// Static source: lifetime erasure is trivially sound for 'static data. +#[test] +fn static_source_is_sound() { + let bytes = Bytes::from_source(&b"hello"[..]); + let slice = bytes.slice(1..4); + drop(bytes); + assert_eq!(slice.as_ref(), b"ell"); +} + +/// String source exercises a different ByteSource impl. +#[test] +fn string_source_is_sound() { + let bytes = Bytes::from_source(String::from("hello world")); + let slice = bytes.slice(6..11); + drop(bytes); + assert_eq!(slice.as_ref(), b"world"); +} + +/// Arc> source reuses the Arc without extra allocation. +#[test] +fn arc_source_is_sound() { + let arc = Arc::new(vec![1u8, 2, 3]); + let bytes = Bytes::from_owning_source_arc(arc); + let slice = bytes.slice(1..3); + drop(bytes); + assert_eq!(slice.as_ref(), &[2, 3]); +} + +// --------------------------------------------------------------------------- +// WeakBytes: raw pointer deref in upgrade() +// --------------------------------------------------------------------------- + +/// upgrade() dereferences `self.data` (a raw pointer) only after confirming +/// the owner is still alive via Weak::upgrade(). Miri validates the pointer. +#[test] +fn weakbytes_upgrade_while_alive() { + let bytes = Bytes::from_source(vec![1u8, 2, 3]); + let weak = bytes.downgrade(); + let upgraded = weak.upgrade().expect("owner alive"); + assert_eq!(upgraded.as_ref(), &[1, 2, 3]); +} + +/// After all strong references are dropped, upgrade must return None +/// without dereferencing the dangling pointer. +#[test] +fn weakbytes_upgrade_after_drop() { + let bytes = Bytes::from_source(vec![1u8, 2, 3]); + let weak = bytes.downgrade(); + drop(bytes); + assert!(weak.upgrade().is_none()); +} + +/// Weak from a slice: the raw pointer points into the middle of the +/// allocation. Upgrading must still be valid. +#[test] +fn weakbytes_from_slice() { + let bytes = Bytes::from_source(vec![10u8, 20, 30, 40, 50]); + let slice = bytes.slice(2..4); + let weak = slice.downgrade(); + drop(slice); + let upgraded = weak.upgrade().expect("original keeps owner alive"); + assert_eq!(upgraded.as_ref(), &[30, 40]); + drop(bytes); + drop(upgraded); + assert!(weak.upgrade().is_none()); +} + +/// Multiple weak references from different slices of the same owner. +#[test] +fn weakbytes_multiple_from_same_owner() { + let bytes = Bytes::from_source(vec![0u8, 1, 2, 3, 4]); + let w1 = bytes.slice(0..2).downgrade(); + let w2 = bytes.slice(3..5).downgrade(); + + assert_eq!(w1.upgrade().unwrap().as_ref(), &[0, 1]); + assert_eq!(w2.upgrade().unwrap().as_ref(), &[3, 4]); + + drop(bytes); + assert!(w1.upgrade().is_none()); + assert!(w2.upgrade().is_none()); +} + +/// Clone a WeakBytes, drop original weak, upgrade the clone. +#[test] +fn weakbytes_clone_then_upgrade() { + let bytes = Bytes::from_source(vec![5u8, 6, 7]); + let weak = bytes.downgrade(); + let weak2 = weak.clone(); + drop(weak); + let strong = weak2.upgrade().expect("clone still valid"); + assert_eq!(strong.as_ref(), &[5, 6, 7]); +} + +// --------------------------------------------------------------------------- +// try_unwrap_owner: the data_ptr trick +// --------------------------------------------------------------------------- + +/// Success path: unique owner, Arc::try_unwrap succeeds. +/// The raw `data_ptr` is never dereferenced. +#[test] +fn try_unwrap_owner_unique() { + let bytes = Bytes::from_source(vec![1u8, 2, 3]); + let v = bytes.try_unwrap_owner::>().expect("unique owner"); + assert_eq!(v, vec![1u8, 2, 3]); +} + +/// Failure path (shared): `data` is converted to raw pointer, dynamic Arc is +/// dropped, then Arc::try_unwrap fails, and the raw pointer is dereferenced +/// to reconstruct the Bytes. This is the critical unsafe path. +#[test] +fn try_unwrap_owner_shared_reconstructs() { + let bytes = Bytes::from_source(vec![1u8, 2, 3]); + let clone = bytes.clone(); + let err = bytes.try_unwrap_owner::>().unwrap_err(); + assert_eq!(err.as_ref(), &[1, 2, 3]); + assert_eq!(clone.as_ref(), &[1, 2, 3]); +} + +/// Failure path (wrong type): early return before the data_ptr trick. +#[test] +fn try_unwrap_owner_wrong_type() { + let bytes = Bytes::from_source(vec![1u8, 2, 3]); + let err = bytes.try_unwrap_owner::().unwrap_err(); + assert_eq!(err.as_ref(), &[1, 2, 3]); +} + +/// try_unwrap_owner on a sliced Bytes: the data pointer points into the +/// middle of the allocation, not to its start. +#[test] +fn try_unwrap_owner_after_slice() { + let mut bytes = Bytes::from_source(vec![10u8, 20, 30, 40]); + let _ = bytes.take_prefix(1); + // data now points to offset 1 in the Vec's allocation + let clone = bytes.clone(); + let err = bytes.try_unwrap_owner::>().unwrap_err(); + assert_eq!(err.as_ref(), &[20, 30, 40]); + drop(clone); + // Now unique - but data ptr still points into middle of Vec + let v = err.try_unwrap_owner::>().expect("now unique"); + assert_eq!(v, vec![10u8, 20, 30, 40]); +} + +// --------------------------------------------------------------------------- +// downcast_to_owner +// --------------------------------------------------------------------------- + +/// Downcast success: the Arc is cloned and downcast. +#[test] +fn downcast_to_owner_success() { + let bytes = Bytes::from_source(vec![1u8, 2, 3]); + let arc: Arc> = bytes.downcast_to_owner().expect("downcast"); + assert_eq!(&*arc, &[1, 2, 3]); +} + +/// Downcast failure: original Bytes is returned intact. +#[test] +fn downcast_to_owner_failure_preserves_data() { + let bytes = Bytes::from_source(vec![1u8, 2, 3]); + let err = bytes.downcast_to_owner::().unwrap_err(); + assert_eq!(err.as_ref(), &[1, 2, 3]); +} + +// --------------------------------------------------------------------------- +// take_prefix / take_suffix / pop_front / pop_back: data pointer mutation +// --------------------------------------------------------------------------- + +/// take_prefix mutates self.data via split_at. The erased-lifetime pointer +/// must remain valid. +#[test] +fn take_prefix_pointer_remains_valid() { + let mut bytes = Bytes::from_source(vec![1u8, 2, 3, 4, 5]); + let prefix = bytes.take_prefix(3).unwrap(); + assert_eq!(prefix.as_ref(), &[1, 2, 3]); + assert_eq!(bytes.as_ref(), &[4, 5]); + // Drop prefix, remainder must still work + drop(prefix); + assert_eq!(bytes.as_ref(), &[4, 5]); +} + +/// Exhaustive pop_front draining every byte. +#[test] +fn pop_front_until_empty() { + let mut bytes = Bytes::from_source(vec![10u8, 20, 30]); + assert_eq!(bytes.pop_front(), Some(10)); + assert_eq!(bytes.pop_front(), Some(20)); + assert_eq!(bytes.pop_front(), Some(30)); + assert_eq!(bytes.pop_front(), None); + assert!(bytes.is_empty()); +} + +/// Interleaving pop_front and pop_back. +#[test] +fn interleaved_pop_front_back() { + let mut bytes = Bytes::from_source(vec![1u8, 2, 3, 4, 5]); + assert_eq!(bytes.pop_front(), Some(1)); + assert_eq!(bytes.pop_back(), Some(5)); + assert_eq!(bytes.pop_front(), Some(2)); + assert_eq!(bytes.pop_back(), Some(4)); + assert_eq!(bytes.pop_front(), Some(3)); + assert_eq!(bytes.pop_front(), None); + assert_eq!(bytes.pop_back(), None); +} + +// --------------------------------------------------------------------------- +// View: get_data/set_data, view construction, field_to_view +// --------------------------------------------------------------------------- + +#[cfg(feature = "zerocopy")] +mod view_tests { + use anybytes::Bytes; + + #[derive( + zerocopy::TryFromBytes, + zerocopy::IntoBytes, + zerocopy::KnownLayout, + zerocopy::Immutable, + Clone, + Copy, + Debug, + PartialEq, + )] + #[repr(C)] + struct Pair { + a: u32, + b: u32, + } + + /// View construction uses get_data() (unsafe lifetime-erased access) + /// and the zerocopy try_ref_from_bytes path. + #[test] + fn view_construction_is_sound() { + let pair = Pair { a: 42, b: 99 }; + let bytes = Bytes::from_source(Box::new(pair)); + let view = bytes.view::().unwrap(); + assert_eq!(*view, Pair { a: 42, b: 99 }); + } + + /// View::bytes() roundtrip: View -> Bytes uses from_raw_parts. + #[test] + fn view_to_bytes_roundtrip() { + let pair = Pair { a: 1, b: 2 }; + let bytes = Bytes::from_source(Box::new(pair)); + let view = bytes.view::().unwrap(); + let back = view.bytes(); + let view2 = back.view::().unwrap(); + assert_eq!(*view2, Pair { a: 1, b: 2 }); + } + + /// view_prefix uses set_data() to advance the internal pointer. + #[test] + fn view_prefix_advances_pointer() { + let mut bytes = Bytes::from_source(vec![1u8, 2, 3, 4, 5, 6, 7, 8]); + let view = bytes.view_prefix::<[u8; 4]>().unwrap(); + assert_eq!(*view, [1, 2, 3, 4]); + assert_eq!(bytes.as_ref(), &[5, 6, 7, 8]); + // Drop view, remainder must still be valid + drop(view); + assert_eq!(bytes.as_ref(), &[5, 6, 7, 8]); + } + + /// view_suffix uses set_data() to shrink the internal pointer. + #[test] + fn view_suffix_shrinks_pointer() { + let mut bytes = Bytes::from_source(vec![1u8, 2, 3, 4, 5, 6, 7, 8]); + let view = bytes.view_suffix::<[u8; 4]>().unwrap(); + assert_eq!(*view, [5, 6, 7, 8]); + assert_eq!(bytes.as_ref(), &[1, 2, 3, 4]); + drop(view); + assert_eq!(bytes.as_ref(), &[1, 2, 3, 4]); + } + + /// view_prefix_with_elems: slice-like view with dynamic count. + #[test] + fn view_prefix_with_elems_is_sound() { + let mut bytes = Bytes::from_source(vec![10u8, 20, 30, 40, 50]); + let view = bytes.view_prefix_with_elems::<[u8]>(3).unwrap(); + assert_eq!(view.as_ref(), &[10, 20, 30]); + assert_eq!(bytes.as_ref(), &[40, 50]); + } + + /// view_suffix_with_elems: slice-like view from the end. + #[test] + fn view_suffix_with_elems_is_sound() { + let mut bytes = Bytes::from_source(vec![10u8, 20, 30, 40, 50]); + let view = bytes.view_suffix_with_elems::<[u8]>(3).unwrap(); + assert_eq!(view.as_ref(), &[30, 40, 50]); + assert_eq!(bytes.as_ref(), &[10, 20]); + } + + /// field_to_view: derives a sub-view from a field reference, + /// erasing its lifetime via erase_lifetime. + #[test] + fn field_to_view_is_sound() { + let pair = Pair { a: 7, b: 13 }; + let bytes = Bytes::from_source(Box::new(pair)); + let view = bytes.view::().unwrap(); + let field_view = view.field_to_view(&view.a).expect("field view"); + assert_eq!(*field_view, 7u32); + // Drop parent view, field view keeps owner alive + drop(view); + assert_eq!(*field_view, 7u32); + } + + /// field_to_view with an unrelated reference must return None. + #[test] + fn field_to_view_rejects_unrelated() { + let pair = Pair { a: 7, b: 13 }; + let bytes = Bytes::from_source(Box::new(pair)); + let view = bytes.view::().unwrap(); + let unrelated: u32 = 42; + assert!(view.field_to_view(&unrelated).is_none()); + } + + /// WeakView upgrade dereferences a raw pointer to T. + #[test] + fn weakview_upgrade_while_alive() { + let bytes = Bytes::from_source(vec![1u8, 2, 3, 4]); + let view = bytes.clone().view::<[u8]>().unwrap(); + let weak = view.downgrade(); + let upgraded = weak.upgrade().expect("alive"); + assert_eq!(upgraded.as_ref(), &[1, 2, 3, 4]); + } + + /// WeakView upgrade after all strong refs dropped. + #[test] + fn weakview_upgrade_after_drop() { + let bytes = Bytes::from_source(vec![1u8, 2, 3, 4]); + let view = bytes.view::<[u8]>().unwrap(); + let weak = view.downgrade(); + drop(view); + assert!(weak.upgrade().is_none()); + } + + /// WeakView from a struct view, checking that the typed pointer + /// (not just byte pointer) is correctly handled. + #[test] + fn weakview_typed_struct() { + let pair = Pair { a: 100, b: 200 }; + let bytes = Bytes::from_source(Box::new(pair)); + let view = bytes.view::().unwrap(); + let weak = view.downgrade(); + let upgraded = weak.upgrade().expect("alive"); + assert_eq!(upgraded.a, 100); + assert_eq!(upgraded.b, 200); + drop(view); + drop(upgraded); + assert!(weak.upgrade().is_none()); + } + + /// Multiple view_prefix calls chained together. + #[test] + fn chained_view_prefix() { + let mut bytes = Bytes::from_source(vec![1u8, 2, 3, 4, 5, 6]); + let v1 = bytes.view_prefix::<[u8; 2]>().unwrap(); + let v2 = bytes.view_prefix::<[u8; 2]>().unwrap(); + assert_eq!(*v1, [1, 2]); + assert_eq!(*v2, [3, 4]); + assert_eq!(bytes.as_ref(), &[5, 6]); + drop(v1); + drop(v2); + assert_eq!(bytes.as_ref(), &[5, 6]); + } +} + +// --------------------------------------------------------------------------- +// Complex drop orderings +// --------------------------------------------------------------------------- + +/// Create a web of clones and slices, drop in various orders. +#[test] +fn complex_drop_ordering() { + let original = Bytes::from_source(vec![0u8, 1, 2, 3, 4, 5, 6, 7]); + let clone1 = original.clone(); + let slice1 = original.slice(2..6); + let slice2 = slice1.slice(1..3); + let weak = slice2.downgrade(); + + drop(original); + assert_eq!(clone1.as_ref(), &[0, 1, 2, 3, 4, 5, 6, 7]); + assert_eq!(slice1.as_ref(), &[2, 3, 4, 5]); + assert_eq!(slice2.as_ref(), &[3, 4]); + + drop(slice1); + assert_eq!(slice2.as_ref(), &[3, 4]); + + let upgraded = weak.upgrade().expect("still alive"); + assert_eq!(upgraded.as_ref(), &[3, 4]); + + drop(clone1); + drop(slice2); + drop(upgraded); + assert!(weak.upgrade().is_none()); +} + +/// try_unwrap_owner interleaved with slicing and cloning. +#[test] +fn try_unwrap_after_clone_drop_sequence() { + let bytes = Bytes::from_source(vec![1u8, 2, 3, 4]); + let clone = bytes.clone(); + let slice = bytes.slice(1..3); + + // Three strong refs (bytes, clone, slice) -> unwrap fails + let err = bytes.try_unwrap_owner::>().unwrap_err(); + assert_eq!(err.as_ref(), &[1, 2, 3, 4]); + + drop(err); + drop(slice); + // Only clone remains -> unwrap succeeds + let v = clone.try_unwrap_owner::>().expect("unique"); + assert_eq!(v, vec![1, 2, 3, 4]); +}