Compare commits

...
16 Commits
Author SHA1 Message Date
d5c8ad3227 Release shared reference on zero truncate (#842)
Co-authored-by: JSap0914 <[email protected]>
2026-07-16 12:19:37 +02:00
bestgopherandGitHub 002df10b8c Simplify shared alignment assertions (#841)
Replace array-underflow checks with const assert expressions.
Add diagnostics that explain the pointer-tagging alignment invariant.

Signed-off-by: bestgopher <[email protected]>
2026-07-14 09:45:41 +02:00
Alice RyhlandGitHub 76c0fbb54e Release bytes v1.12.1 (#838) 2026-07-08 12:00:46 +02:00
Alice RyhlandGitHub 924c82bf00 Handle unwinding from Box::new (#837) 2026-07-07 15:33:07 +02:00
Alice RyhlandGitHub 91402cee60 Release bytes v1.12.0 (#831) 2026-06-18 12:33:12 +02:00
vip892766gmaandGitHub 2256e6dc3e chore: add safety comments on unsafe blocks (#827) 2026-05-19 13:11:51 -04:00
DaniPopesandGitHub 245adff079 Pass vtable data by value (#826) 2026-04-29 07:28:59 +02:00
Mai Thanh MinhandGitHub 00cc5ff2bd Implement BytesMut::extend_from_within (#818) 2026-02-03 15:33:17 +01:00
Alice Ryhl 5b79d316c9 Merge tag 'v1.11.1' 2026-02-03 13:45:19 +00:00
Alice RyhlandGitHub 417dccdeff Release bytes v1.11.1 (#820) 2026-02-03 14:43:56 +01:00
Alice RyhlandGitHub d0293b0e35 Merge commit from fork
* Add repro for integer overflow

Signed-off-by: Alice Ryhl <[email protected]>

* Always check overflow in new_cap + offset

Signed-off-by: Alice Ryhl <[email protected]>

---------

Signed-off-by: Alice Ryhl <[email protected]>
2026-02-03 14:40:22 +01:00
Petros AngelatosandGitHub 804ee6d039 Make try_unsplit method public (#746) 2026-01-23 11:45:15 +01:00
Georg SemmlerandGitHub fd426ca084 Exclude development scripts from published package (#810)
During a dependency review we noticed that the bytes crate includes various development scripts. These development scripts shouldn't be there as they might, at some point become problematic. As of now they prevent any downstream user from enabling the `[bans.build.interpreted]` option of cargo deny.

I opted for using an explicit include list instead of an exclude list to prevent these files from being included in the published packages to make sure that everything that's included is an conscious choice.
2025-12-17 13:51:19 +00:00
Alice RyhlandGitHub b4ed70daee Add test for copy_to_bytes() -> BytesMut avoiding clone (#809) 2025-12-02 13:08:19 +01:00
Paolo BarboliniandGitHub 94e42915a9 Document that BytesMut::{reserve,try_reserve} doesn't preserve unused capacity (#808) 2025-11-28 10:02:34 +01:00
Paolo BarboliniandGitHub acd1e0ffb8 Fix get_int if nbytes is zero (#806) 2025-11-21 11:14:40 +01:00
9 changed files with 452 additions and 150 deletions
+25
View File
@@ -1,3 +1,28 @@
# 1.12.1 (July 8th, 2026)
### Fixed
- Properly handle when `Box::new` panics (#837)
# 1.12.0 (June 18th, 2026)
### Added
- Add `BytesMut::extend_from_within()` (#818)
- Add `BytesMut::try_unsplit()` (#746)
### Fixed
- Fix panic in `get_int` if `nbytes` is zero (#806)
### Changed
- Pass vtable data by value (#826)
- Exclude development scripts from published package (#810)
### Documented
- Document that `BytesMut::{reserve,try_reserve}` doesn't preserve unused capacity (#808)
# 1.11.1 (February 3rd, 2026)
- Fix integer overflow in `BytesMut::reserve`
# 1.11.0 (November 14th, 2025)
- Bump MSRV to 1.57 (#788)
+2 -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.11.0"
version = "1.12.1"
edition = "2021"
rust-version = "1.57"
license = "MIT"
@@ -17,6 +17,7 @@ repository = "https://github.com/tokio-rs/bytes"
readme = "README.md"
keywords = ["buffers", "zero-copy", "io"]
categories = ["network-programming", "data-structures"]
include = ["CHANGELOG.md", "LICENSE", "README.md", "SECURITY.md", "Cargo.toml", "src/**/*.rs", "tests/**/*.rs", "clippy.toml"]
[features]
default = ["std"]
+3
View File
@@ -8,3 +8,6 @@ export MIRIFLAGS="-Zmiri-strict-provenance"
cargo miri test
cargo miri test --target mips64-unknown-linux-gnuabi64
# run with wrapping integer overflow instead of panic
cargo miri test --release
+7 -2
View File
@@ -86,8 +86,13 @@ 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
if nbytes == 0 {
// avoid `val << 64` panic
0
} else {
let shift = (8 - nbytes) * 8;
(val << shift) as i64 >> shift
}
}
/// Read bytes from a buffer.
+112 -114
View File
@@ -1,4 +1,4 @@
use core::mem::{self, ManuallyDrop};
use core::mem::{self, ManuallyDrop, MaybeUninit};
use core::ops::{Deref, RangeBounds};
use core::ptr::NonNull;
use core::{cmp, fmt, hash, ptr, slice};
@@ -106,18 +106,22 @@ pub struct Bytes {
vtable: &'static Vtable,
}
// `data` is passed by value (`*mut ()` instead of `&mut AtomicPtr<()>`)
// when `&mut self` or `self` is consumed.
// This allows the optimizer to see that the address of the `Bytes` is not
// captured by the indirect call, enabling further optimizations.
pub(crate) struct Vtable {
/// fn(data, ptr, len)
pub clone: unsafe fn(&AtomicPtr<()>, *const u8, usize) -> Bytes,
/// fn(data, ptr, len)
///
/// `into_*` consumes the `Bytes`, returning the respective value.
pub into_vec: unsafe fn(&AtomicPtr<()>, *const u8, usize) -> Vec<u8>,
pub into_mut: unsafe fn(&AtomicPtr<()>, *const u8, usize) -> BytesMut,
pub into_vec: unsafe fn(*mut (), *const u8, usize) -> Vec<u8>,
pub into_mut: unsafe fn(*mut (), *const u8, usize) -> BytesMut,
/// fn(data)
pub is_unique: unsafe fn(&AtomicPtr<()>) -> bool,
/// fn(data, ptr, len)
pub drop: unsafe fn(&mut AtomicPtr<()>, *const u8, usize),
pub drop: unsafe fn(*mut (), *const u8, usize),
}
impl Bytes {
@@ -367,34 +371,7 @@ impl Bytes {
/// Requires that `begin <= end` and `end <= self.len()`, otherwise slicing
/// will panic.
pub fn slice(&self, range: impl RangeBounds<usize>) -> Self {
use core::ops::Bound;
let len = self.len();
let begin = match range.start_bound() {
Bound::Included(&n) => n,
Bound::Excluded(&n) => n.checked_add(1).expect("out of range"),
Bound::Unbounded => 0,
};
let end = match range.end_bound() {
Bound::Included(&n) => n.checked_add(1).expect("out of range"),
Bound::Excluded(&n) => n,
Bound::Unbounded => len,
};
assert!(
begin <= end,
"range start must not be greater than end: {:?} <= {:?}",
begin,
end,
);
assert!(
end <= len,
"range end out of bounds: {:?} <= {:?}",
end,
len,
);
let (begin, end) = crate::range(range, self.len());
if end == begin {
return Bytes::new_empty_with_ptr(self.ptr.wrapping_add(begin));
@@ -512,6 +489,8 @@ impl Bytes {
self.len = at;
// SAFETY: `at` has been asserted to be <= `self.len()`, and the
// `at == self.len()` and `at == 0` cases were handled above.
unsafe { ret.inc_start(at) };
ret
@@ -560,6 +539,8 @@ impl Bytes {
let mut ret = self.clone();
// SAFETY: `at` has been asserted to be <= `self.len()`, and the
// `at == self.len()` and `at == 0` cases were handled above.
unsafe { self.inc_start(at) };
ret.len = at;
@@ -586,7 +567,9 @@ impl Bytes {
/// ```
#[inline]
pub fn truncate(&mut self, len: usize) {
if len < self.len {
if len == 0 {
drop(mem::replace(self, Bytes::new_empty_with_ptr(self.ptr)));
} else if len < self.len {
// The Vec "promotable" vtables do not store the capacity,
// so we cannot truncate while using this repr. We *have* to
// promote using `split_off` so the capacity can be stored.
@@ -671,6 +654,11 @@ impl Bytes {
self.len -= by;
self.ptr = self.ptr.add(by);
}
#[inline]
fn data_mut(&mut self) -> *mut () {
self.data.with_mut(|p| *p)
}
}
// Vtable must enforce this behavior
@@ -680,7 +668,8 @@ unsafe impl Sync for Bytes {}
impl Drop for Bytes {
#[inline]
fn drop(&mut self) {
unsafe { (self.vtable.drop)(&mut self.data, self.ptr, self.len) }
let data = self.data_mut();
unsafe { (self.vtable.drop)(data, self.ptr, self.len) }
}
}
@@ -959,24 +948,26 @@ impl From<&'static str> for Bytes {
impl From<Vec<u8>> for Bytes {
fn from(vec: Vec<u8>) -> Bytes {
// Avoid an extra allocation if possible.
if vec.len() == vec.capacity() {
return Bytes::from(vec.into_boxed_slice());
}
let shared = Box::new(MaybeUninit::<Shared>::uninit());
let mut vec = ManuallyDrop::new(vec);
let ptr = vec.as_mut_ptr();
let len = vec.len();
let cap = vec.capacity();
// Avoid an extra allocation if possible.
if len == cap {
let vec = ManuallyDrop::into_inner(vec);
return Bytes::from(vec.into_boxed_slice());
}
let shared = Shared::init_to_raw(
shared,
Shared {
buf: ptr,
cap,
ref_cnt: AtomicUsize::new(1),
},
);
let shared = Box::new(Shared {
buf: ptr,
cap,
ref_cnt: AtomicUsize::new(1),
});
let shared = Box::into_raw(shared);
// The pointer should be aligned, so this assert should
// always succeed.
debug_assert!(
@@ -1040,8 +1031,9 @@ impl From<Bytes> for BytesMut {
/// assert_eq!(BytesMut::from(bytes), BytesMut::from(&b"hello"[..]));
/// ```
fn from(bytes: Bytes) -> Self {
let bytes = ManuallyDrop::new(bytes);
unsafe { (bytes.vtable.into_mut)(&bytes.data, bytes.ptr, bytes.len) }
let mut bytes = ManuallyDrop::new(bytes);
let data = bytes.data_mut();
unsafe { (bytes.vtable.into_mut)(data, bytes.ptr, bytes.len) }
}
}
@@ -1053,8 +1045,9 @@ impl From<String> for Bytes {
impl From<Bytes> for Vec<u8> {
fn from(bytes: Bytes) -> Vec<u8> {
let bytes = ManuallyDrop::new(bytes);
unsafe { (bytes.vtable.into_vec)(&bytes.data, bytes.ptr, bytes.len) }
let mut bytes = ManuallyDrop::new(bytes);
let data = bytes.data_mut();
unsafe { (bytes.vtable.into_vec)(data, bytes.ptr, bytes.len) }
}
}
@@ -1084,12 +1077,12 @@ unsafe fn static_clone(_: &AtomicPtr<()>, ptr: *const u8, len: usize) -> Bytes {
Bytes::from_static(slice)
}
unsafe fn static_to_vec(_: &AtomicPtr<()>, ptr: *const u8, len: usize) -> Vec<u8> {
unsafe fn static_to_vec(_: *mut (), ptr: *const u8, len: usize) -> Vec<u8> {
let slice = slice::from_raw_parts(ptr, len);
slice.to_vec()
}
unsafe fn static_to_mut(_: &AtomicPtr<()>, ptr: *const u8, len: usize) -> BytesMut {
unsafe fn static_to_mut(_: *mut (), ptr: *const u8, len: usize) -> BytesMut {
let slice = slice::from_raw_parts(ptr, len);
BytesMut::from(slice)
}
@@ -1098,7 +1091,7 @@ fn static_is_unique(_: &AtomicPtr<()>) -> bool {
false
}
unsafe fn static_drop(_: &mut AtomicPtr<()>, _: *const u8, _: usize) {
unsafe fn static_drop(_: *mut (), _: *const u8, _: usize) {
// nothing to drop for &'static [u8]
}
@@ -1135,15 +1128,15 @@ unsafe fn owned_clone<T>(data: &AtomicPtr<()>, ptr: *const u8, len: usize) -> By
}
}
unsafe fn owned_to_vec<T>(data: &AtomicPtr<()>, ptr: *const u8, len: usize) -> Vec<u8> {
unsafe fn owned_to_vec<T>(owned: *mut (), ptr: *const u8, len: usize) -> Vec<u8> {
let slice = slice::from_raw_parts(ptr, len);
let vec = slice.to_vec();
owned_drop_impl::<T>(data.load(Ordering::Relaxed));
owned_drop_impl::<T>(owned);
vec
}
unsafe fn owned_to_mut<T>(data: &AtomicPtr<()>, ptr: *const u8, len: usize) -> BytesMut {
BytesMut::from_vec(owned_to_vec::<T>(data, ptr, len))
unsafe fn owned_to_mut<T>(owned: *mut (), ptr: *const u8, len: usize) -> BytesMut {
BytesMut::from_vec(owned_to_vec::<T>(owned, ptr, len))
}
unsafe fn owned_is_unique(_data: &AtomicPtr<()>) -> bool {
@@ -1168,9 +1161,8 @@ unsafe fn owned_drop_impl<T>(owned: *mut ()) {
drop(Box::<Owned<T>>::from_raw(owned.cast()));
}
unsafe fn owned_drop<T>(data: &mut AtomicPtr<()>, _ptr: *const u8, _len: usize) {
let owned = data.load(Ordering::Relaxed);
owned_drop_impl::<T>(owned);
unsafe fn owned_drop<T>(data: *mut (), _ptr: *const u8, _len: usize) {
owned_drop_impl::<T>(data);
}
// ===== impl PromotableVtable =====
@@ -1205,12 +1197,11 @@ unsafe fn promotable_even_clone(data: &AtomicPtr<()>, ptr: *const u8, len: usize
}
unsafe fn promotable_to_vec(
data: &AtomicPtr<()>,
shared: *mut (),
ptr: *const u8,
len: usize,
f: fn(*mut ()) -> *mut u8,
) -> Vec<u8> {
let shared = data.load(Ordering::Acquire);
let kind = shared as usize & KIND_MASK;
if kind == KIND_ARC {
@@ -1231,12 +1222,11 @@ unsafe fn promotable_to_vec(
}
unsafe fn promotable_to_mut(
data: &AtomicPtr<()>,
shared: *mut (),
ptr: *const u8,
len: usize,
f: fn(*mut ()) -> *mut u8,
) -> BytesMut {
let shared = data.load(Ordering::Acquire);
let kind = shared as usize & KIND_MASK;
if kind == KIND_ARC {
@@ -1259,31 +1249,28 @@ unsafe fn promotable_to_mut(
}
}
unsafe fn promotable_even_to_vec(data: &AtomicPtr<()>, ptr: *const u8, len: usize) -> Vec<u8> {
promotable_to_vec(data, ptr, len, |shared| {
unsafe fn promotable_even_to_vec(shared: *mut (), ptr: *const u8, len: usize) -> Vec<u8> {
promotable_to_vec(shared, ptr, len, |shared| {
ptr_map(shared.cast(), |addr| addr & !KIND_MASK)
})
}
unsafe fn promotable_even_to_mut(data: &AtomicPtr<()>, ptr: *const u8, len: usize) -> BytesMut {
promotable_to_mut(data, ptr, len, |shared| {
unsafe fn promotable_even_to_mut(shared: *mut (), ptr: *const u8, len: usize) -> BytesMut {
promotable_to_mut(shared, ptr, len, |shared| {
ptr_map(shared.cast(), |addr| addr & !KIND_MASK)
})
}
unsafe fn promotable_even_drop(data: &mut AtomicPtr<()>, ptr: *const u8, len: usize) {
data.with_mut(|shared| {
let shared = *shared;
let kind = shared as usize & KIND_MASK;
unsafe fn promotable_even_drop(shared: *mut (), ptr: *const u8, len: usize) {
let kind = shared as usize & KIND_MASK;
if kind == KIND_ARC {
release_shared(shared.cast());
} else {
debug_assert_eq!(kind, KIND_VEC);
let buf = ptr_map(shared.cast(), |addr| addr & !KIND_MASK);
free_boxed_slice(buf, ptr, len);
}
});
if kind == KIND_ARC {
release_shared(shared.cast());
} else {
debug_assert_eq!(kind, KIND_VEC);
let buf = ptr_map(shared.cast(), |addr| addr & !KIND_MASK);
free_boxed_slice(buf, ptr, len);
}
}
unsafe fn promotable_odd_clone(data: &AtomicPtr<()>, ptr: *const u8, len: usize) -> Bytes {
@@ -1298,27 +1285,24 @@ unsafe fn promotable_odd_clone(data: &AtomicPtr<()>, ptr: *const u8, len: usize)
}
}
unsafe fn promotable_odd_to_vec(data: &AtomicPtr<()>, ptr: *const u8, len: usize) -> Vec<u8> {
promotable_to_vec(data, ptr, len, |shared| shared.cast())
unsafe fn promotable_odd_to_vec(shared: *mut (), ptr: *const u8, len: usize) -> Vec<u8> {
promotable_to_vec(shared, ptr, len, |shared| shared.cast())
}
unsafe fn promotable_odd_to_mut(data: &AtomicPtr<()>, ptr: *const u8, len: usize) -> BytesMut {
promotable_to_mut(data, ptr, len, |shared| shared.cast())
unsafe fn promotable_odd_to_mut(shared: *mut (), ptr: *const u8, len: usize) -> BytesMut {
promotable_to_mut(shared, ptr, len, |shared| shared.cast())
}
unsafe fn promotable_odd_drop(data: &mut AtomicPtr<()>, ptr: *const u8, len: usize) {
data.with_mut(|shared| {
let shared = *shared;
let kind = shared as usize & KIND_MASK;
unsafe fn promotable_odd_drop(shared: *mut (), ptr: *const u8, len: usize) {
let kind = shared as usize & KIND_MASK;
if kind == KIND_ARC {
release_shared(shared.cast());
} else {
debug_assert_eq!(kind, KIND_VEC);
if kind == KIND_ARC {
release_shared(shared.cast());
} else {
debug_assert_eq!(kind, KIND_VEC);
free_boxed_slice(shared.cast(), ptr, len);
}
});
free_boxed_slice(shared.cast(), ptr, len);
}
}
unsafe fn promotable_is_unique(data: &AtomicPtr<()>) -> bool {
@@ -1347,6 +1331,15 @@ struct Shared {
ref_cnt: AtomicUsize,
}
impl Shared {
fn init_to_raw(b: Box<MaybeUninit<Self>>, v: Self) -> *mut Self {
let shared = Box::into_raw(b).cast::<Self>();
// SAFETY: The Box has the right layout.
unsafe { shared.write(v) };
shared
}
}
impl Drop for Shared {
fn drop(&mut self) {
unsafe { dealloc(self.buf, Layout::from_size_align(self.cap, 1).unwrap()) }
@@ -1357,7 +1350,12 @@ impl Drop for Shared {
// This is a necessary invariant since we depend on allocating `Shared` a
// shared object to implicitly carry the `KIND_ARC` flag in its pointer.
// This flag is set when the LSB is 0.
const _: [(); 0 - mem::align_of::<Shared>() % 2] = []; // Assert that the alignment of `Shared` is divisible by 2.
const _: () = {
assert!(
mem::align_of::<Shared>() % 2 == 0,
"Shared alignment must be divisible by 2 for pointer tagging"
);
};
static SHARED_VTABLE: Vtable = Vtable {
clone: shared_clone,
@@ -1405,8 +1403,8 @@ unsafe fn shared_to_vec_impl(shared: *mut Shared, ptr: *const u8, len: usize) ->
}
}
unsafe fn shared_to_vec(data: &AtomicPtr<()>, ptr: *const u8, len: usize) -> Vec<u8> {
shared_to_vec_impl(data.load(Ordering::Relaxed).cast(), ptr, len)
unsafe fn shared_to_vec(shared: *mut (), ptr: *const u8, len: usize) -> Vec<u8> {
shared_to_vec_impl(shared.cast(), ptr, len)
}
unsafe fn shared_to_mut_impl(shared: *mut Shared, ptr: *const u8, len: usize) -> BytesMut {
@@ -1444,8 +1442,8 @@ unsafe fn shared_to_mut_impl(shared: *mut Shared, ptr: *const u8, len: usize) ->
}
}
unsafe fn shared_to_mut(data: &AtomicPtr<()>, ptr: *const u8, len: usize) -> BytesMut {
shared_to_mut_impl(data.load(Ordering::Relaxed).cast(), ptr, len)
unsafe fn shared_to_mut(shared: *mut (), ptr: *const u8, len: usize) -> BytesMut {
shared_to_mut_impl(shared.cast(), ptr, len)
}
pub(crate) unsafe fn shared_is_unique(data: &AtomicPtr<()>) -> bool {
@@ -1454,10 +1452,8 @@ pub(crate) unsafe fn shared_is_unique(data: &AtomicPtr<()>) -> bool {
ref_cnt == 1
}
unsafe fn shared_drop(data: &mut AtomicPtr<()>, _ptr: *const u8, _len: usize) {
data.with_mut(|shared| {
release_shared(shared.cast());
});
unsafe fn shared_drop(shared: *mut (), _ptr: *const u8, _len: usize) {
release_shared(shared.cast());
}
unsafe fn shallow_clone_arc(shared: *mut Shared, ptr: *const u8, len: usize) -> Bytes {
@@ -1494,16 +1490,18 @@ unsafe fn shallow_clone_vec(
// updated and since the buffer hasn't been promoted to an
// `Arc`, those three fields still are the components of the
// vector.
let shared = Box::new(Shared {
buf,
cap: offset.offset_from(buf) as usize + len,
// Initialize refcount to 2. One for this reference, and one
// for the new clone that will be returned from
// `shallow_clone`.
ref_cnt: AtomicUsize::new(2),
});
let shared = Box::into_raw(shared);
let shared = Box::new(MaybeUninit::<Shared>::uninit());
let shared = Shared::init_to_raw(
shared,
Shared {
buf,
cap: offset.offset_from(buf) as usize + len,
// Initialize refcount to 2. One for this reference, and one
// for the new clone that will be returned from
// `shallow_clone`.
ref_cnt: AtomicUsize::new(2),
},
);
// The pointer should be aligned, so this assert should
// always succeed.
+123 -26
View File
@@ -79,11 +79,25 @@ struct Shared {
ref_count: AtomicUsize,
}
impl Shared {
fn init_to_raw(b: Box<MaybeUninit<Self>>, v: Self) -> *mut Self {
let shared = Box::into_raw(b).cast::<Self>();
// SAFETY: The Box has the right layout.
unsafe { shared.write(v) };
shared
}
}
// Assert that the alignment of `Shared` is divisible by 2.
// This is a necessary invariant since we depend on allocating `Shared` a
// shared object to implicitly carry the `KIND_ARC` flag in its pointer.
// This flag is set when the LSB is 0.
const _: [(); 0 - mem::align_of::<Shared>() % 2] = []; // Assert that the alignment of `Shared` is divisible by 2.
const _: () = {
assert!(
mem::align_of::<Shared>() % 2 == 0,
"Shared alignment must be divisible by 2 for pointer tagging"
);
};
// Buffer storage strategy flags.
const KIND_ARC: usize = 0b0;
@@ -324,6 +338,10 @@ impl BytesMut {
self.capacity(),
);
unsafe {
// SAFETY: `shallow_clone` increments the reference count (or
// promotes to shared) and returns a bitwise copy of the handle.
// The caller immediately adjusts both handles so they represent
// disjoint regions.
let mut other = self.shallow_clone();
// SAFETY: We've checked that `at` <= `self.capacity()` above.
other.advance_unchecked(at);
@@ -400,6 +418,10 @@ impl BytesMut {
);
unsafe {
// SAFETY: `shallow_clone` increments the reference count (or
// promotes to shared) and returns a bitwise copy of the handle.
// The caller immediately adjusts both handles so they represent
// disjoint regions.
let mut other = self.shallow_clone();
// SAFETY: We've checked that `at` <= `self.len()` and we know that `self.len()` <=
// `self.capacity()`.
@@ -551,6 +573,8 @@ impl BytesMut {
/// and the original buffer is large enough to fit the requested additional
/// capacity, then reallocations will never happen.
///
/// This method does not preserve data stored in the unused capacity.
///
/// # Examples
///
/// In the following example, a new buffer is allocated.
@@ -695,9 +719,15 @@ impl BytesMut {
let offset = self.ptr.as_ptr().offset_from(ptr) as usize;
let new_cap_plus_offset = match new_cap.checked_add(offset) {
Some(new_cap_plus_offset) => new_cap_plus_offset,
None if !allocate => return false,
None => panic!("overflow"),
};
// Compare the condition in the `kind == KIND_VEC` case above
// for more details.
if v_capacity >= new_cap + offset {
if v_capacity >= new_cap_plus_offset {
self.cap = new_cap;
// no copy is necessary
} else if v_capacity >= new_cap && offset >= len {
@@ -713,14 +743,12 @@ impl BytesMut {
if !allocate {
return false;
}
// calculate offset
let off = (self.ptr.as_ptr() as usize) - (v.as_ptr() as usize);
// new_cap is calculated in terms of `BytesMut`, not the underlying
// `Vec`, so it does not take the offset into account.
//
// Thus we have to manually add it here.
new_cap = new_cap.checked_add(off).expect("overflow");
new_cap = new_cap_plus_offset;
// The vector capacity is not sufficient. The reserve request is
// asking for more than the initial buffer capacity. Allocate more
@@ -742,13 +770,13 @@ impl BytesMut {
// the unused capacity of the vector is copied over to the new
// allocation, so we need to ensure that we don't have any data we
// care about in the unused capacity before calling `reserve`.
debug_assert!(off + len <= v.capacity());
v.set_len(off + len);
debug_assert!(offset + len <= v.capacity());
v.set_len(offset + len);
v.reserve(new_cap - v.len());
// Update the info
self.ptr = vptr(v.as_mut_ptr().add(off));
self.cap = v.capacity() - off;
self.ptr = vptr(v.as_mut_ptr().add(offset));
self.cap = v.capacity() - offset;
}
return true;
@@ -797,6 +825,8 @@ impl BytesMut {
/// references through other `BytesMut`s or `Bytes` which point to the same underlying
/// storage.
///
/// This method does not preserve data stored in the unused capacity.
///
/// # Examples
///
/// ```
@@ -876,7 +906,43 @@ impl BytesMut {
}
}
/// Absorbs a `BytesMut` that was previously split off.
/// Clones the elements in the given `range` within this `BytesMut` and
/// appends them to the end.
///
/// # Panics
///
/// Panics if `range` is out of bounds for this `BytesMut`.
///
/// # Examples
///
/// ```
/// use bytes::BytesMut;
///
/// let mut buf = BytesMut::with_capacity(0);
/// buf.extend_from_slice(b"aaabbb_");
/// buf.extend_from_within(3..6);
///
/// assert_eq!(b"aaabbb_bbb", &buf[..]);
/// ```
pub fn extend_from_within(&mut self, range: impl core::ops::RangeBounds<usize>) {
let (begin, end) = crate::range(range, self.len());
let cnt = end - begin;
self.reserve(cnt);
// SAFETY: range is already checked
let src = unsafe { self.as_ptr().add(begin) };
let dst = self.spare_capacity_mut();
// SAFETY: range doesn't overlap with spare capacity
unsafe { ptr::copy_nonoverlapping(src, dst.as_mut_ptr().cast(), cnt) }
// SAFETY: capacity is already reserved and filled with data
unsafe { self.advance_mut(cnt) }
}
/// Absorbs a `BytesMut` that was previously split off if they are
/// contiguous, otherwise appends its bytes to this `BytesMut`.
///
/// If the two `BytesMut` objects were previously contiguous and not mutated
/// in a way that causes re-allocation i.e., if `other` was created by
@@ -989,7 +1055,35 @@ impl BytesMut {
self.cap -= count;
}
fn try_unsplit(&mut self, other: BytesMut) -> Result<(), BytesMut> {
/// Absorbs a `BytesMut` that was previously split off.
///
/// If the two `BytesMut` objects were previously contiguous, i.e., if
/// `other` was created by calling `split_off` on this `BytesMut`, then
/// this is an `O(1)` operation that just decreases a reference
/// count and sets a few indices. Otherwise this method returns an error
/// containing the original `other`.
///
/// # Examples
///
/// ```
/// use bytes::BytesMut;
///
/// let mut buf = BytesMut::with_capacity(64);
/// buf.extend_from_slice(b"aaabbbcccddd");
///
/// let mut split_1 = buf.split_off(3);
/// let split_2 = split_1.split_off(3);
/// assert_eq!(b"aaa", &buf[..]);
/// assert_eq!(b"bbb", &split_1[..]);
/// assert_eq!(b"cccddd", &split_2[..]);
///
/// let split_2 = buf.try_unsplit(split_2).unwrap_err();
///
/// buf.try_unsplit(split_1).unwrap();
/// buf.try_unsplit(split_2).unwrap();
/// assert_eq!(b"aaabbbcccddd", &buf[..]);
/// ```
pub fn try_unsplit(&mut self, other: BytesMut) -> Result<(), BytesMut> {
if other.capacity() == 0 {
return Ok(());
}
@@ -1032,13 +1126,18 @@ impl BytesMut {
// updated and since the buffer hasn't been promoted to an
// `Arc`, those three fields still are the components of the
// vector.
let shared = Box::new(Shared {
vec: rebuild_vec(self.ptr.as_ptr(), self.len, self.cap, off),
original_capacity_repr,
ref_count: AtomicUsize::new(ref_cnt),
});
let shared = Box::into_raw(shared);
//
// Explicitly allocate before invoking rebuild_vec() so that
// the vector is not dropped if Box::new() panics.
let shared = Box::new(MaybeUninit::<Shared>::uninit());
let shared = Shared::init_to_raw(
shared,
Shared {
vec: rebuild_vec(self.ptr.as_ptr(), self.len, self.cap, off),
original_capacity_repr,
ref_count: AtomicUsize::new(ref_cnt),
},
);
// The pointer should be aligned, so this assert should
// always succeed.
@@ -1806,8 +1905,8 @@ unsafe fn shared_v_clone(data: &AtomicPtr<()>, ptr: *const u8, len: usize) -> By
Bytes::with_vtable(ptr, len, data, &SHARED_VTABLE)
}
unsafe fn shared_v_to_vec(data: &AtomicPtr<()>, ptr: *const u8, len: usize) -> Vec<u8> {
let shared: *mut Shared = data.load(Ordering::Relaxed).cast();
unsafe fn shared_v_to_vec(shared: *mut (), ptr: *const u8, len: usize) -> Vec<u8> {
let shared: *mut Shared = shared.cast();
if (*shared).is_unique() {
let shared = &mut *shared;
@@ -1828,8 +1927,8 @@ unsafe fn shared_v_to_vec(data: &AtomicPtr<()>, ptr: *const u8, len: usize) -> V
}
}
unsafe fn shared_v_to_mut(data: &AtomicPtr<()>, ptr: *const u8, len: usize) -> BytesMut {
let shared: *mut Shared = data.load(Ordering::Relaxed).cast();
unsafe fn shared_v_to_mut(shared: *mut (), ptr: *const u8, len: usize) -> BytesMut {
let shared: *mut Shared = shared.cast();
if (*shared).is_unique() {
let shared = &mut *shared;
@@ -1863,10 +1962,8 @@ unsafe fn shared_v_is_unique(data: &AtomicPtr<()>) -> bool {
ref_count == 1
}
unsafe fn shared_v_drop(data: &mut AtomicPtr<()>, _ptr: *const u8, _len: usize) {
data.with_mut(|shared| {
release_shared(*shared as *mut Shared);
});
unsafe fn shared_v_drop(shared: *mut (), _ptr: *const u8, _len: usize) {
release_shared(shared.cast());
}
// compile-fails
+37
View File
@@ -129,6 +129,43 @@ fn min_u64_usize(a: u64, b: usize) -> usize {
}
}
/// Performs bounds checking of a range.
///
/// This is a spiritual copy of [core::slice::index::range] because that
/// function is currently unstable.
#[inline(always)]
#[track_caller]
fn range(range: impl core::ops::RangeBounds<usize>, len: usize) -> (usize, usize) {
use core::ops::Bound;
let begin = match range.start_bound() {
Bound::Included(&n) => n,
Bound::Excluded(&n) => n.checked_add(1).expect("out of range"),
Bound::Unbounded => 0,
};
let end = match range.end_bound() {
Bound::Included(&n) => n.checked_add(1).expect("out of range"),
Bound::Excluded(&n) => n,
Bound::Unbounded => len,
};
assert!(
begin <= end,
"range start must not be greater than end: {:?} <= {:?}",
begin,
end,
);
assert!(
end <= len,
"range end out of bounds: {:?} <= {:?}",
end,
len,
);
(begin, end)
}
/// Error type for the `try_get_` methods of [`Buf`].
/// Indicates that there were not enough remaining
/// bytes in the buffer while attempting
+29 -7
View File
@@ -250,12 +250,12 @@ macro_rules! buf_tests {
buf_tests!(number $make_input, get_f64_le, get_f64_le_overflow, f64, get_f64_le, f64::from_bits(0x7144726a727146ff));
buf_tests!(number $make_input, get_f64_ne, get_f64_ne_overflow, f64, get_f64_ne, f64::from_bits(e!(0xff4671726a724471, 0x7144726a727146ff)));
buf_tests!(var_number $make_input, get_uint_be, get_uint_be_overflow, u64, get_uint, 3, 0xff4671);
buf_tests!(var_number $make_input, get_uint_le, get_uint_le_overflow, u64, get_uint_le, 3, 0x7146ff);
buf_tests!(var_number $make_input, get_uint_ne, get_uint_ne_overflow, u64, get_uint_ne, 3, e!(0xff4671, 0x7146ff));
buf_tests!(var_number $make_input, get_int_be, get_int_be_overflow, i64, get_int, 3, 0xffffffffffff4671u64 as i64);
buf_tests!(var_number $make_input, get_int_le, get_int_le_overflow, i64, get_int_le, 3, 0x7146ff);
buf_tests!(var_number $make_input, get_int_ne, get_int_ne_overflow, i64, get_int_ne, 3, e!(0xffffffffffff4671u64 as i64, 0x7146ff));
buf_tests!(var_number $make_input, get_uint_be, get_uint_be_zero, get_uint_be_overflow, u64, get_uint, 3, 0xff4671);
buf_tests!(var_number $make_input, get_uint_le, get_uint_le_zero, get_uint_le_overflow, u64, get_uint_le, 3, 0x7146ff);
buf_tests!(var_number $make_input, get_uint_ne, get_uint_ne_zero, get_uint_ne_overflow, u64, get_uint_ne, 3, e!(0xff4671, 0x7146ff));
buf_tests!(var_number $make_input, get_int_be, get_int_be_zero, get_int_be_overflow, i64, get_int, 3, 0xffffffffffff4671u64 as i64);
buf_tests!(var_number $make_input, get_int_le, get_int_le_zero, get_int_le_overflow, i64, get_int_le, 3, 0x7146ff);
buf_tests!(var_number $make_input, get_int_ne, get_int_ne_zero, get_int_ne_overflow, i64, get_int_ne, 3, e!(0xffffffffffff4671u64 as i64, 0x7146ff));
};
(number $make_input:ident, $ok_name:ident, $panic_name:ident, $number:ty, $method:ident, $value:expr) => {
#[test]
@@ -276,7 +276,7 @@ macro_rules! buf_tests {
let _ = buf.$method();
}
};
(var_number $make_input:ident, $ok_name:ident, $panic_name:ident, $number:ty, $method:ident, $len:expr, $value:expr) => {
(var_number $make_input:ident, $ok_name:ident, $ok_zero_name:ident, $panic_name:ident, $number:ty, $method:ident, $len:expr, $value:expr) => {
#[test]
fn $ok_name() {
let mut buf = $make_input(INPUT);
@@ -287,6 +287,17 @@ macro_rules! buf_tests {
assert_eq!(value, $value);
}
// Regression test for https://github.com/tokio-rs/bytes/issues/798
#[test]
fn $ok_zero_name() {
let mut buf = $make_input(INPUT);
let value = buf.$method(0);
assert_eq!(buf.remaining(), 64);
assert!(buf.has_remaining());
assert_eq!(value, 0);
}
#[test]
#[should_panic]
fn $panic_name() {
@@ -437,3 +448,14 @@ fn test_deref_buf_forwards() {
assert_eq!((Box::new(Special) as Box<dyn Buf>).get_u8(), b'x');
assert_eq!(Box::new(Special).get_u8(), b'x');
}
#[test]
fn copy_to_bytes_mut() {
let mut bytes_mut = BytesMut::from(b"foobar".as_slice());
let ptr = bytes_mut.as_ptr();
let ret = bytes_mut.copy_to_bytes(bytes_mut.len());
assert_eq!(ret.as_ptr(), ptr);
drop(bytes_mut);
let bytes_mut2 = BytesMut::from(ret);
assert_eq!(bytes_mut2.as_ptr(), ptr);
}
+114
View File
@@ -339,6 +339,31 @@ fn truncate() {
assert_eq!(hello, "hello");
}
#[test]
fn truncate_to_zero_releases_shared_reference() {
let mut bytes = BytesMut::from(&b"hello"[..]);
drop(bytes.split_off(bytes.len()));
let mut truncated = bytes.freeze();
let remaining = truncated.clone();
truncated.truncate(0);
assert!(truncated.is_empty());
let mut remaining = remaining.try_into_mut().unwrap();
remaining[0] = b'H';
assert_eq!(remaining, b"Hello"[..]);
let mut bytes = BytesMut::from(&b"hello"[..]);
drop(bytes.split_off(bytes.len()));
let mut nonempty = bytes.freeze();
let remaining = nonempty.clone();
nonempty.truncate(1);
assert_eq!(nonempty, b"h"[..]);
assert!(remaining.try_into_mut().is_err());
}
#[test]
fn freeze_clone_shared() {
let s = &b"abcdefgh"[..];
@@ -607,6 +632,23 @@ fn extend_from_slice_mut() {
}
}
#[test]
fn extend_from_within_normal() {
let mut bytes = BytesMut::new();
bytes.extend_from_slice(&LONG[..23]);
bytes.extend_from_within(10..22);
bytes.extend_from_within(22..35);
assert_eq!(LONG[..], *bytes);
}
#[test]
#[should_panic]
fn extend_from_within_out_of_range() {
let mut bytes = BytesMut::new();
bytes.extend_from_slice(&LONG[..23]);
bytes.extend_from_within(23..=23);
}
#[test]
fn extend_mut_from_bytes() {
let mut bytes = BytesMut::with_capacity(0);
@@ -1526,6 +1568,65 @@ fn split_to_empty_addr_mut() {
let _ = &buf[..];
}
#[test]
fn bytes_mut_split_boundary_capacities() {
// VEC mode
for at in [0, 5, 11] {
let mut buf = BytesMut::with_capacity(64);
buf.extend_from_slice(b"hello world");
let other = buf.split_off(at);
assert_eq!(
buf.capacity() + other.capacity(),
64,
"split_off at {} should preserve total capacity",
at
);
}
for at in [0, 5, 11] {
let mut buf = BytesMut::with_capacity(64);
buf.extend_from_slice(b"hello world");
let other = buf.split_to(at);
assert_eq!(
buf.capacity() + other.capacity(),
64,
"split_to at {} should preserve total capacity",
at
);
}
// ARC mode (promote via a no-op split)
for at in [0, 5, 11] {
let mut buf = BytesMut::with_capacity(64);
buf.extend_from_slice(b"hello world");
let _ = buf.split_to(0); // promotes to ARC
let other = buf.split_off(at);
assert_eq!(
buf.capacity() + other.capacity(),
64,
"ARC split_off at {} should preserve total capacity",
at
);
}
for at in [0, 5, 11] {
let mut buf = BytesMut::with_capacity(64);
buf.extend_from_slice(b"hello world");
let _ = buf.split_to(0); // promotes to ARC
let other = buf.split_to(at);
assert_eq!(
buf.capacity() + other.capacity(),
64,
"ARC split_to at {} should preserve total capacity",
at
);
}
}
#[derive(Clone)]
struct SharedAtomicCounter(Arc<AtomicUsize>);
@@ -1707,3 +1808,16 @@ fn bytes_mut_put_bytes_specialization() {
// If allocation is reused, capacity should be equal to original vec capacity.
assert_eq!(bytes_mut.capacity(), capacity);
}
#[test]
#[should_panic]
fn bytes_mut_reserve_overflow() {
let mut a = BytesMut::from(&b"hello world"[..]);
let mut b = a.split_off(5);
// Ensure b becomes the unique owner of the backing storage
drop(a);
// Trigger overflow in new_cap + offset inside reserve
b.reserve(usize::MAX - 6);
// This call relies on the corrupted cap and may cause UB & HBO
b.put_u8(b'h');
}