Compare commits

...
14 Commits
Author SHA1 Message Date
Kyle BarronandGitHub d0a14deeb5 chore: prepare bytes v1.9.0 (#748) 2024-11-28 13:43:45 +01:00
Leonid LogvinovandGitHub 54f1c26f69 Rename hex_impl! to fmt_impl! and reuse it for fmt::Debug (#743) 2024-11-08 10:23:02 +01:00
Paolo BarboliniandGitHub 4cd8969e85 Replace BufMut::put with BufMut::put_slice in Writer impl (#745) 2024-11-01 18:22:07 +00:00
katelyn martinandGitHub 2d996a2b41 Fix typo in Buf::chunk() comment (#744) 2024-11-01 14:46:09 +00:00
Adam CimarostiandGitHub 30ee8e9cba Add Bytes::from_owner (#742) 2024-10-29 13:18:03 +01:00
Alice RyhlandGitHub c45697ce42 chore: prepare bytes v1.8.0 (#741) 2024-10-21 21:54:34 +02:00
Alice RyhlandGitHub 0ac54ca706 Guarantee address in split_off/split_to for empty slices (#740)
Signed-off-by: Alice Ryhl <[email protected]>
2024-10-18 19:46:49 +02:00
Paolo BarboliniandGitHub d7c1d658d9 chore: prepare bytes v1.7.2 (#736) 2024-09-18 07:39:32 +02:00
Taiki EndoandGitHub ac46ebdd46 ci: update nightly to nightly-2024-09-15 (#734) 2024-09-17 11:02:45 -04:00
Paolo BarboliniandGitHub 79fb85323c fix: apply sign extension when decoding int (#732)
Closes #730
2024-08-30 08:20:29 -04:00
Paolo BarboliniandGitHub 291df5acc9 Fix double spaces in comments and doc comments (#731) 2024-08-19 10:19:35 +02:00
Cameron BythewayandGitHub ed7d5ff39e test: ensure BytesMut::advance reduces capacity (#728) 2024-08-02 19:07:45 +00:00
Alice RyhlandGitHub dc4fb3e8f4 chore: prepare bytes v1.7.1 (#727) 2024-08-01 22:55:24 +02:00
Alice RyhlandGitHub f488be48d0 Revert "Reuse capacity when possible in <BytesMut as Buf>::advance impl" (#726)
This reverts commit baa5053572.
2024-08-01 22:38:17 +02:00
15 changed files with 570 additions and 57 deletions
+4 -1
View File
@@ -13,7 +13,7 @@ on:
env:
RUSTFLAGS: -Dwarnings
RUST_BACKTRACE: 1
nightly: nightly-2022-11-12
nightly: nightly-2024-09-15
defaults:
run:
@@ -136,6 +136,8 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install Rust
run: rustup update $nightly && rustup default $nightly
- name: Miri
run: ci/miri.sh
@@ -160,6 +162,7 @@ jobs:
- minrust
- cross
- tsan
- miri
- loom
runs-on: ubuntu-latest
steps:
+41
View File
@@ -1,3 +1,44 @@
# 1.9.0 (November 27, 2024)
### Added
- Add `Bytes::from_owner` to enable externally-allocated memory (#742)
### Documented
- Fix typo in Buf::chunk() comment (#744)
### Internal changes
- Replace BufMut::put with BufMut::put_slice in Writer impl (#745)
- Rename hex_impl! to fmt_impl! and reuse it for fmt::Debug (#743)
# 1.8.0 (October 21, 2024)
- Guarantee address in `split_off`/`split_to` for empty slices (#740)
# 1.7.2 (September 17, 2024)
### Fixed
- Fix default impl of `Buf::{get_int, get_int_le}` (#732)
### Documented
- Fix double spaces in comments and doc comments (#731)
### Internal changes
- Ensure BytesMut::advance reduces capacity (#728)
# 1.7.1 (August 1, 2024)
This release reverts the following change due to a regression:
- Reuse capacity when possible in `<BytesMut as Buf>::advance` impl (#698)
The revert can be found at #726.
# 1.7.0 (July 31, 2024)
### Added
+1 -1
View File
@@ -4,7 +4,7 @@ name = "bytes"
# When releasing to crates.io:
# - Update CHANGELOG.md.
# - Create "v1.x.y" git tag.
version = "1.7.0"
version = "1.9.0"
edition = "2018"
rust-version = "1.39"
license = "MIT"
+1 -2
View File
@@ -1,8 +1,7 @@
#!/bin/bash
set -e
rustup toolchain install nightly --component miri
rustup override set nightly
rustup component add miri
cargo miri setup
export MIRIFLAGS="-Zmiri-strict-provenance"
+10 -4
View File
@@ -66,6 +66,12 @@ macro_rules! buf_get_impl {
}};
}
// https://en.wikipedia.org/wiki/Sign_extension
fn sign_extend(val: u64, nbytes: usize) -> i64 {
let shift = (8 - nbytes) * 8;
(val << shift) as i64 >> shift
}
/// Read bytes from a buffer.
///
/// A buffer stores bytes in memory such that read operations are infallible.
@@ -119,8 +125,8 @@ pub trait Buf {
fn remaining(&self) -> usize;
/// Returns a slice starting at the current position and of length between 0
/// and `Buf::remaining()`. Note that this *can* return shorter slice (this allows
/// non-continuous internal representation).
/// and `Buf::remaining()`. Note that this *can* return a shorter slice (this
/// allows non-continuous internal representation).
///
/// This is a lower level function. Most operations are done with other
/// functions.
@@ -923,7 +929,7 @@ pub trait Buf {
/// This function panics if there is not enough remaining data in `self`, or
/// if `nbytes` is greater than 8.
fn get_int(&mut self, nbytes: usize) -> i64 {
buf_get_impl!(be => self, i64, nbytes);
sign_extend(self.get_uint(nbytes), nbytes)
}
/// Gets a signed n-byte integer from `self` in little-endian byte order.
@@ -944,7 +950,7 @@ pub trait Buf {
/// This function panics if there is not enough remaining data in `self`, or
/// if `nbytes` is greater than 8.
fn get_int_le(&mut self, nbytes: usize) -> i64 {
buf_get_impl!(le => self, i64, nbytes);
sign_extend(self.get_uint_le(nbytes), nbytes)
}
/// Gets a signed n-byte integer from `self` in native-endian byte order.
+5 -5
View File
@@ -1107,7 +1107,7 @@ pub unsafe trait BufMut {
}
}
/// Writes an IEEE754 single-precision (4 bytes) floating point number to
/// Writes an IEEE754 single-precision (4 bytes) floating point number to
/// `self` in big-endian byte order.
///
/// The current position is advanced by 4.
@@ -1131,7 +1131,7 @@ pub unsafe trait BufMut {
self.put_u32(n.to_bits());
}
/// Writes an IEEE754 single-precision (4 bytes) floating point number to
/// Writes an IEEE754 single-precision (4 bytes) floating point number to
/// `self` in little-endian byte order.
///
/// The current position is advanced by 4.
@@ -1183,7 +1183,7 @@ pub unsafe trait BufMut {
self.put_u32_ne(n.to_bits());
}
/// Writes an IEEE754 double-precision (8 bytes) floating point number to
/// Writes an IEEE754 double-precision (8 bytes) floating point number to
/// `self` in big-endian byte order.
///
/// The current position is advanced by 8.
@@ -1207,7 +1207,7 @@ pub unsafe trait BufMut {
self.put_u64(n.to_bits());
}
/// Writes an IEEE754 double-precision (8 bytes) floating point number to
/// Writes an IEEE754 double-precision (8 bytes) floating point number to
/// `self` in little-endian byte order.
///
/// The current position is advanced by 8.
@@ -1231,7 +1231,7 @@ pub unsafe trait BufMut {
self.put_u64_le(n.to_bits());
}
/// Writes an IEEE754 double-precision (8 bytes) floating point number to
/// Writes an IEEE754 double-precision (8 bytes) floating point number to
/// `self` in native-endian byte order.
///
/// The current position is advanced by 8.
+1 -1
View File
@@ -110,7 +110,7 @@ impl UninitSlice {
unsafe { self[index..].as_mut_ptr().write(byte) }
}
/// Copies bytes from `src` into `self`.
/// Copies bytes from `src` into `self`.
///
/// The length of `src` must be the same as `self`.
///
+1 -1
View File
@@ -78,7 +78,7 @@ impl<B: BufMut + Sized> io::Write for Writer<B> {
fn write(&mut self, src: &[u8]) -> io::Result<usize> {
let n = cmp::min(self.buf.remaining_mut(), src.len());
self.buf.put(&src[0..n]);
self.buf.put_slice(&src[..n]);
Ok(n)
}
+205 -9
View File
@@ -1,6 +1,7 @@
use core::iter::FromIterator;
use core::mem::{self, ManuallyDrop};
use core::ops::{Deref, RangeBounds};
use core::ptr::NonNull;
use core::{cmp, fmt, hash, ptr, slice, usize};
use alloc::{
@@ -142,6 +143,7 @@ impl Bytes {
Bytes::from_static(EMPTY)
}
/// Creates a new empty `Bytes`.
#[cfg(all(loom, test))]
pub fn new() -> Self {
const EMPTY: &[u8] = &[];
@@ -172,6 +174,7 @@ impl Bytes {
}
}
/// Creates a new `Bytes` from a static slice.
#[cfg(all(loom, test))]
pub fn from_static(bytes: &'static [u8]) -> Self {
Bytes {
@@ -182,6 +185,110 @@ impl Bytes {
}
}
/// Creates a new `Bytes` with length zero and the given pointer as the address.
fn new_empty_with_ptr(ptr: *const u8) -> Self {
debug_assert!(!ptr.is_null());
// Detach this pointer's provenance from whichever allocation it came from, and reattach it
// to the provenance of the fake ZST [u8;0] at the same address.
let ptr = without_provenance(ptr as usize);
Bytes {
ptr,
len: 0,
data: AtomicPtr::new(ptr::null_mut()),
vtable: &STATIC_VTABLE,
}
}
/// Create [Bytes] with a buffer whose lifetime is controlled
/// via an explicit owner.
///
/// A common use case is to zero-copy construct from mapped memory.
///
/// ```
/// # struct File;
/// #
/// # impl File {
/// # pub fn open(_: &str) -> Result<Self, ()> {
/// # Ok(Self)
/// # }
/// # }
/// #
/// # mod memmap2 {
/// # pub struct Mmap;
/// #
/// # impl Mmap {
/// # pub unsafe fn map(_file: &super::File) -> Result<Self, ()> {
/// # Ok(Self)
/// # }
/// # }
/// #
/// # impl AsRef<[u8]> for Mmap {
/// # fn as_ref(&self) -> &[u8] {
/// # b"buf"
/// # }
/// # }
/// # }
/// use bytes::Bytes;
/// use memmap2::Mmap;
///
/// # fn main() -> Result<(), ()> {
/// let file = File::open("upload_bundle.tar.gz")?;
/// let mmap = unsafe { Mmap::map(&file) }?;
/// let b = Bytes::from_owner(mmap);
/// # Ok(())
/// # }
/// ```
///
/// The `owner` will be transferred to the constructed [Bytes] object, which
/// will ensure it is dropped once all remaining clones of the constructed
/// object are dropped. The owner will then be responsible for dropping the
/// specified region of memory as part of its [Drop] implementation.
///
/// Note that converting [Bytes] constructed from an owner into a [BytesMut]
/// will always create a deep copy of the buffer into newly allocated memory.
pub fn from_owner<T>(owner: T) -> Self
where
T: AsRef<[u8]> + Send + 'static,
{
// Safety & Miri:
// The ownership of `owner` is first transferred to the `Owned` wrapper and `Bytes` object.
// This ensures that the owner is pinned in memory, allowing us to call `.as_ref()` safely
// since the lifetime of the owner is controlled by the lifetime of the new `Bytes` object,
// and the lifetime of the resulting borrowed `&[u8]` matches that of the owner.
// Note that this remains safe so long as we only call `.as_ref()` once.
//
// There are some additional special considerations here:
// * We rely on Bytes's Drop impl to clean up memory should `.as_ref()` panic.
// * Setting the `ptr` and `len` on the bytes object last (after moving the owner to
// Bytes) allows Miri checks to pass since it avoids obtaining the `&[u8]` slice
// from a stack-owned Box.
// More details on this: https://github.com/tokio-rs/bytes/pull/742/#discussion_r1813375863
// and: https://github.com/tokio-rs/bytes/pull/742/#discussion_r1813316032
let owned = Box::into_raw(Box::new(Owned {
lifetime: OwnedLifetime {
ref_cnt: AtomicUsize::new(1),
drop: owned_box_and_drop::<T>,
},
owner,
}));
let mut ret = Bytes {
ptr: NonNull::dangling().as_ptr(),
len: 0,
data: AtomicPtr::new(owned.cast()),
vtable: &OWNED_VTABLE,
};
let buf = unsafe { &*owned }.owner.as_ref();
ret.ptr = buf.as_ptr();
ret.len = buf.len();
ret
}
/// Returns the number of bytes contained in this `Bytes`.
///
/// # Examples
@@ -212,14 +319,16 @@ impl Bytes {
self.len == 0
}
/// Returns true if this is the only reference to the data.
/// Returns true if this is the only reference to the data and
/// `Into<BytesMut>` would avoid cloning the underlying buffer.
///
/// Always returns false if the data is backed by a static slice.
/// Always returns false if the data is backed by a [static slice](Bytes::from_static),
/// or an [owner](Bytes::from_owner).
///
/// The result of this method may be invalidated immediately if another
/// thread clones this value while this is being called. Ensure you have
/// unique access to this value (`&mut Bytes`) first if you need to be
/// certain the result is valid (i.e. for safety reasons)
/// certain the result is valid (i.e. for safety reasons).
/// # Examples
///
/// ```
@@ -364,7 +473,9 @@ impl Bytes {
/// Splits the bytes into two at the given index.
///
/// Afterwards `self` contains elements `[0, at)`, and the returned `Bytes`
/// contains elements `[at, len)`.
/// contains elements `[at, len)`. It's guaranteed that the memory does not
/// move, that is, the address of `self` does not change, and the address of
/// the returned slice is `at` bytes after that.
///
/// This is an `O(1)` operation that just increases the reference count and
/// sets a few indices.
@@ -387,11 +498,11 @@ impl Bytes {
#[must_use = "consider Bytes::truncate if you don't need the other half"]
pub fn split_off(&mut self, at: usize) -> Self {
if at == self.len() {
return Bytes::new();
return Bytes::new_empty_with_ptr(self.ptr.wrapping_add(at));
}
if at == 0 {
return mem::replace(self, Bytes::new());
return mem::replace(self, Bytes::new_empty_with_ptr(self.ptr));
}
assert!(
@@ -436,11 +547,12 @@ impl Bytes {
#[must_use = "consider Bytes::advance if you don't need the other half"]
pub fn split_to(&mut self, at: usize) -> Self {
if at == self.len() {
return mem::replace(self, Bytes::new());
let end_ptr = self.ptr.wrapping_add(at);
return mem::replace(self, Bytes::new_empty_with_ptr(end_ptr));
}
if at == 0 {
return Bytes::new();
return Bytes::new_empty_with_ptr(self.ptr);
}
assert!(
@@ -515,6 +627,9 @@ impl Bytes {
/// If `self` is not unique for the entire original buffer, this will fail
/// and return self.
///
/// This will also always fail if the buffer was constructed via either
/// [from_owner](Bytes::from_owner) or [from_static](Bytes::from_static).
///
/// # Examples
///
/// ```
@@ -991,6 +1106,83 @@ unsafe fn static_drop(_: &mut AtomicPtr<()>, _: *const u8, _: usize) {
// nothing to drop for &'static [u8]
}
// ===== impl OwnedVtable =====
#[repr(C)]
struct OwnedLifetime {
ref_cnt: AtomicUsize,
drop: unsafe fn(*mut ()),
}
#[repr(C)]
struct Owned<T> {
lifetime: OwnedLifetime,
owner: T,
}
unsafe fn owned_box_and_drop<T>(ptr: *mut ()) {
let b: Box<Owned<T>> = Box::from_raw(ptr as _);
drop(b);
}
unsafe fn owned_clone(data: &AtomicPtr<()>, ptr: *const u8, len: usize) -> Bytes {
let owned = data.load(Ordering::Relaxed);
let ref_cnt = &(*owned.cast::<OwnedLifetime>()).ref_cnt;
let old_cnt = ref_cnt.fetch_add(1, Ordering::Relaxed);
if old_cnt > usize::MAX >> 1 {
crate::abort()
}
Bytes {
ptr,
len,
data: AtomicPtr::new(owned as _),
vtable: &OWNED_VTABLE,
}
}
unsafe fn owned_to_vec(_data: &AtomicPtr<()>, ptr: *const u8, len: usize) -> Vec<u8> {
let slice = slice::from_raw_parts(ptr, len);
slice.to_vec()
}
unsafe fn owned_to_mut(data: &AtomicPtr<()>, ptr: *const u8, len: usize) -> BytesMut {
let bytes_mut = BytesMut::from_vec(owned_to_vec(data, ptr, len));
owned_drop_impl(data.load(Ordering::Relaxed));
bytes_mut
}
unsafe fn owned_is_unique(_data: &AtomicPtr<()>) -> bool {
false
}
unsafe fn owned_drop_impl(owned: *mut ()) {
let lifetime = owned.cast::<OwnedLifetime>();
let ref_cnt = &(*lifetime).ref_cnt;
let old_cnt = ref_cnt.fetch_sub(1, Ordering::Release);
if old_cnt != 1 {
return;
}
ref_cnt.load(Ordering::Acquire);
let drop_fn = &(*lifetime).drop;
drop_fn(owned)
}
unsafe fn owned_drop(data: &mut AtomicPtr<()>, _ptr: *const u8, _len: usize) {
let owned = data.load(Ordering::Relaxed);
owned_drop_impl(owned);
}
static OWNED_VTABLE: Vtable = Vtable {
clone: owned_clone,
to_vec: owned_to_vec,
to_mut: owned_to_mut,
is_unique: owned_is_unique,
drop: owned_drop,
};
// ===== impl PromotableVtable =====
static PROMOTABLE_EVEN_VTABLE: Vtable = Vtable {
@@ -1301,7 +1493,7 @@ unsafe fn shallow_clone_vec(
offset: *const u8,
len: usize,
) -> Bytes {
// If the buffer is still tracked in a `Vec<u8>`. It is time to
// If the buffer is still tracked in a `Vec<u8>`. It is time to
// promote the vec to an `Arc`. This could potentially be called
// concurrently, so some care must be taken.
@@ -1424,6 +1616,10 @@ where
new_addr as *mut u8
}
fn without_provenance(ptr: usize) -> *const u8 {
core::ptr::null::<u8>().wrapping_add(ptr)
}
// compile-fails
/// ```compile_fail
+3 -8
View File
@@ -291,7 +291,9 @@ impl BytesMut {
/// Splits the bytes into two at the given index.
///
/// Afterwards `self` contains elements `[0, at)`, and the returned
/// `BytesMut` contains elements `[at, capacity)`.
/// `BytesMut` contains elements `[at, capacity)`. It's guaranteed that the
/// memory does not move, that is, the address of `self` does not change,
/// and the address of the returned slice is `at` bytes after that.
///
/// This is an `O(1)` operation that just increases the reference count
/// and sets a few indices.
@@ -1148,13 +1150,6 @@ impl Buf for BytesMut {
#[inline]
fn advance(&mut self, cnt: usize) {
// Advancing by the length is the same as resetting the length to 0,
// except this way we get to reuse the full capacity.
if cnt == self.remaining() {
self.clear();
return;
}
assert!(
cnt <= self.remaining(),
"cannot advance past `remaining`: {:?} <= {:?}",
+2 -11
View File
@@ -36,14 +36,5 @@ impl Debug for BytesRef<'_> {
}
}
impl Debug for Bytes {
fn fmt(&self, f: &mut Formatter<'_>) -> Result {
Debug::fmt(&BytesRef(self.as_ref()), f)
}
}
impl Debug for BytesMut {
fn fmt(&self, f: &mut Formatter<'_>) -> Result {
Debug::fmt(&BytesRef(self.as_ref()), f)
}
}
fmt_impl!(Debug, Bytes);
fmt_impl!(Debug, BytesMut);
+4 -14
View File
@@ -21,17 +21,7 @@ impl UpperHex for BytesRef<'_> {
}
}
macro_rules! hex_impl {
($tr:ident, $ty:ty) => {
impl $tr for $ty {
fn fmt(&self, f: &mut Formatter<'_>) -> Result {
$tr::fmt(&BytesRef(self.as_ref()), f)
}
}
};
}
hex_impl!(LowerHex, Bytes);
hex_impl!(LowerHex, BytesMut);
hex_impl!(UpperHex, Bytes);
hex_impl!(UpperHex, BytesMut);
fmt_impl!(LowerHex, Bytes);
fmt_impl!(LowerHex, BytesMut);
fmt_impl!(UpperHex, Bytes);
fmt_impl!(UpperHex, BytesMut);
+10
View File
@@ -1,3 +1,13 @@
macro_rules! fmt_impl {
($tr:ident, $ty:ty) => {
impl $tr for $ty {
fn fmt(&self, f: &mut Formatter<'_>) -> Result {
$tr::fmt(&BytesRef(self.as_ref()), f)
}
}
};
}
mod debug;
mod hex;
+13
View File
@@ -36,6 +36,19 @@ fn test_get_u16() {
assert_eq!(0x5421, buf.get_u16_le());
}
#[test]
fn test_get_int() {
let mut buf = &b"\xd6zomg"[..];
assert_eq!(-42, buf.get_int(1));
let mut buf = &b"\xd6zomg"[..];
assert_eq!(-42, buf.get_int_le(1));
let mut buf = &b"\xfe\x1d\xc0zomg"[..];
assert_eq!(0xffffffffffc01dfeu64 as i64, buf.get_int_le(3));
let mut buf = &b"\xfe\x1d\xc0zomg"[..];
assert_eq!(0xfffffffffffe1dc0u64 as i64, buf.get_int(3));
}
#[test]
#[should_panic]
fn test_get_u16_buffer_underflow() {
+269
View File
@@ -1,7 +1,10 @@
#![warn(rust_2018_idioms)]
use bytes::{Buf, BufMut, Bytes, BytesMut};
use std::sync::atomic::{AtomicUsize, Ordering};
use std::sync::Arc;
use std::panic::{self, AssertUnwindSafe};
use std::usize;
const LONG: &[u8] = b"mary had a little lamb, little lamb, little lamb";
@@ -676,6 +679,43 @@ fn advance_bytes_mut() {
assert_eq!(a, b"d zomg wat wat"[..]);
}
// Ensures BytesMut::advance reduces always capacity
//
// See https://github.com/tokio-rs/bytes/issues/725
#[test]
fn advance_bytes_mut_remaining_capacity() {
// reduce the search space under miri
let max_capacity = if cfg!(miri) { 16 } else { 256 };
for capacity in 0..=max_capacity {
for len in 0..=capacity {
for advance in 0..=len {
eprintln!("testing capacity={capacity}, len={len}, advance={advance}");
let mut buf = BytesMut::with_capacity(capacity);
buf.resize(len, 42);
assert_eq!(buf.len(), len, "resize should write `len` bytes");
assert_eq!(
buf.remaining(),
len,
"Buf::remaining() should equal BytesMut::len"
);
buf.advance(advance);
assert_eq!(
buf.remaining(),
len - advance,
"Buf::advance should reduce the remaining len"
);
assert_eq!(
buf.capacity(),
capacity - advance,
"Buf::advance should reduce the remaining capacity"
);
}
}
}
}
#[test]
#[should_panic]
fn advance_past_len() {
@@ -1362,3 +1402,232 @@ fn try_reclaim_arc() {
buf.advance(2);
assert_eq!(true, buf.try_reclaim(6));
}
#[test]
fn split_off_empty_addr() {
let mut buf = Bytes::from(vec![0; 1024]);
let ptr_start = buf.as_ptr();
let ptr_end = ptr_start.wrapping_add(1024);
let empty_end = buf.split_off(1024);
assert_eq!(empty_end.len(), 0);
assert_eq!(empty_end.as_ptr(), ptr_end);
let _ = buf.split_off(0);
assert_eq!(buf.len(), 0);
assert_eq!(buf.as_ptr(), ptr_start);
// Is miri happy about the provenance?
let _ = &empty_end[..];
let _ = &buf[..];
}
#[test]
fn split_to_empty_addr() {
let mut buf = Bytes::from(vec![0; 1024]);
let ptr_start = buf.as_ptr();
let ptr_end = ptr_start.wrapping_add(1024);
let empty_start = buf.split_to(0);
assert_eq!(empty_start.len(), 0);
assert_eq!(empty_start.as_ptr(), ptr_start);
let _ = buf.split_to(1024);
assert_eq!(buf.len(), 0);
assert_eq!(buf.as_ptr(), ptr_end);
// Is miri happy about the provenance?
let _ = &empty_start[..];
let _ = &buf[..];
}
#[test]
fn split_off_empty_addr_mut() {
let mut buf = BytesMut::from([0; 1024].as_slice());
let ptr_start = buf.as_ptr();
let ptr_end = ptr_start.wrapping_add(1024);
let empty_end = buf.split_off(1024);
assert_eq!(empty_end.len(), 0);
assert_eq!(empty_end.as_ptr(), ptr_end);
let _ = buf.split_off(0);
assert_eq!(buf.len(), 0);
assert_eq!(buf.as_ptr(), ptr_start);
// Is miri happy about the provenance?
let _ = &empty_end[..];
let _ = &buf[..];
}
#[test]
fn split_to_empty_addr_mut() {
let mut buf = BytesMut::from([0; 1024].as_slice());
let ptr_start = buf.as_ptr();
let ptr_end = ptr_start.wrapping_add(1024);
let empty_start = buf.split_to(0);
assert_eq!(empty_start.len(), 0);
assert_eq!(empty_start.as_ptr(), ptr_start);
let _ = buf.split_to(1024);
assert_eq!(buf.len(), 0);
assert_eq!(buf.as_ptr(), ptr_end);
// Is miri happy about the provenance?
let _ = &empty_start[..];
let _ = &buf[..];
}
#[derive(Clone)]
struct SharedAtomicCounter(Arc<AtomicUsize>);
impl SharedAtomicCounter {
pub fn new() -> Self {
SharedAtomicCounter(Arc::new(AtomicUsize::new(0)))
}
pub fn increment(&self) {
self.0.fetch_add(1, Ordering::AcqRel);
}
pub fn get(&self) -> usize {
self.0.load(Ordering::Acquire)
}
}
#[derive(Clone)]
struct OwnedTester<const L: usize> {
buf: [u8; L],
drop_count: SharedAtomicCounter,
pub panic_as_ref: bool,
}
impl<const L: usize> OwnedTester<L> {
fn new(buf: [u8; L], drop_count: SharedAtomicCounter) -> Self {
Self {
buf,
drop_count,
panic_as_ref: false,
}
}
}
impl<const L: usize> AsRef<[u8]> for OwnedTester<L> {
fn as_ref(&self) -> &[u8] {
if self.panic_as_ref {
panic!("test-triggered panic in `AsRef<[u8]> for OwnedTester`");
}
self.buf.as_slice()
}
}
impl<const L: usize> Drop for OwnedTester<L> {
fn drop(&mut self) {
self.drop_count.increment();
}
}
#[test]
fn owned_is_unique_always_false() {
let b1 = Bytes::from_owner([1, 2, 3, 4, 5, 6, 7]);
assert!(!b1.is_unique()); // even if ref_cnt == 1
let b2 = b1.clone();
assert!(!b1.is_unique());
assert!(!b2.is_unique());
drop(b1);
assert!(!b2.is_unique()); // even if ref_cnt == 1
}
#[test]
fn owned_buf_sharing() {
let buf = [1, 2, 3, 4, 5, 6, 7];
let b1 = Bytes::from_owner(buf);
let b2 = b1.clone();
assert_eq!(&buf[..], &b1[..]);
assert_eq!(&buf[..], &b2[..]);
assert_eq!(b1.as_ptr(), b2.as_ptr());
assert_eq!(b1.len(), b2.len());
assert_eq!(b1.len(), buf.len());
}
#[test]
fn owned_buf_slicing() {
let b1 = Bytes::from_owner(SHORT);
assert_eq!(SHORT, &b1[..]);
let b2 = b1.slice(1..(b1.len() - 1));
assert_eq!(&SHORT[1..(SHORT.len() - 1)], b2);
assert_eq!(unsafe { SHORT.as_ptr().add(1) }, b2.as_ptr());
assert_eq!(SHORT.len() - 2, b2.len());
}
#[test]
fn owned_dropped_exactly_once() {
let buf: [u8; 5] = [1, 2, 3, 4, 5];
let drop_counter = SharedAtomicCounter::new();
let owner = OwnedTester::new(buf, drop_counter.clone());
let b1 = Bytes::from_owner(owner);
let b2 = b1.clone();
assert_eq!(drop_counter.get(), 0);
drop(b1);
assert_eq!(drop_counter.get(), 0);
let b3 = b2.slice(1..b2.len() - 1);
drop(b2);
assert_eq!(drop_counter.get(), 0);
drop(b3);
assert_eq!(drop_counter.get(), 1);
}
#[test]
fn owned_to_mut() {
let buf: [u8; 10] = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
let drop_counter = SharedAtomicCounter::new();
let owner = OwnedTester::new(buf, drop_counter.clone());
let b1 = Bytes::from_owner(owner);
// Holding an owner will fail converting to a BytesMut,
// even when the bytes instance has a ref_cnt == 1.
let b1 = b1.try_into_mut().unwrap_err();
// That said, it's still possible, just not cheap.
let bm1: BytesMut = b1.into();
let new_buf = &bm1[..];
assert_eq!(new_buf, &buf[..]);
// `.into::<BytesMut>()` has correctly dropped the owner
assert_eq!(drop_counter.get(), 1);
}
#[test]
fn owned_to_vec() {
let buf: [u8; 10] = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
let drop_counter = SharedAtomicCounter::new();
let owner = OwnedTester::new(buf, drop_counter.clone());
let b1 = Bytes::from_owner(owner);
let v1 = b1.to_vec();
assert_eq!(&v1[..], &buf[..]);
assert_eq!(&v1[..], &b1[..]);
drop(b1);
assert_eq!(drop_counter.get(), 1);
}
#[test]
fn owned_safe_drop_on_as_ref_panic() {
let buf: [u8; 10] = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
let drop_counter = SharedAtomicCounter::new();
let mut owner = OwnedTester::new(buf, drop_counter.clone());
owner.panic_as_ref = true;
let result = panic::catch_unwind(AssertUnwindSafe(|| {
let _ = Bytes::from_owner(owner);
}));
assert!(result.is_err());
assert_eq!(drop_counter.get(), 1);
}